本文实例为大家分享了Android实现圆形云标签效果展示的具体代码,供大家参考,具体内容如下
下面是实现的效果图:
这个适合用于选择 用户的一些兴趣标签,个性名片等。
代码:
Activity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
|
package com.dyl.cloudtags; import java.util.ArrayList; import java.util.Arrays; import java.util.Random; import android.app.Activity; import android.content.SharedPreferences; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.EditText; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends Activity { private KeywordsFlow keywordsFlow; private String[] keywords; public static final String SEARCH_HISTORY = "search_history" ; private ArrayList<SearchDataPojo> searchItem; private String longhistory; private SharedPreferences sp; private ArrayList<String> history; private EditText world_shopping_search_input; private TextView world_city_refresh, clear_history; private ImageView toSearch; @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); initSearchHistory(); refreshTags(); } private void initView() { world_shopping_search_input = (EditText) findViewById(R.id.world_shopping_search_input); keywordsFlow = (KeywordsFlow) findViewById(R.id.keywordsflow); world_city_refresh = (TextView) findViewById(R.id.world_city_refresh); world_city_refresh.setOnClickListener( new OnClickListener() { @Override public void onClick(View arg0) { refreshTags(); } }); clear_history = (TextView) findViewById(R.id.clear_history); clear_history.setOnClickListener( new OnClickListener() { @Override public void onClick(View arg0) { clearSearchHistory(); } }); toSearch = (ImageView) findViewById(R.id.toSearch); toSearch.setOnClickListener( new OnClickListener() { @Override public void onClick(View arg0) { saveSearchHistory(); refreshTags(); } }); } private void refreshTags() { initSearchHistory(); keywordsFlow.setDuration(800l); keywordsFlow.setOnItemClickListener( new OnClickListener() { @Override public void onClick(View v) { String keyword = ((TextView) v).getText().toString(); // 获得点击的标签 world_shopping_search_input.setText(keyword); } }); // 添加 feedKeywordsFlow(keywordsFlow, keywords); keywordsFlow.go2Show(KeywordsFlow.ANIMATION_IN); } private static void feedKeywordsFlow(KeywordsFlow keywordsFlow, String[] arr) { Random random = new Random(); for ( int i = 0 ; i < KeywordsFlow.MAX; i++) { int ran = random.nextInt(arr.length); String tmp = arr[ran]; keywordsFlow.feedKeyword(tmp); } } /** * 读取历史搜索记录 */ private void initSearchHistory() { sp = getSharedPreferences(MainActivity.SEARCH_HISTORY, 0 ); longhistory = sp.getString(MainActivity.SEARCH_HISTORY, "" ); if (!longhistory.equals( "" )) { keywords = longhistory.split( "," ); searchItem = new ArrayList<SearchDataPojo>(); for ( int i = 0 ; i < keywords.length; i++) { searchItem.add( new SearchDataPojo().setContent(keywords[i])); } } else { // 如果SharedPreferences没有值得话,就显示默认的数据 keywords = new String[] { "口味虾" , "牛蛙" , "火锅" , "真功夫" , "料理" , "密室逃" , "天成房" , "波比艾" }; } } /* * 保存搜索记录 */ private void saveSearchHistory() { String text = world_shopping_search_input.getText().toString().trim(); Toast.makeText( this , text, Toast.LENGTH_LONG).show(); if (!text.equals( "" ) && text != null ) { if (text.length() < 1 ) { return ; } sp = getSharedPreferences(SEARCH_HISTORY, 0 ); String longhistory = sp.getString(SEARCH_HISTORY, "" ); String[] tmpHistory = longhistory.split( "," ); history = new ArrayList<String>(Arrays.asList(tmpHistory)); if (history.size() > 0 ) { int i; for (i = 0 ; i < history.size(); i++) { if (text.equals(history.get(i))) { history.remove(i); break ; } } history.add( 0 , text); } if (history.size() > 0 ) { StringBuilder sb = new StringBuilder(); for ( int i = 0 ; i < history.size(); i++) { sb.append(history.get(i) + "," ); } sp.edit().putString(SEARCH_HISTORY, sb.toString()).commit(); } else { sp.edit().putString(SEARCH_HISTORY, text + "," ).commit(); } clear_history.setVisibility(View.VISIBLE); } } // 清除历史数据 private void clearSearchHistory() { searchItem.removeAll(searchItem); sp.edit().clear().commit(); Toast.makeText( this , "清除历史记录" , Toast.LENGTH_LONG).show(); } } |
用于将控件设置为圆形的自定义TextView
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
package com.dyl.cloudtags; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PaintFlagsDrawFilter; import android.util.AttributeSet; import android.widget.TextView; public class CircleView extends TextView { private Paint mBgPaint = new Paint(); PaintFlagsDrawFilter pfd = new PaintFlagsDrawFilter( 0 , Paint.ANTI_ALIAS_FLAG|Paint.FILTER_BITMAP_FLAG); public CircleView(Context context, AttributeSet attrs, int defStyle) { super (context, attrs, defStyle); // TODO Auto-generated constructor stub } public CircleView(Context context, AttributeSet attrs) { super (context, attrs); // TODO Auto-generated constructor stub mBgPaint.setColor(Color.WHITE); mBgPaint.setAntiAlias( true ); } public CircleView(Context context) { super (context); // TODO Auto-generated constructor stub mBgPaint.setColor(Color.WHITE); mBgPaint.setAntiAlias( true ); } @Override protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) { // TODO Auto-generated method stub super .onMeasure(widthMeasureSpec, heightMeasureSpec); int measuredWidth = getMeasuredWidth(); int measuredHeight = getMeasuredHeight(); int max = Math.max(measuredWidth, measuredHeight); setMeasuredDimension(max, max); } @Override public void setBackgroundColor( int color) { // TODO Auto-generated method stub mBgPaint.setColor(color); } /** * 设置通知个数显示 * @param text */ public void setNotifiText( int text){ // if(text>99){ // String string = 99+"+"; // setText(string); // return; // } setText(text+ "" ); } @Override public void draw(Canvas canvas) { // TODO Auto-generated method stub canvas.setDrawFilter(pfd); canvas.drawCircle(getWidth()/ 2 , getHeight()/ 2 , Math.max(getWidth(), getHeight())/ 2 , mBgPaint); super .draw(canvas); } } |
自定义布局用于动态生成多个控件 核心类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
|
package com.dyl.cloudtags; import java.util.LinkedList; import java.util.Random; import java.util.Vector; import android.content.Context; import android.graphics.Color; import android.graphics.Paint; import android.graphics.drawable.GradientDrawable; import android.util.AttributeSet; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.Animation.AnimationListener; import android.view.animation.AnimationSet; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.view.animation.ScaleAnimation; import android.view.animation.TranslateAnimation; import android.widget.FrameLayout; public class KeywordsFlow extends FrameLayout implements OnGlobalLayoutListener { public static final int IDX_X = 0 ; public static final int IDX_Y = 1 ; public static final int IDX_TXT_LENGTH = 2 ; public static final int IDX_DIS_Y = 3 ; /** 由外至内的动画。 */ public static final int ANIMATION_IN = 1 ; /** 由内至外的动画。 */ public static final int ANIMATION_OUT = 2 ; /** 位移动画类型:从外围移动到坐标点。 */ public static final int OUTSIDE_TO_LOCATION = 1 ; /** 位移动画类型:从坐标点移动到外围。 */ public static final int LOCATION_TO_OUTSIDE = 2 ; /** 位移动画类型:从中心点移动到坐标点。 */ public static final int CENTER_TO_LOCATION = 3 ; /** 位移动画类型:从坐标点移动到中心点。 */ public static final int LOCATION_TO_CENTER = 4 ; public static final long ANIM_DURATION = 800l; public static final int MAX = 12 ; public static final int TEXT_SIZE_MAX = 20 ; public static final int TEXT_SIZE_MIN = 10 ; private OnClickListener itemClickListener; private static Interpolator interpolator; private static AlphaAnimation animAlpha2Opaque; private static AlphaAnimation animAlpha2Transparent; private static ScaleAnimation animScaleLarge2Normal, animScaleNormal2Large, animScaleZero2Normal, animScaleNormal2Zero; /** 存储显示的关键字。 */ private Vector<String> vecKeywords; private int width, height; /** * go2Show()中被赋值为true,标识开发人员触发其开始动画显示。<br/> * 本标识的作用是防止在填充keywrods未完成的过程中获取到width和height后提前启动动画。<br/> * 在show()方法中其被赋值为false。<br/> * 真正能够动画显示的另一必要条件:width 和 height不为0。<br/> */ private boolean enableShow; private Random random; private int txtAnimInType, txtAnimOutType; /** 最近一次启动动画显示的时间。 */ private long lastStartAnimationTime; /** 动画运行时间。 */ private long animDuration; private Context context; public KeywordsFlow(Context context) { super (context); init(); } public KeywordsFlow(Context context, AttributeSet attrs) { super (context, attrs); init(); } public KeywordsFlow(Context context, AttributeSet attrs, int defStyle) { super (context, attrs, defStyle); init(); } private void init() { lastStartAnimationTime = 0l; animDuration = ANIM_DURATION; random = new Random(); vecKeywords = new Vector<String>(MAX); getViewTreeObserver().addOnGlobalLayoutListener( this ); interpolator = AnimationUtils.loadInterpolator(getContext(), android.R.anim.decelerate_interpolator); animAlpha2Opaque = new AlphaAnimation( 0 .0f, 1 .0f); animAlpha2Transparent = new AlphaAnimation( 1 .0f, 0 .0f); animScaleLarge2Normal = new ScaleAnimation( 2 , 1 , 2 , 1 ); animScaleNormal2Large = new ScaleAnimation( 1 , 2 , 1 , 2 ); animScaleZero2Normal = new ScaleAnimation( 0 , 1 , 0 , 1 ); animScaleNormal2Zero = new ScaleAnimation( 1 , 0 , 1 , 0 ); } public long getDuration() { return animDuration; } public void setDuration( long duration) { animDuration = duration; } public boolean feedKeyword(String keyword) { boolean result = false ; if (vecKeywords.size() < MAX) { result = vecKeywords.add(keyword); } return result; } /** * 开始动画显示。<br/> * 之前已经存在的TextView将会显示退出动画。<br/> * * @return 正常显示动画返回true;反之为false。返回false原因如下:<br/> * 1.时间上不允许,受lastStartAnimationTime的制约;<br/> * 2.未获取到width和height的值。<br/> */ public boolean go2Show( int animType) { if (System.currentTimeMillis() - lastStartAnimationTime > animDuration) { enableShow = true ; if (animType == ANIMATION_IN) { txtAnimInType = OUTSIDE_TO_LOCATION; txtAnimOutType = LOCATION_TO_CENTER; } else if (animType == ANIMATION_OUT) { txtAnimInType = CENTER_TO_LOCATION; txtAnimOutType = LOCATION_TO_OUTSIDE; } disapper(); boolean result = show(); return result; } return false ; } private void disapper() { int size = getChildCount(); for ( int i = size - 1 ; i >= 0 ; i--) { final CircleView txv = (CircleView) getChildAt(i); if (txv.getVisibility() == View.GONE) { removeView(txv); continue ; } FrameLayout.LayoutParams layParams = (LayoutParams) txv .getLayoutParams(); int [] xy = new int [] { layParams.leftMargin, layParams.topMargin, txv.getWidth() }; AnimationSet animSet = getAnimationSet(xy, (width >> 1 ), (height >> 1 ), txtAnimOutType); txv.startAnimation(animSet); animSet.setAnimationListener( new AnimationListener() { public void onAnimationStart(Animation animation) { } public void onAnimationRepeat(Animation animation) { } public void onAnimationEnd(Animation animation) { txv.setOnClickListener( null ); txv.setClickable( false ); txv.setVisibility(View.GONE); } }); } } private boolean show() { if (width > 0 && height > 0 && vecKeywords != null && vecKeywords.size() > 0 && enableShow) { enableShow = false ; lastStartAnimationTime = System.currentTimeMillis(); int xCenter = width >> 1 , yCenter = height >> 1 ; int size = vecKeywords.size(); int xItem = width / size, yItem = height / size; LinkedList<Integer> listX = new LinkedList<Integer>(), listY = new LinkedList<Integer>(); for ( int i = 0 ; i < size; i++) { // 准备随机候选数,分别对应x/y轴位置 listX.add(i * xItem); listY.add(i * yItem + (yItem >> 2 )); } LinkedList<CircleView> listTxtTop = new LinkedList<CircleView>(); LinkedList<CircleView> listTxtBottom = new LinkedList<CircleView>(); for ( int i = 0 ; i < size; i++) { String keyword = vecKeywords.get(i); // 随机位置,糙值 int xy[] = randomXY(random, listX, listY, xItem); // 实例化TextView final CircleView txv = new CircleView(getContext()); txv.setBackgroundResource(R.drawable.text_view_border); txv.setGravity(Gravity.CENTER); txv.setOnClickListener(itemClickListener); txv.setText(keyword); txv.setTextColor(Color.WHITE); txv.setPadding( 8 , 6 , 8 , 6 ); txv.setSingleLine( true ); int r = random.nextInt( 256 ); int g= random.nextInt( 256 ); int b = random.nextInt( 256 ); int mColor = Color.rgb(r, g, b); GradientDrawable myGrad = (GradientDrawable)txv.getBackground(); myGrad.setColor(mColor); // txv.setBackgroundColor(mColor); // 获取文本长度 Paint paint = txv.getPaint(); int strWidth = ( int ) Math.ceil(paint.measureText(keyword)); xy[IDX_TXT_LENGTH] = strWidth; // 第一次修正:修正x坐标 if (xy[IDX_X] + strWidth > width - (xItem >> 1 )) { int baseX = width - strWidth; // 减少文本右边缘一样的概率 xy[IDX_X] = baseX - xItem + random.nextInt(xItem >> 1 ); } else if (xy[IDX_X] == 0 ) { // 减少文本左边缘一样的概率 xy[IDX_X] = Math.max(random.nextInt(xItem), xItem / 3 ); } xy[IDX_DIS_Y] = Math.abs(xy[IDX_Y] - yCenter); txv.setTag(xy); if (xy[IDX_Y] > yCenter) { listTxtBottom.add(txv); } else { listTxtTop.add(txv); } } attach2Screen(listTxtTop, xCenter, yCenter, yItem); attach2Screen(listTxtBottom, xCenter, yCenter, yItem); return true ; } return false ; } /** 修正TextView的Y坐标将将其添加到容器上。 */ private void attach2Screen(LinkedList<CircleView> listTxt, int xCenter, int yCenter, int yItem) { int size = listTxt.size(); sortXYList(listTxt, size); for ( int i = 0 ; i < size; i++) { CircleView txv = listTxt.get(i); int [] iXY = ( int []) txv.getTag(); // 第二次修正:修正y坐标 int yDistance = iXY[IDX_Y] - yCenter; // 对于最靠近中心点的,其值不会大于yItem<br/> // 对于可以一路下降到中心点的,则该值也是其应调整的大小<br/> int yMove = Math.abs(yDistance); inner: for ( int k = i - 1 ; k >= 0 ; k--) { int [] kXY = ( int []) listTxt.get(k).getTag(); int startX = kXY[IDX_X]; int endX = startX + kXY[IDX_TXT_LENGTH]; // y轴以中心点为分隔线,在同一侧 if (yDistance * (kXY[IDX_Y] - yCenter) > 0 ) { if (isXMixed(startX, endX, iXY[IDX_X], iXY[IDX_X] + iXY[IDX_TXT_LENGTH])) { int tmpMove = Math.abs(iXY[IDX_Y] - kXY[IDX_Y]); if (tmpMove > yItem) { yMove = tmpMove; } else if (yMove > 0 ) { // 取消默认值。 yMove = 0 ; } break inner; } } } if (yMove > yItem) { int maxMove = yMove - yItem; int randomMove = random.nextInt(maxMove); int realMove = Math.max(randomMove, maxMove >> 1 ) * yDistance / Math.abs(yDistance); iXY[IDX_Y] = iXY[IDX_Y] - realMove; iXY[IDX_DIS_Y] = Math.abs(iXY[IDX_Y] - yCenter); // 已经调整过前i个需要再次排序 sortXYList(listTxt, i + 1 ); } FrameLayout.LayoutParams layParams = new FrameLayout.LayoutParams( FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT); layParams.gravity = Gravity.LEFT | Gravity.TOP; layParams.leftMargin = iXY[IDX_X]; layParams.topMargin = iXY[IDX_Y]; addView(txv, layParams); // 动画 AnimationSet animSet = getAnimationSet(iXY, xCenter, yCenter, txtAnimInType); txv.startAnimation(animSet); } } public AnimationSet getAnimationSet( int [] xy, int xCenter, int yCenter, int type) { AnimationSet animSet = new AnimationSet( true ); animSet.setInterpolator(interpolator); if (type == OUTSIDE_TO_LOCATION) { animSet.addAnimation(animAlpha2Opaque); animSet.addAnimation(animScaleLarge2Normal); TranslateAnimation translate = new TranslateAnimation((xy[IDX_X] + (xy[IDX_TXT_LENGTH] >> 1 ) - xCenter) << 1 , 0 , (xy[IDX_Y] - yCenter) << 1 , 0 ); animSet.addAnimation(translate); } else if (type == LOCATION_TO_OUTSIDE) { animSet.addAnimation(animAlpha2Transparent); animSet.addAnimation(animScaleNormal2Large); TranslateAnimation translate = new TranslateAnimation( 0 , (xy[IDX_X] + (xy[IDX_TXT_LENGTH] >> 1 ) - xCenter) << 1 , 0 , (xy[IDX_Y] - yCenter) << 1 ); animSet.addAnimation(translate); } else if (type == LOCATION_TO_CENTER) { animSet.addAnimation(animAlpha2Transparent); animSet.addAnimation(animScaleNormal2Zero); TranslateAnimation translate = new TranslateAnimation( 0 , (-xy[IDX_X] + xCenter), 0 , (-xy[IDX_Y] + yCenter)); animSet.addAnimation(translate); } else if (type == CENTER_TO_LOCATION) { animSet.addAnimation(animAlpha2Opaque); animSet.addAnimation(animScaleZero2Normal); TranslateAnimation translate = new TranslateAnimation( (-xy[IDX_X] + xCenter), 0 , (-xy[IDX_Y] + yCenter), 0 ); animSet.addAnimation(translate); } animSet.setDuration(animDuration); return animSet; } /** * 根据与中心点的距离由近到远进行冒泡排序。 * * @param endIdx * 起始位置。 * @param txtArr * 待排序的数组。 * */ private void sortXYList(LinkedList<CircleView> listTxt, int endIdx) { for ( int i = 0 ; i < endIdx; i++) { for ( int k = i + 1 ; k < endIdx; k++) { if ((( int []) listTxt.get(k).getTag())[IDX_DIS_Y] < (( int []) listTxt .get(i).getTag())[IDX_DIS_Y]) { CircleView iTmp = listTxt.get(i); CircleView kTmp = listTxt.get(k); listTxt.set(i, kTmp); listTxt.set(k, iTmp); } } } } /** A线段与B线段所代表的直线在X轴映射上是否有交集。 */ private boolean isXMixed( int startA, int endA, int startB, int endB) { boolean result = false ; if (startB >= startA && startB <= endA) { result = true ; } else if (endB >= startA && endB <= endA) { result = true ; } else if (startA >= startB && startA <= endB) { result = true ; } else if (endA >= startB && endA <= endB) { result = true ; } return result; } //得到随机坐标 private int [] randomXY(Random ran, LinkedList<Integer> listX, LinkedList<Integer> listY, int xItem) { int [] arr = new int [ 4 ]; arr[IDX_X] = listX.remove(ran.nextInt(listX.size())); arr[IDX_Y] = listY.remove(ran.nextInt(listY.size())); return arr; } public void onGlobalLayout() { int tmpW = getWidth(); int tmpH = getHeight(); if (width != tmpW || height != tmpH) { width = tmpW; height = tmpH; show(); } } public Vector<String> getKeywords() { return vecKeywords; } public void rubKeywords() { vecKeywords.clear(); } /** 直接清除所有的TextView。在清除之前不会显示动画。 */ public void rubAllViews() { removeAllViews(); } public void setOnItemClickListener(OnClickListener listener) { itemClickListener = listener; } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
package com.dyl.cloudtags; /** * 搜索记录 * @author dengyalan * */ public class SearchDataPojo { private String content = "" ; public String getContent() { return content; } public SearchDataPojo setContent(String content) { this .content = content; return this ; } } |
源码下载:Android实现圆形云标签效果
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:https://blog.csdn.net/shaoyezhangliwei/article/details/49281619