├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── KeywordsFlowDemo.iml ├── KeywordsFlowView.iml ├── README.md ├── app ├── .gitignore ├── app.iml ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── wenhuaijun │ │ └── keywordsflowdemo │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── wenhuaijun │ │ │ └── keywordsflowdemo │ │ │ ├── KeywordsFlow.java │ │ │ ├── KeywordsFlowView.java │ │ │ └── MainActivity.java │ └── res │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── wenhuaijun │ └── keywordsflowdemo │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── introduce ├── S60225-134045.jpg ├── S60225-135726.jpg └── S60225-135738.jpg └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | /.idea/libraries 5 | .DS_Store 6 | /build 7 | /captures 8 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | KeywordsFlowView -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 1.7 31 | 32 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /KeywordsFlowDemo.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /KeywordsFlowView.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KeywordsFlowView 2 | ###随机布满屏幕的文字,飞入飞出动画效果的控件。 3 | 4 | 文字颜色随机,数量可设置,支持滑动屏幕切换文字 5 | ##示例 6 | ![1](https://github.com/wenhuaijun/KeywordsFlowView/blob/master/introduce/S60225-134045.jpg " ") 7 | ![2](https://github.com/wenhuaijun/KeywordsFlowView/blob/master/introduce/S60225-135726.jpg " ") 8 | ![3](https://github.com/wenhuaijun/KeywordsFlowView/blob/master/introduce/S60225-135738.jpg " ") 9 | 10 | ##KeywordsFlowView的布局 11 | 12 | 17 | ##KeywordsFlowView的java代码 18 | 19 | public static final String[] keywords = { "Apple", "Android", "呵呵", 20 | "高富帅","女神","拥抱","旅行","爱情","屌丝","搞笑","暴走漫画","重邮","信科", 21 | "唯美","汪星人","秋天","雨天","科幻","黑夜", 22 | "孤独","星空","东京食尸鬼","金正恩","张全蛋","东京热","陈希妍", 23 | "明星","NBA","马云","码农","动漫","时尚","熊孩子","地理","伤感", 24 | "二次元" 25 | }; 26 | keywordsFlowView = (KeywordsFlowView)findViewById(R.id.keywordsFlowView); 27 | //设置每次随机飞入文字的个数 28 | keywordsFlowView.setTextShowSize(15); 29 | //设置是否允许滑动屏幕切换文字 30 | keywordsFlowView.shouldScroolFlow(true); 31 | //开始展示 32 | keywordsFlowView.show(keywords, KeywordsFlow.ANIMATION_IN);//文字随机飞入 33 | keywordsFlowView.show(keywords,KeywordsFlow.ANIMATION_OUT);//文字随机飞出 34 | //监听文字的点击事件 35 | keywordsFlowView.setOnItemClickListener(this);//点击文字会将TextView返回到onClick(View view)回调中 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 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 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.2" 6 | 7 | defaultConfig { 8 | applicationId "com.wenhuaijun.keywordsflowdemo" 9 | minSdkVersion 15 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:23.1.1' 26 | } 27 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in F:\Users\Administrator\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/wenhuaijun/keywordsflowdemo/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.wenhuaijun.keywordsflowdemo; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/wenhuaijun/keywordsflowdemo/KeywordsFlow.java: -------------------------------------------------------------------------------- 1 | package com.wenhuaijun.keywordsflowdemo; 2 | 3 | import java.util.LinkedList; 4 | import java.util.Random; 5 | import java.util.Vector; 6 | import android.content.Context; 7 | import android.graphics.Paint; 8 | import android.util.AttributeSet; 9 | import android.util.Log; 10 | import android.util.TypedValue; 11 | import android.view.Gravity; 12 | import android.view.View; 13 | import android.view.ViewTreeObserver.OnGlobalLayoutListener; 14 | import android.view.animation.AlphaAnimation; 15 | import android.view.animation.Animation; 16 | import android.view.animation.Animation.AnimationListener; 17 | import android.view.animation.AnimationSet; 18 | import android.view.animation.AnimationUtils; 19 | import android.view.animation.Interpolator; 20 | import android.view.animation.ScaleAnimation; 21 | import android.view.animation.TranslateAnimation; 22 | import android.widget.FrameLayout; 23 | import android.widget.TextView; 24 | 25 | public class KeywordsFlow extends FrameLayout implements OnGlobalLayoutListener{ 26 | public static final int IDX_X = 0; 27 | public static final int IDX_Y = 1; 28 | public static final int IDX_TXT_LENGTH = 2; 29 | public static final int IDX_DIS_Y = 3; 30 | /** 由外至内的动画。 */ 31 | public static final int ANIMATION_IN = 1; 32 | /** 由内至外的动画。 */ 33 | public static final int ANIMATION_OUT = 2; 34 | /** 位移动画类型:从外围移动到坐标点。 */ 35 | public static final int OUTSIDE_TO_LOCATION = 1; 36 | /** 位移动画类型:从坐标点移动到外围。 */ 37 | public static final int LOCATION_TO_OUTSIDE = 2; 38 | /** 位移动画类型:从中心点移动到坐标点。 */ 39 | public static final int CENTER_TO_LOCATION = 3; 40 | /** 位移动画类型:从坐标点移动到中心点。 */ 41 | public static final int LOCATION_TO_CENTER = 4; 42 | public static final long ANIM_DURATION = 800l; 43 | public static int MAX = 10; 44 | public static final int TEXT_SIZE_MAX = 25; 45 | public static final int TEXT_SIZE_MIN = 15; 46 | private OnClickListener itemClickListener; 47 | private static Interpolator interpolator; 48 | private static AlphaAnimation animAlpha2Opaque; 49 | private static AlphaAnimation animAlpha2Transparent; 50 | private static ScaleAnimation animScaleLarge2Normal, animScaleNormal2Large, animScaleZero2Normal, 51 | animScaleNormal2Zero; 52 | /** 存储显示的关键字。 */ 53 | private Vector vecKeywords; 54 | private int width, height; 55 | /** 56 | * go2Show()中被赋值为true,标识开发人员触发其开始动画显示。
57 | * 本标识的作用是防止在填充keywrods未完成的过程中获取到width和height后提前启动动画。
58 | * 在show()方法中其被赋值为false。
59 | * 真正能够动画显示的另一必要条件:width 和 height不为0。
60 | */ 61 | private boolean enableShow; 62 | private Random random; 63 | /** 64 | * @see //ANIMATION_IN 65 | * @see //ANIMATION_OUT 66 | * @see //OUTSIDE_TO_LOCATION 67 | * @see //LOCATION_TO_OUTSIDE 68 | * @see //LOCATION_TO_CENTER 69 | * @see //CENTER_TO_LOCATION 70 | * */ 71 | private int txtAnimInType, txtAnimOutType; 72 | /** 最近一次启动动画显示的时间。 */ 73 | private long lastStartAnimationTime; 74 | /** 动画运行时间。 */ 75 | private long animDuration; 76 | 77 | public KeywordsFlow(Context context, AttributeSet attrs, int defStyle) { 78 | super(context, attrs, defStyle); 79 | init(); 80 | } 81 | 82 | public KeywordsFlow(Context context, AttributeSet attrs) { 83 | super(context, attrs); 84 | init(); 85 | } 86 | 87 | public KeywordsFlow(Context context) { 88 | super(context); 89 | init(); 90 | } 91 | 92 | private void init() { 93 | lastStartAnimationTime = 0l; 94 | animDuration = ANIM_DURATION; 95 | random = new Random(); 96 | vecKeywords = new Vector(MAX); 97 | getViewTreeObserver().addOnGlobalLayoutListener(this); 98 | interpolator = AnimationUtils.loadInterpolator(getContext(), android.R.anim.decelerate_interpolator); 99 | animAlpha2Opaque = new AlphaAnimation(0.0f, 1.0f); 100 | animAlpha2Transparent = new AlphaAnimation(1.0f, 0.0f); 101 | animScaleLarge2Normal = new ScaleAnimation(2, 1, 2, 1); 102 | animScaleNormal2Large = new ScaleAnimation(1, 2, 1, 2); 103 | animScaleZero2Normal = new ScaleAnimation(0, 1, 0, 1); 104 | animScaleNormal2Zero = new ScaleAnimation(1, 0, 1, 0); 105 | } 106 | 107 | public long getDuration() { 108 | return animDuration; 109 | } 110 | 111 | public void setDuration(long duration) { 112 | animDuration = duration; 113 | } 114 | 115 | public boolean feedKeyword(String keyword) { 116 | boolean result = false; 117 | if (vecKeywords.size() < MAX) { 118 | result = vecKeywords.add(keyword); 119 | } 120 | return result; 121 | } 122 | 123 | /** 124 | * 开始动画显示。
125 | * 之前已经存在的TextView将会显示退出动画。
126 | * 127 | * @return 正常显示动画返回true;反之为false。返回false原因如下:
128 | * 1.时间上不允许,受lastStartAnimationTime的制约;
129 | * 2.未获取到width和height的值。
130 | */ 131 | public boolean go2Show(int animType) { 132 | if (System.currentTimeMillis() - lastStartAnimationTime > animDuration) { 133 | enableShow = true; 134 | if (animType == ANIMATION_IN) { 135 | txtAnimInType = OUTSIDE_TO_LOCATION; 136 | txtAnimOutType = LOCATION_TO_CENTER; 137 | } else if (animType == ANIMATION_OUT) { 138 | txtAnimInType = CENTER_TO_LOCATION; 139 | txtAnimOutType = LOCATION_TO_OUTSIDE; 140 | } 141 | disapper(); 142 | boolean result = show(); 143 | return result; 144 | } 145 | return false; 146 | } 147 | 148 | private void disapper() { 149 | int size = getChildCount(); 150 | for (int i = size - 1; i >= 0; i--) { 151 | final TextView txt = (TextView) getChildAt(i); 152 | if (txt.getVisibility() == View.GONE) { 153 | removeView(txt); 154 | continue; 155 | } 156 | LayoutParams layParams = (LayoutParams) txt.getLayoutParams(); 157 | // Log.d("ANDROID_LAB", txt.getText() + " leftM=" + 158 | // layParams.leftMargin + " topM=" + layParams.topMargin 159 | // + " width=" + txt.getWidth()); 160 | int[] xy = new int[] { layParams.leftMargin, layParams.topMargin, txt.getWidth() }; 161 | AnimationSet animSet = getAnimationSet(xy, (width >> 1), (height >> 1), txtAnimOutType); 162 | txt.startAnimation(animSet); 163 | animSet.setAnimationListener(new AnimationListener() { 164 | public void onAnimationStart(Animation animation) { 165 | } 166 | 167 | public void onAnimationRepeat(Animation animation) { 168 | } 169 | 170 | public void onAnimationEnd(Animation animation) { 171 | txt.setOnClickListener(null); 172 | txt.setClickable(false); 173 | txt.setVisibility(View.GONE); 174 | } 175 | }); 176 | } 177 | } 178 | 179 | private boolean show() { 180 | if (width > 0 && height > 0 && vecKeywords != null && vecKeywords.size() > 0 && enableShow) { 181 | enableShow = false; 182 | lastStartAnimationTime = System.currentTimeMillis(); 183 | //找到中心点 184 | int xCenter = width >> 1, yCenter = height >> 1; 185 | //关键字的个数。 186 | int size = vecKeywords.size(); 187 | int xItem = width / size, yItem = height / size; 188 | Log.d("ANDROID_LAB", "--------------------------width=" + width + 189 | " height=" + height + " xItem=" + xItem 190 | + " yItem=" + yItem + "---------------------------"); 191 | LinkedList listX = new LinkedList(), listY = new LinkedList(); 192 | for (int i = 0; i < size; i++) { 193 | // 准备随机候选数,分别对应x/y轴位置 194 | listX.add(i * xItem); 195 | listY.add(i * yItem + (yItem >> 2)); 196 | Log.e("Search", "ListX:"+(i * xItem)+"#listY:"+(i * yItem + (yItem >> 2))); 197 | } 198 | // TextView[] txtArr = new TextView[size]; 199 | LinkedList listTxtTop = new LinkedList(); 200 | LinkedList listTxtBottom = new LinkedList(); 201 | for (int i = 0; i < size; i++) { 202 | String keyword = vecKeywords.get(i); 203 | // 随机颜色 204 | int ranColor = 0xff000000 | random.nextInt(0x0077ffff); 205 | // 随机位置,糙值 206 | int xy[] = randomXY(random, listX, listY, xItem); 207 | // 随机字体大小 208 | int txtSize = TEXT_SIZE_MIN + random.nextInt(TEXT_SIZE_MAX - TEXT_SIZE_MIN + 1); 209 | // 实例化TextView 210 | final TextView txt = new TextView(getContext()); 211 | txt.setOnClickListener(itemClickListener); 212 | txt.setText(keyword); 213 | txt.setTextColor(ranColor); 214 | txt.setTextSize(TypedValue.COMPLEX_UNIT_SP, txtSize); 215 | txt.setShadowLayer(1, 1, 1, 0xdd696969); 216 | txt.setGravity(Gravity.CENTER); 217 | 218 | // txt.setBackgroundColor(Color.RED); 219 | // 获取文本长度 220 | Paint paint = txt.getPaint(); 221 | int strWidth = (int) Math.ceil(paint.measureText(keyword)); 222 | xy[IDX_TXT_LENGTH] = strWidth; 223 | // 第一次修正:修正x坐标 224 | if (xy[IDX_X] + strWidth > width - (xItem >> 1)) { 225 | int baseX = width - strWidth; 226 | // 减少文本右边缘一样的概率 227 | xy[IDX_X] = baseX - xItem + random.nextInt(xItem >> 1); 228 | } else if (xy[IDX_X] == 0) { 229 | // 减少文本左边缘一样的概率 230 | xy[IDX_X] = Math.max(random.nextInt(xItem), xItem / 3); 231 | } 232 | xy[IDX_DIS_Y] = Math.abs(xy[IDX_Y] - yCenter); 233 | txt.setTag(xy); 234 | if (xy[IDX_Y] > yCenter) { 235 | listTxtBottom.add(txt); 236 | } else { 237 | listTxtTop.add(txt); 238 | } 239 | } 240 | attach2Screen(listTxtTop, xCenter, yCenter, yItem); 241 | attach2Screen(listTxtBottom, xCenter, yCenter, yItem); 242 | return true; 243 | } 244 | return false; 245 | } 246 | 247 | /** 修正TextView的Y坐标将将其添加到容器上。 */ 248 | private void attach2Screen(LinkedList listTxt, int xCenter, int yCenter, int yItem) { 249 | int size = listTxt.size(); 250 | sortXYList(listTxt, size); 251 | for (int i = 0; i < size; i++) { 252 | TextView txt = listTxt.get(i); 253 | int[] iXY = (int[]) txt.getTag(); 254 | // Log.d("ANDROID_LAB", "fix[ " + txt.getText() + " ] x:" + 255 | // iXY[IDX_X] + " y:" + iXY[IDX_Y] + " r2=" 256 | // + iXY[IDX_DIS_Y]); 257 | // 第二次修正:修正y坐标 258 | int yDistance = iXY[IDX_Y] - yCenter; 259 | // 对于最靠近中心点的,其值不会大于yItem
260 | // 对于可以一路下降到中心点的,则该值也是其应调整的大小
261 | int yMove = Math.abs(yDistance); 262 | inner: for (int k = i - 1; k >= 0; k--) { 263 | int[] kXY = (int[]) listTxt.get(k).getTag(); 264 | int startX = kXY[IDX_X]; 265 | int endX = startX + kXY[IDX_TXT_LENGTH]; 266 | // y轴以中心点为分隔线,在同一侧 267 | if (yDistance * (kXY[IDX_Y] - yCenter) > 0) { 268 | // Log.d("ANDROID_LAB", "compare:" + 269 | // listTxt.get(k).getText()); 270 | if (isXMixed(startX, endX, iXY[IDX_X], iXY[IDX_X] + iXY[IDX_TXT_LENGTH])) { 271 | int tmpMove = Math.abs(iXY[IDX_Y] - kXY[IDX_Y]); 272 | if (tmpMove > yItem) { 273 | yMove = tmpMove; 274 | } else if (yMove > 0) { 275 | // 取消默认值。 276 | yMove = 0; 277 | } 278 | // Log.d("ANDROID_LAB", "break"); 279 | break inner; 280 | } 281 | } 282 | } 283 | // Log.d("ANDROID_LAB", txt.getText() + " yMove=" + yMove); 284 | if (yMove > yItem) { 285 | int maxMove = yMove - yItem; 286 | int randomMove = random.nextInt(maxMove); 287 | int realMove = Math.max(randomMove, maxMove >> 1) * yDistance / Math.abs(yDistance); 288 | iXY[IDX_Y] = iXY[IDX_Y] - realMove; 289 | iXY[IDX_DIS_Y] = Math.abs(iXY[IDX_Y] - yCenter); 290 | // 已经调整过前i个需要再次排序 291 | sortXYList(listTxt, i + 1); 292 | } 293 | LayoutParams layParams = new LayoutParams(LayoutParams.WRAP_CONTENT, 294 | LayoutParams.WRAP_CONTENT); 295 | layParams.gravity = Gravity.LEFT | Gravity.TOP; 296 | layParams.leftMargin = iXY[IDX_X]; 297 | layParams.topMargin = iXY[IDX_Y]; 298 | addView(txt, layParams); 299 | // 动画 300 | AnimationSet animSet = getAnimationSet(iXY, xCenter, yCenter, txtAnimInType); 301 | txt.startAnimation(animSet); 302 | } 303 | } 304 | 305 | public AnimationSet getAnimationSet(int[] xy, int xCenter, int yCenter, int type) { 306 | AnimationSet animSet = new AnimationSet(true); 307 | animSet.setInterpolator(interpolator); 308 | if (type == OUTSIDE_TO_LOCATION) { 309 | animSet.addAnimation(animAlpha2Opaque); 310 | animSet.addAnimation(animScaleLarge2Normal); 311 | TranslateAnimation translate = new TranslateAnimation( 312 | (xy[IDX_X] + (xy[IDX_TXT_LENGTH] >> 1) - xCenter) << 1, 0, (xy[IDX_Y] - yCenter) << 1, 0); 313 | animSet.addAnimation(translate); 314 | } else if (type == LOCATION_TO_OUTSIDE) { 315 | animSet.addAnimation(animAlpha2Transparent); 316 | animSet.addAnimation(animScaleNormal2Large); 317 | TranslateAnimation translate = new TranslateAnimation(0, 318 | (xy[IDX_X] + (xy[IDX_TXT_LENGTH] >> 1) - xCenter) << 1, 0, (xy[IDX_Y] - yCenter) << 1); 319 | animSet.addAnimation(translate); 320 | } else if (type == LOCATION_TO_CENTER) { 321 | animSet.addAnimation(animAlpha2Transparent); 322 | animSet.addAnimation(animScaleNormal2Zero); 323 | TranslateAnimation translate = new TranslateAnimation(0, (-xy[IDX_X] + xCenter), 0, (-xy[IDX_Y] + yCenter)); 324 | animSet.addAnimation(translate); 325 | } else if (type == CENTER_TO_LOCATION) { 326 | animSet.addAnimation(animAlpha2Opaque); 327 | animSet.addAnimation(animScaleZero2Normal); 328 | TranslateAnimation translate = new TranslateAnimation((-xy[IDX_X] + xCenter), 0, (-xy[IDX_Y] + yCenter), 0); 329 | animSet.addAnimation(translate); 330 | } 331 | animSet.setDuration(animDuration); 332 | return animSet; 333 | } 334 | 335 | /** 336 | * 根据与中心点的距离由近到远进行冒泡排序。 337 | * 338 | * @param endIdx 339 | * 起始位置。 340 | * /*@param txtArr 341 | * 待排序的数组。 */ 342 | 343 | 344 | private void sortXYList(LinkedList listTxt, int endIdx) { 345 | for (int i = 0; i < endIdx; i++) { 346 | for (int k = i + 1; k < endIdx; k++) { 347 | if (((int[]) listTxt.get(k).getTag())[IDX_DIS_Y] < ((int[]) listTxt.get(i).getTag())[IDX_DIS_Y]) { 348 | TextView iTmp = listTxt.get(i); 349 | TextView kTmp = listTxt.get(k); 350 | listTxt.set(i, kTmp); 351 | listTxt.set(k, iTmp); 352 | } 353 | } 354 | } 355 | } 356 | 357 | /** A线段与B线段所代表的直线在X轴映射上是否有交集。 */ 358 | private boolean isXMixed(int startA, int endA, int startB, int endB) { 359 | boolean result = false; 360 | if (startB >= startA && startB <= endA) { 361 | result = true; 362 | } else if (endB >= startA && endB <= endA) { 363 | result = true; 364 | } else if (startA >= startB && startA <= endB) { 365 | result = true; 366 | } else if (endA >= startB && endA <= endB) { 367 | result = true; 368 | } 369 | return result; 370 | } 371 | 372 | private int[] randomXY(Random ran, LinkedList listX, LinkedList listY, int xItem) { 373 | int[] arr = new int[4]; 374 | arr[IDX_X] = listX.remove(ran.nextInt(listX.size())); 375 | arr[IDX_Y] = listY.remove(ran.nextInt(listY.size())); 376 | return arr; 377 | } 378 | 379 | public void onGlobalLayout() { 380 | int tmpW = getWidth(); 381 | int tmpH = getHeight(); 382 | if (width != tmpW || height != tmpH) { 383 | width = tmpW; 384 | height = tmpH; 385 | show(); 386 | } 387 | } 388 | 389 | public Vector getKeywords() { 390 | return vecKeywords; 391 | } 392 | 393 | public void rubKeywords() { 394 | vecKeywords.clear(); 395 | } 396 | 397 | /** 直接清除所有的TextView。在清除之前不会显示动画。 */ 398 | public void rubAllViews() { 399 | removeAllViews(); 400 | } 401 | 402 | public void setOnItemClickListener(OnClickListener listener) { 403 | itemClickListener = listener; 404 | } 405 | 406 | // public void onDraw(Canvas canvas) { 407 | // super.onDraw(canvas); 408 | // Paint p = new Paint(); 409 | // p.setColor(Color.BLACK); 410 | // canvas.drawCircle((width >> 1) - 2, (height >> 1) - 2, 4, p); 411 | // p.setColor(Color.RED); 412 | // } 413 | } 414 | -------------------------------------------------------------------------------- /app/src/main/java/com/wenhuaijun/keywordsflowdemo/KeywordsFlowView.java: -------------------------------------------------------------------------------- 1 | package com.wenhuaijun.keywordsflowdemo; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.MotionEvent; 6 | import android.view.VelocityTracker; 7 | 8 | import java.util.Random; 9 | 10 | /** 11 | * Created by Administrator on 2016/2/24 0024. 12 | */ 13 | public class KeywordsFlowView extends KeywordsFlow{ 14 | //手势滑动 15 | public static final int SNAP_VELOCITY = 50; 16 | private float yDown; 17 | private float yMove; 18 | private float yUp; 19 | private String[] words; 20 | private boolean shouldScroolFlow = true; 21 | //用于计算手指滑动的速度。 22 | private VelocityTracker mVelocityTracker; 23 | public KeywordsFlowView(Context context, AttributeSet attrs, int defStyle) { 24 | super(context, attrs, defStyle); 25 | } 26 | 27 | public KeywordsFlowView(Context context, AttributeSet attrs) { 28 | super(context, attrs); 29 | } 30 | 31 | public KeywordsFlowView(Context context) { 32 | super(context); 33 | } 34 | 35 | 36 | 37 | public void setWords(String[] words){ 38 | this.words =words; 39 | } 40 | public void show(String[] words,int animType){ 41 | setWords(words); 42 | rubKeywords(); 43 | feedKeywordsFlow(this, words); 44 | go2Show(animType); 45 | } 46 | private static void feedKeywordsFlow(KeywordsFlow keywordsFlow, String[] words) { 47 | Random random = new Random(); 48 | for (int i = 0; i < KeywordsFlow.MAX; i++) { 49 | int ran = random.nextInt(words.length); 50 | String tmp = words[ran]; 51 | keywordsFlow.feedKeyword(tmp); 52 | } 53 | } 54 | public void setTextShowSize(int size){ 55 | MAX=size; 56 | } 57 | public void shouldScroolFlow(boolean shouldScroolFlow){ 58 | this.shouldScroolFlow =shouldScroolFlow; 59 | } 60 | @Override 61 | public boolean onTouchEvent(MotionEvent event) { 62 | if(!shouldScroolFlow){ 63 | return super.onTouchEvent(event); 64 | } 65 | createVelocityTracker(event); 66 | switch(event.getAction()){ 67 | case MotionEvent.ACTION_DOWN: 68 | yDown=event.getRawY(); 69 | break; 70 | case MotionEvent.ACTION_MOVE: 71 | yMove=event.getRawY(); 72 | break; 73 | case MotionEvent.ACTION_UP: 74 | yUp=event.getRawY(); 75 | float distance=yUp-yDown; 76 | if(distance<-100&& getScrollVelocity() > SNAP_VELOCITY){ 77 | rubKeywords(); 78 | feedKeywordsFlow(this, words); 79 | go2Show(KeywordsFlow.ANIMATION_OUT); 80 | }else if(distance>30&&getScrollVelocity() > SNAP_VELOCITY){ 81 | rubKeywords(); 82 | feedKeywordsFlow(this, words); 83 | go2Show(KeywordsFlow.ANIMATION_IN); 84 | } 85 | break; 86 | 87 | } 88 | return true; 89 | } 90 | private void createVelocityTracker(MotionEvent event) { 91 | if (mVelocityTracker == null) { 92 | mVelocityTracker = VelocityTracker.obtain(); 93 | } 94 | mVelocityTracker.addMovement(event); 95 | } 96 | private int getScrollVelocity() { 97 | mVelocityTracker.computeCurrentVelocity(1000); 98 | int velocity = (int) mVelocityTracker.getXVelocity(); 99 | return Math.abs(velocity); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /app/src/main/java/com/wenhuaijun/keywordsflowdemo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.wenhuaijun.keywordsflowdemo; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | import android.widget.Button; 7 | import android.widget.TextView; 8 | import android.widget.Toast; 9 | 10 | public class MainActivity extends AppCompatActivity implements View.OnClickListener { 11 | KeywordsFlowView keywordsFlowView; 12 | Button flow_in; 13 | Button flow_out; 14 | 15 | public static final String[] keywords = { "Apple", "Android", "呵呵", 16 | "高富帅","女神","拥抱","旅行","爱情","屌丝","搞笑","暴走漫画","重邮","信科", 17 | "唯美","汪星人","秋天","雨天","科幻","黑夜", 18 | "孤独","星空","东京食尸鬼","金正恩","张全蛋","东京热","陈希妍", 19 | "明星","NBA","马云","码农","动漫","时尚","熊孩子","地理","伤感", 20 | "二次元" 21 | }; 22 | @Override 23 | protected void onCreate(Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | setContentView(R.layout.activity_main); 26 | flow_in =(Button)findViewById(R.id.flow_in); 27 | flow_out =(Button)findViewById(R.id.flow_out); 28 | keywordsFlowView = (KeywordsFlowView)findViewById(R.id.keywordsFlowView); 29 | //设置每次随机飞入文字的个数 30 | keywordsFlowView.setTextShowSize(15); 31 | //设置是否允许滑动屏幕切换文字 32 | keywordsFlowView.shouldScroolFlow(true); 33 | //开始展示 34 | keywordsFlowView.show(keywords, KeywordsFlow.ANIMATION_IN); 35 | flow_in.setOnClickListener(this); 36 | flow_out.setOnClickListener(this); 37 | //设置文字的点击点击监听 38 | keywordsFlowView.setOnItemClickListener(new View.OnClickListener() { 39 | @Override 40 | public void onClick(View v) { 41 | Toast.makeText(MainActivity.this,((TextView)v).getText().toString(),Toast.LENGTH_SHORT).show(); 42 | } 43 | }); 44 | 45 | } 46 | 47 | @Override 48 | public void onClick(View v) { 49 | if(v==flow_in){ 50 | //文字随机飞入 51 | keywordsFlowView.show(keywords,KeywordsFlow.ANIMATION_IN); 52 | }else if (v==flow_out){ 53 | //文字随机飞出 54 | keywordsFlowView.show(keywords,KeywordsFlow.ANIMATION_OUT); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 |