├── .classpath
├── .gitignore
├── .project
├── AndroidManifest.xml
├── README.md
├── proguard.cfg
├── project.properties
├── res
├── drawable-hdpi
│ ├── icon.png
│ └── state_bg_1.9.png
├── drawable-ldpi
│ └── icon.png
├── drawable-mdpi
│ └── icon.png
├── layout
│ └── main.xml
└── values
│ └── strings.xml
├── sample
├── sample.png
└── sampleV2.png
└── src
└── com
└── socogame
└── cloudtag
├── CloudTagActivity.java
└── CloudTagManager.java
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # built application files
2 | *.apk
3 | *.ap_
4 |
5 | # files for the dex VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # generated files
12 | bin/
13 | gen/
14 |
15 | # Local configuration file (sdk path, etc)
16 | local.properties
17 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | CloudTag
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | AndroidCloudTag
2 | ===============
3 |
4 | Android的标签云(或者叫搜索云图?)
5 |
6 | 基于Android Animation及FrameLayout。感谢Sodino。
7 |
8 | ## 效果图
9 |
10 | 
11 |
12 | ## 版本信息
13 |
14 | * V0.2
15 |
16 | * 修正快速点击残影的问题
17 | * 增加飞入和飞出两个效果
18 |
19 | * V0.1
20 |
21 | * 创建版本
22 |
23 |
24 |
--------------------------------------------------------------------------------
/proguard.cfg:
--------------------------------------------------------------------------------
1 | -optimizationpasses 5
2 | -dontusemixedcaseclassnames
3 | -dontskipnonpubliclibraryclasses
4 | -dontpreverify
5 | -verbose
6 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
7 |
8 | -keep public class * extends android.app.Activity
9 | -keep public class * extends android.app.Application
10 | -keep public class * extends android.app.Service
11 | -keep public class * extends android.content.BroadcastReceiver
12 | -keep public class * extends android.content.ContentProvider
13 | -keep public class * extends android.app.backup.BackupAgentHelper
14 | -keep public class * extends android.preference.Preference
15 | -keep public class com.android.vending.licensing.ILicensingService
16 |
17 | -keepclasseswithmembernames class * {
18 | native ;
19 | }
20 |
21 | -keepclasseswithmembernames class * {
22 | public (android.content.Context, android.util.AttributeSet);
23 | }
24 |
25 | -keepclasseswithmembernames class * {
26 | public (android.content.Context, android.util.AttributeSet, int);
27 | }
28 |
29 | -keepclassmembers enum * {
30 | public static **[] values();
31 | public static ** valueOf(java.lang.String);
32 | }
33 |
34 | -keep class * implements android.os.Parcelable {
35 | public static final android.os.Parcelable$Creator *;
36 | }
37 |
--------------------------------------------------------------------------------
/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-8
15 |
--------------------------------------------------------------------------------
/res/drawable-hdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samma835/AndroidCloudTag/ac2f15980d269afa9830056933b5bcba9f2a951a/res/drawable-hdpi/icon.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/state_bg_1.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samma835/AndroidCloudTag/ac2f15980d269afa9830056933b5bcba9f2a951a/res/drawable-hdpi/state_bg_1.9.png
--------------------------------------------------------------------------------
/res/drawable-ldpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samma835/AndroidCloudTag/ac2f15980d269afa9830056933b5bcba9f2a951a/res/drawable-ldpi/icon.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samma835/AndroidCloudTag/ac2f15980d269afa9830056933b5bcba9f2a951a/res/drawable-mdpi/icon.png
--------------------------------------------------------------------------------
/res/layout/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
13 |
17 |
18 |
25 |
26 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | CloudTag
4 |
5 |
--------------------------------------------------------------------------------
/sample/sample.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samma835/AndroidCloudTag/ac2f15980d269afa9830056933b5bcba9f2a951a/sample/sample.png
--------------------------------------------------------------------------------
/sample/sampleV2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samma835/AndroidCloudTag/ac2f15980d269afa9830056933b5bcba9f2a951a/sample/sampleV2.png
--------------------------------------------------------------------------------
/src/com/socogame/cloudtag/CloudTagActivity.java:
--------------------------------------------------------------------------------
1 | package com.socogame.cloudtag;
2 |
3 | import java.util.Random;
4 |
5 | import android.app.Activity;
6 | import android.os.Bundle;
7 | import android.util.Log;
8 | import android.view.GestureDetector;
9 | import android.view.GestureDetector.SimpleOnGestureListener;
10 | import android.view.MotionEvent;
11 | import android.view.View;
12 | import android.view.View.OnClickListener;
13 | import android.widget.Button;
14 | import android.widget.TextView;
15 | import android.widget.Toast;
16 |
17 | public class CloudTagActivity extends Activity implements OnClickListener {
18 | public static final String[] keywords = { "下雨啦", "墨迹天气", "豆瓣", "Diablo3",
19 | "魔兽争霸", "Dota", "音乐", "崔健", "九月", "十二", "五月天", "夏天的故事", "我是一只大袋鼠",
20 | "星巴克", "乐知天命" };
21 | private CloudTagManager keywordsFlow;
22 | private Button btnIn, btnOut;
23 | private GestureDetector gestureDetector;
24 |
25 | public void onCreate(Bundle savedInstanceState) {
26 | super.onCreate(savedInstanceState);
27 | setContentView(R.layout.main);
28 | btnIn = (Button) findViewById(R.id.btnIn);
29 | btnOut = (Button) findViewById(R.id.btnOut);
30 | btnIn.setOnClickListener(this);
31 | btnOut.setOnClickListener(this);
32 | keywordsFlow = (CloudTagManager) findViewById(R.id.keywordsFlow);
33 | keywordsFlow.setDuration(800l);
34 | keywordsFlow.setOnItemClickListener(this);
35 | // 添加
36 | feedKeywordsFlow(keywordsFlow, keywords);
37 | keywordsFlow.go2Show(CloudTagManager.ANIMATION_IN);
38 |
39 | gestureDetector = new GestureDetector(new DefaultGestureDetector());
40 | }
41 |
42 | private static void feedKeywordsFlow(CloudTagManager keywordsFlow,
43 | String[] arr) {
44 | for (int i = 0; i < arr.length; i++) {
45 | String tmp = arr[i];
46 | keywordsFlow.feedKeyword(tmp);
47 | }
48 | }
49 |
50 | @Override
51 | public void onClick(View v) {
52 | if (v == btnIn) {
53 | flyIn();
54 | } else if (v == btnOut) {
55 | flyOut();
56 | } else if (v instanceof TextView) {
57 | String keyword = ((TextView) v).getText().toString();
58 | Toast.makeText(this, keyword, 0).show();
59 | }
60 | }
61 |
62 | @Override
63 | public boolean onTouchEvent(MotionEvent event) {
64 | return gestureDetector.onTouchEvent(event);
65 | }
66 |
67 | private class DefaultGestureDetector extends SimpleOnGestureListener {
68 | @Override
69 | public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
70 | float velocityY) {
71 | final int FLING_MIN_DISTANCE = 100;//X或者y轴上移动的距离(像素)
72 | final int FLING_MIN_VELOCITY = 100;//x或者y轴上的移动速度(像素/秒)
73 | Random random = new Random();
74 |
75 | if (random.nextInt(2) == 0) {
76 | flyIn();
77 | } else {
78 | flyOut();
79 | }
80 | // if ((e1.getX() - e2.getX()) > FLING_MIN_DISTANCE
81 | // && Math.abs(velocityX) > FLING_MIN_VELOCITY) {
82 | // } else if ((e2.getX() - e1.getX()) > FLING_MIN_DISTANCE
83 | // && Math.abs(velocityX) > FLING_MIN_VELOCITY) {
84 | // } else if ((e1.getY() - e2.getY()) > FLING_MIN_DISTANCE
85 | // && Math.abs(velocityY) > FLING_MIN_VELOCITY) {
86 | // } else if ((e2.getY() - e1.getY()) > FLING_MIN_DISTANCE
87 | // && Math.abs(velocityY) > FLING_MIN_VELOCITY) {
88 | // } else {
89 | // }
90 | return false;
91 | }
92 | }
93 |
94 | private void flyIn() {
95 | keywordsFlow.rubKeywords();
96 | // keywordsFlow.rubAllViews();
97 | feedKeywordsFlow(keywordsFlow, keywords);
98 | keywordsFlow.go2Show(CloudTagManager.ANIMATION_IN);
99 | }
100 |
101 | private void flyOut() {
102 | keywordsFlow.rubKeywords();
103 | // keywordsFlow.rubAllViews();
104 | feedKeywordsFlow(keywordsFlow, keywords);
105 | keywordsFlow.go2Show(CloudTagManager.ANIMATION_OUT);
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/com/socogame/cloudtag/CloudTagManager.java:
--------------------------------------------------------------------------------
1 | package com.socogame.cloudtag;
2 |
3 | import java.util.LinkedList;
4 | import java.util.Random;
5 | import java.util.Vector;
6 |
7 | import android.content.Context;
8 | import android.graphics.Color;
9 | import android.graphics.Paint;
10 | import android.text.TextUtils.TruncateAt;
11 | import android.util.AttributeSet;
12 | import android.view.Gravity;
13 | import android.view.View;
14 | import android.view.ViewTreeObserver.OnGlobalLayoutListener;
15 | import android.view.animation.AlphaAnimation;
16 | import android.view.animation.Animation;
17 | import android.view.animation.Animation.AnimationListener;
18 | import android.view.animation.AnimationSet;
19 | import android.view.animation.AnimationUtils;
20 | import android.view.animation.Interpolator;
21 | import android.view.animation.ScaleAnimation;
22 | import android.view.animation.TranslateAnimation;
23 | import android.widget.FrameLayout;
24 | import android.widget.TextView;
25 |
26 | /**
27 | * 注意,出包时出混淆包,应在proguard.cfg中加入:
28 | * -keep public class * extends Android.widget.FrameLayout
29 | */
30 | public class CloudTagManager extends FrameLayout implements
31 | OnGlobalLayoutListener {
32 | public static final int IDX_X = 0;
33 | public static final int IDX_Y = 1;
34 | public static final int IDX_TXT_LENGTH = 2;
35 | public static final int IDX_DIS_Y = 3;
36 | /** 由外至内的动画。 */
37 | public static final int ANIMATION_IN = 1;
38 | /** 由内至外的动画。 */
39 | public static final int ANIMATION_OUT = 2;
40 | /** 位移动画类型:从外围移动到坐标点。 */
41 | public static final int OUTSIDE_TO_LOCATION = 1;
42 | /** 位移动画类型:从坐标点移动到外围。 */
43 | public static final int LOCATION_TO_OUTSIDE = 2;
44 | /** 位移动画类型:从中心点移动到坐标点。 */
45 | public static final int CENTER_TO_LOCATION = 3;
46 | /** 位移动画类型:从坐标点移动到中心点。 */
47 | public static final int LOCATION_TO_CENTER = 4;
48 | public static final long ANIM_DURATION = 800l;
49 | public static final int MAX = 36;
50 | public static final int TEXT_SIZE_MAX = 25;
51 | public static final int TEXT_SIZE_MIN = 15;
52 | private OnClickListener itemClickListener;
53 | private static Interpolator interpolator;
54 | private static AlphaAnimation animAlpha2Opaque;
55 | private static AlphaAnimation animAlpha2Transparent;
56 | private static ScaleAnimation animScaleLarge2Normal, animScaleNormal2Large,
57 | animScaleZero2Normal, animScaleNormal2Zero;
58 | private int[] cr = { Color.BLUE, Color.CYAN, Color.DKGRAY, Color.GREEN,
59 | Color.LTGRAY, Color.BLACK, Color.MAGENTA, Color.RED, Color.YELLOW };
60 | private int[] textSize = { 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 };
61 | /** 存储显示的关键字。 */
62 | private Vector vecKeywords;
63 | private int width, height;
64 | /**
65 | * go2Show()中被赋值为true,标识开发人员触发其开始动画显示。
66 | * 本标识的作用是防止在填充keywrods未完成的过程中获取到width和height后提前启动动画。
67 | * 在show()方法中其被赋值为false。
68 | * 真正能够动画显示的另一必要条件:width 和 height不为0。
69 | */
70 | private boolean enableShow;
71 | private Random random;
72 | /**
73 | * @see ANIMATION_IN
74 | * @see ANIMATION_OUT
75 | * @see OUTSIDE_TO_LOCATION
76 | * @see LOCATION_TO_OUTSIDE
77 | * @see LOCATION_TO_CENTER
78 | * @see CENTER_TO_LOCATION
79 | * */
80 | private int txtAnimInType, txtAnimOutType;
81 | /** 最近一次启动动画显示的时间。 */
82 | private long lastStartAnimationTime;
83 | /** 动画运行时间。 */
84 | private long animDuration;
85 |
86 | public CloudTagManager(Context context, AttributeSet attrs, int defStyle) {
87 | super(context, attrs, defStyle);
88 | init();
89 | }
90 |
91 | public CloudTagManager(Context context, AttributeSet attrs) {
92 | super(context, attrs);
93 | init();
94 | }
95 |
96 | public CloudTagManager(Context context) {
97 | super(context);
98 | init();
99 | }
100 |
101 | private void init() {
102 | lastStartAnimationTime = 0l;
103 | animDuration = ANIM_DURATION;
104 | random = new Random();
105 | vecKeywords = new Vector(MAX);
106 | getViewTreeObserver().addOnGlobalLayoutListener(this);
107 | interpolator = AnimationUtils.loadInterpolator(getContext(),
108 | android.R.anim.decelerate_interpolator);
109 | animAlpha2Opaque = new AlphaAnimation(0.0f, 1.0f);
110 | animAlpha2Transparent = new AlphaAnimation(1.0f, 0.0f);
111 | animScaleLarge2Normal = new ScaleAnimation(2, 1, 2, 1);
112 | animScaleNormal2Large = new ScaleAnimation(1, 2, 1, 2);
113 | animScaleZero2Normal = new ScaleAnimation(0, 1, 0, 1);
114 | animScaleNormal2Zero = new ScaleAnimation(1, 0, 1, 0);
115 | }
116 |
117 | public long getDuration() {
118 | return animDuration;
119 | }
120 |
121 | public void setDuration(long duration) {
122 | animDuration = duration;
123 | }
124 |
125 | public boolean feedKeyword(String keyword) {
126 | boolean result = false;
127 | if (vecKeywords.size() < MAX) {
128 | result = vecKeywords.add(keyword);
129 | }
130 | return result;
131 | }
132 |
133 | /**
134 | * 开始动画显示。
135 | * 之前已经存在的TextView将会显示退出动画。
136 | *
137 | * @return 正常显示动画返回true;反之为false。返回false原因如下:
138 | * 1.时间上不允许,受lastStartAnimationTime的制约;
139 | * 2.未获取到width和height的值。
140 | */
141 | public boolean go2Show(int animType) {
142 | if (System.currentTimeMillis() - lastStartAnimationTime > animDuration) {
143 | enableShow = true;
144 | if (animType == ANIMATION_IN) {
145 | txtAnimInType = OUTSIDE_TO_LOCATION;
146 | txtAnimOutType = LOCATION_TO_CENTER;
147 | } else if (animType == ANIMATION_OUT) {
148 | txtAnimInType = CENTER_TO_LOCATION;
149 | txtAnimOutType = LOCATION_TO_OUTSIDE;
150 | }
151 | disapper();
152 | boolean result = show();
153 | return result;
154 | }
155 | return false;
156 | }
157 |
158 | private void disapper() {
159 | int size = getChildCount();
160 | for (int i = size - 1; i >= 0; i--) {
161 | final TextView txt = (TextView) getChildAt(i);
162 | if (txt.getVisibility() == View.GONE) {
163 | removeView(txt);
164 | continue;
165 | }
166 | FrameLayout.LayoutParams layParams = (LayoutParams) txt
167 | .getLayoutParams();
168 | // Log.d("Android_LAB", txt.getText() + " leftM=" +
169 | // layParams.leftMargin + " topM=" + layParams.topMargin
170 | // + " width=" + txt.getWidth());
171 | int[] xy = new int[] { layParams.leftMargin, layParams.topMargin,
172 | txt.getWidth() };
173 | AnimationSet animSet = getAnimationSet(xy, (width >> 1),
174 | (height >> 1), txtAnimOutType);
175 | txt.startAnimation(animSet);
176 | animSet.setAnimationListener(new AnimationListener() {
177 | public void onAnimationStart(Animation animation) {
178 | }
179 |
180 | public void onAnimationRepeat(Animation animation) {
181 | }
182 |
183 | public void onAnimationEnd(Animation animation) {
184 | txt.setOnClickListener(null);
185 | txt.setClickable(false);
186 | txt.setVisibility(View.GONE);
187 | }
188 | });
189 | }
190 | }
191 |
192 | private boolean show() {
193 | if (width > 0 && height > 0 && vecKeywords != null
194 | && vecKeywords.size() > 0 && enableShow) {
195 | enableShow = false;
196 | lastStartAnimationTime = System.currentTimeMillis();
197 | int xCenter = width >> 1, yCenter = height >> 1;
198 | int size = vecKeywords.size();
199 | int xItem = width / size, yItem = height / size;
200 | LinkedList listX = new LinkedList(), listY = new LinkedList();
201 | for (int i = 0; i < size; i++) {
202 | // 准备随机候选数,分别对应x/y轴位置
203 | listX.add(i * xItem); //水平上不重叠
204 | listY.add(i * yItem + (yItem >> 2)); //垂直方向允许重叠1/4高度
205 | }
206 | LinkedList listTxtTop = new LinkedList();
207 | LinkedList listTxtBottom = new LinkedList();
208 | for (int i = 0; i < size; i++) {
209 | String keyword = vecKeywords.get(i);
210 | // 随机颜色
211 | int ranColor = 0xff000000 | random.nextInt(0x0077ffff);
212 | // 随机位置,糙值
213 | int xy[] = randomXY(random, listX, listY, xItem);
214 | // 随机字体大小
215 | int txtSize = TEXT_SIZE_MIN
216 | + random.nextInt(TEXT_SIZE_MAX - TEXT_SIZE_MIN + 1);
217 | // 实例化TextView
218 | final TextView txt = new TextView(getContext());
219 | txt.setLayoutParams(new LayoutParams(480, 30));
220 | txt.setOnClickListener(itemClickListener);
221 | txt.setText(keyword);
222 | // txt.setTextColor(ranColor);
223 | // txt.setTextSize(TypedValue.COMPLEX_UNIT_SP, txtSize);
224 | // txt.setShadowLayer(2, 2, 2, 0xff696969);
225 | txt.setTextColor(cr[random.nextInt(cr.length)]);
226 | txt.setTextSize(textSize[random.nextInt(textSize.length)]);
227 | txt.setGravity(Gravity.CENTER);
228 | txt.setEllipsize(TruncateAt.MIDDLE);
229 | txt.setSingleLine(true);
230 | // txt.setEms(10);
231 |
232 | // 获取文本长度
233 | Paint paint = txt.getPaint();
234 | int strWidth = (int) Math.ceil(paint.measureText(keyword));
235 | System.out.println("文本内容 : " + keyword + ",文本长度 :" + strWidth);
236 | xy[IDX_TXT_LENGTH] = strWidth;
237 | // 第一次修正:修正x坐标
238 | if (xy[IDX_X] + strWidth > width - (xItem >> 1)) {
239 | int baseX = width - strWidth;
240 | // 减少文本右边缘一样的概率
241 | xy[IDX_X] = baseX - xItem + random.nextInt(xItem >> 1);
242 | } else if (xy[IDX_X] == 0) {
243 | // 减少文本左边缘一样的概率
244 | xy[IDX_X] = Math.max(random.nextInt(xItem), xItem / 3);
245 | }
246 | xy[IDX_DIS_Y] = Math.abs(xy[IDX_Y] - yCenter);
247 | txt.setTag(xy);
248 | if (xy[IDX_Y] > yCenter) {
249 | listTxtBottom.add(txt);
250 | } else {
251 | listTxtTop.add(txt);
252 | }
253 | }
254 | attach2Screen(listTxtTop, xCenter, yCenter, yItem);
255 | attach2Screen(listTxtBottom, xCenter, yCenter, yItem);
256 | return true;
257 | }
258 | return false;
259 | }
260 |
261 | /** 修正TextView的Y坐标将将其添加到容器上。 */
262 | private void attach2Screen(LinkedList listTxt, int xCenter,
263 | int yCenter, int yItem) {
264 | int size = listTxt.size();
265 | sortXYList(listTxt, size);
266 | for (int i = 0; i < size; i++) {
267 | TextView txt = listTxt.get(i);
268 | int[] iXY = (int[]) txt.getTag();
269 | // 第二次修正:修正y坐标
270 | int yDistance = iXY[IDX_Y] - yCenter;
271 | // 对于最靠近中心点的,其值不会大于yItem
272 | // 对于可以一路下降到中心点的,则该值也是其应调整的大小
273 | int yMove = Math.abs(yDistance);
274 | inner: for (int k = i - 1; k >= 0; k--) {
275 | int[] kXY = (int[]) listTxt.get(k).getTag();
276 | int startX = kXY[IDX_X];
277 | int endX = startX + kXY[IDX_TXT_LENGTH];
278 | // y轴以中心点为分隔线,在同一侧
279 | if (yDistance * (kXY[IDX_Y] - yCenter) > 0) {
280 | // Log.d("Android_LAB", "compare:" +
281 | // listTxt.get(k).getText());
282 | if (isXMixed(startX, endX, iXY[IDX_X], iXY[IDX_X]
283 | + iXY[IDX_TXT_LENGTH])) {
284 | int tmpMove = Math.abs(iXY[IDX_Y] - kXY[IDX_Y]);
285 | if (tmpMove > yItem) {
286 | yMove = tmpMove;
287 | } else if (yMove > 0) {
288 | // 取消默认值。
289 | yMove = 0;
290 | }
291 | // Log.d("Android_LAB", "break");
292 | break inner;
293 | }
294 | }
295 | }
296 | // Log.d("Android_LAB", txt.getText() + " yMove=" + yMove);
297 | if (yMove > yItem) {
298 | int maxMove = yMove - yItem;
299 | int randomMove = random.nextInt(maxMove);
300 | int realMove = Math.max(randomMove, maxMove >> 1) * yDistance
301 | / Math.abs(yDistance);
302 | iXY[IDX_Y] = iXY[IDX_Y] - realMove;
303 | iXY[IDX_DIS_Y] = Math.abs(iXY[IDX_Y] - yCenter);
304 | // 已经调整过前i个需要再次排序
305 | sortXYList(listTxt, i + 1);
306 | }
307 | FrameLayout.LayoutParams layParams = new FrameLayout.LayoutParams(
308 | FrameLayout.LayoutParams.WRAP_CONTENT,
309 | FrameLayout.LayoutParams.WRAP_CONTENT);
310 | layParams.gravity = Gravity.LEFT | Gravity.TOP;
311 | layParams.leftMargin = iXY[IDX_X];
312 | layParams.topMargin = iXY[IDX_Y];
313 | addView(txt, layParams);
314 | // 动画
315 | AnimationSet animSet = getAnimationSet(iXY, xCenter, yCenter,
316 | txtAnimInType);
317 | txt.startAnimation(animSet);
318 | }
319 | }
320 |
321 | public AnimationSet getAnimationSet(int[] xy, int xCenter, int yCenter,
322 | int type) {
323 | AnimationSet animSet = new AnimationSet(true);
324 | animSet.setInterpolator(interpolator);
325 | if (type == OUTSIDE_TO_LOCATION) {
326 | animSet.addAnimation(animAlpha2Opaque);
327 | animSet.addAnimation(animScaleLarge2Normal);
328 | TranslateAnimation translate = new TranslateAnimation((xy[IDX_X]
329 | + (xy[IDX_TXT_LENGTH] >> 1) - xCenter) << 1, 0,
330 | (xy[IDX_Y] - yCenter) << 1, 0);
331 | animSet.addAnimation(translate);
332 | } else if (type == LOCATION_TO_OUTSIDE) {
333 | animSet.addAnimation(animAlpha2Transparent);
334 | animSet.addAnimation(animScaleNormal2Large);
335 | TranslateAnimation translate = new TranslateAnimation(0, (xy[IDX_X]
336 | + (xy[IDX_TXT_LENGTH] >> 1) - xCenter) << 1, 0,
337 | (xy[IDX_Y] - yCenter) << 1);
338 | animSet.addAnimation(translate);
339 | } else if (type == LOCATION_TO_CENTER) {
340 | animSet.addAnimation(animAlpha2Transparent);
341 | animSet.addAnimation(animScaleNormal2Zero);
342 | TranslateAnimation translate = new TranslateAnimation(0,
343 | (-xy[IDX_X] + xCenter), 0, (-xy[IDX_Y] + yCenter));
344 | animSet.addAnimation(translate);
345 | } else if (type == CENTER_TO_LOCATION) {
346 | animSet.addAnimation(animAlpha2Opaque);
347 | animSet.addAnimation(animScaleZero2Normal);
348 | TranslateAnimation translate = new TranslateAnimation(
349 | (-xy[IDX_X] + xCenter), 0, (-xy[IDX_Y] + yCenter), 0);
350 | animSet.addAnimation(translate);
351 | }
352 | animSet.setDuration(animDuration);
353 | return animSet;
354 | }
355 |
356 | /**
357 | * 根据与中心点的距离由近到远进行冒泡排序。
358 | *
359 | * @param endIdx
360 | * 起始位置。
361 | * @param txtArr
362 | * 待排序的数组。
363 | *
364 | */
365 | private void sortXYList(LinkedList listTxt, int endIdx) {
366 | for (int i = 0; i < endIdx; i++) {
367 | for (int k = i + 1; k < endIdx; k++) {
368 | if (((int[]) listTxt.get(k).getTag())[IDX_DIS_Y] < ((int[]) listTxt
369 | .get(i).getTag())[IDX_DIS_Y]) {
370 | TextView iTmp = listTxt.get(i);
371 | TextView kTmp = listTxt.get(k);
372 | listTxt.set(i, kTmp);
373 | listTxt.set(k, iTmp);
374 | }
375 | }
376 | }
377 | }
378 |
379 | /** A线段与B线段所代表的直线在X轴映射上是否有交集。 */
380 | private boolean isXMixed(int startA, int endA, int startB, int endB) {
381 | boolean result = false;
382 | if (startB >= startA && startB <= endA) {
383 | result = true;
384 | } else if (endB >= startA && endB <= endA) {
385 | result = true;
386 | } else if (startA >= startB && startA <= endB) {
387 | result = true;
388 | } else if (endA >= startB && endA <= endB) {
389 | result = true;
390 | }
391 | return result;
392 | }
393 |
394 | private int[] randomXY(Random ran, LinkedList listX,
395 | LinkedList listY, int xItem) {
396 | int[] arr = new int[4];
397 | arr[IDX_X] = listX.remove(ran.nextInt(listX.size()));
398 | arr[IDX_Y] = listY.remove(ran.nextInt(listY.size()));
399 | return arr;
400 | }
401 |
402 | public void onGlobalLayout() {
403 | int tmpW = getWidth();
404 | int tmpH = getHeight();
405 | if (width != tmpW || height != tmpH) {
406 | width = tmpW;
407 | height = tmpH;
408 | show();
409 | }
410 | }
411 |
412 | public Vector getKeywords() {
413 | return vecKeywords;
414 | }
415 |
416 | public void rubKeywords() {
417 | vecKeywords.clear();
418 | }
419 |
420 | /** 直接清除所有的TextView。在清除之前不会显示动画。 */
421 | public void rubAllViews() {
422 | removeAllViews();
423 | }
424 |
425 | public void setOnItemClickListener(OnClickListener listener) {
426 | itemClickListener = listener;
427 | }
428 | }
--------------------------------------------------------------------------------