效果图:

实现该效果需要解决以下五点:

1.布局的选用。
2.确定动画区域,即布局的宽高。
3.对关键字坐标的随机分配。
4.对随机分配的坐标进行向中心靠拢。
5.动画的实现。

本文内容归CSDN博客博主Sodino 所有
转载请注明出处:http://blog.csdn.net/sodino/article/details/7176796

下面各个击破:
1.布局的选用。
在五种常用布局中,可实现此效果的有AbsoluteLayout、FrameLayout、RelativeLayout三种。一开始我选用的AbsoluteLayout,运行结果出来后,发现AbsoluteLayout下的TextView一旦超出其显示范围,超出的范围将无法显示,而余下的两种布局,其超出的范围会自动换行显示出来(TextView长度超出父组件显示范围可在代码中避免,此处仅是举例,说明AbsoluteLayout的先天不足)。另,官方已不再推荐使用AbsoluteLayout,所以本处凭个人喜好我选用FrameLayout。

FrameLayout如何实现AbsoluteLayout对其子组件进行定点放置呢?答案在FrameLayout.LayoutParams上。该类有相关属性为leftMargin及topMargin。要将子组件左上角定点放置在其父组件中的(x,y)处,仅需对leftMargin赋值为x,对topMargin赋值为y即可。

2.确定动画区域,即布局的宽高。
在对显示关键字TextView进行分配坐标之前,应该要先知道父组件的宽高各有多少可供随机分配。
获取宽高使用到OnGlobalLayoutListener。本例中KeywordsFlow继承自FrameLayout,同时也实现了OnGlobalLayoutListener接口,在其初始化方法init()中设置了监听getViewTreeObserver().addOnGlobalLayoutListener(this);
当监听事件被触发时,即可获取而已的宽高。

[java] view plaincopyprint?
  1. public void onGlobalLayout() {
  2. int tmpW = getWidth();
  3. int tmpH = getHeight();
  4. if (width != tmpW || height != tmpH) {
  5. width = tmpW;
  6. height = tmpH;
  7. show();
  8. }
  9. }

public void onGlobalLayout() {int tmpW = getWidth();int tmpH = getHeight();if (width != tmpW || height != tmpH) {width = tmpW;height = tmpH;show();}}

3.对关键字坐标的随机分配。
TextView坐标的随机是否到位分配决定着整体效果的好坏。
本例设定关键字最多为10个,在布局的X Y轴上各自进行10等分。每个关键字依照其添加顺序随机各自在X轴和Y轴上选择等分后的10点中的某个点为margin的值。此值为糙值,需要对X轴进行越界修正,对Y轴进行向中心靠拢修正。对X轴坐标的修正为如下:

[java] view plaincopyprint?
  1. // 获取文本长度
  2. Paint paint = txt.getPaint();
  3. int strWidth = (int) Math.ceil(paint.measureText(keyword));
  4. xy[IDX_TXT_LENGTH] = strWidth;
  5. // 第一次修正:修正x坐标
  6. if (xy[IDX_X] + strWidth > width - (xItem >> 1)) {
  7. int baseX = width - strWidth;
  8. // 减少文本右边缘一样的概率
  9. xy[IDX_X] = baseX - xItem + random.nextInt(xItem >> 1);
  10. } else if (xy[IDX_X] == 0) {
  11. // 减少文本左边缘一样的概率
  12. xy[IDX_X] = Math.max(random.nextInt(xItem), xItem / 3);
  13. }

// 获取文本长度Paint paint = txt.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);}

4.对随机分配的坐标进行向中心靠拢。
此操作将修正Y轴坐标。
由于随机分配中,可能出现某个关键字在朝中心点方向上的空间中再没有其它关键字了,此时该关键字在Y轴上应该朝中心点靠拢。实现代码如下:

[java] view plaincopyprint?
  1. // 第二次修正:修正y坐标
  2. int yDistance = iXY[IDX_Y] - yCenter;
  3. // 对于最靠近中心点的,其值不会大于yItem<br/>
  4. // 对于可以一路下降到中心点的,则该值也是其应调整的大小<br/>
  5. int yMove = Math.abs(yDistance);
  6. inner: for (int k = i - 1; k >= 0; k--) {
  7. int[] kXY = (int[]) listTxt.get(k).getTag();
  8. int startX = kXY[IDX_X];
  9. int endX = startX + kXY[IDX_TXT_LENGTH];
  10. // y轴以中心点为分隔线,在同一侧
  11. if (yDistance * (kXY[IDX_Y] - yCenter) > 0) {
  12. // Log.d("ANDROID_LAB", "compare:" +
  13. // listTxt.get(k).getText());
  14. if (isXMixed(startX, endX, iXY[IDX_X], iXY[IDX_X] + iXY[IDX_TXT_LENGTH])) {
  15. int tmpMove = Math.abs(iXY[IDX_Y] - kXY[IDX_Y]);
  16. if (tmpMove > yItem) {
  17. yMove = tmpMove;
  18. } else if (yMove > 0) {
  19. // 取消默认值。
  20. yMove = 0;
  21. }
  22. // Log.d("ANDROID_LAB", "break");
  23. break inner;
  24. }
  25. }
  26. }
  27. // Log.d("ANDROID_LAB", txt.getText() + " yMove=" + yMove);
  28. if (yMove > yItem) {
  29. int maxMove = yMove - yItem;
  30. int randomMove = random.nextInt(maxMove);
  31. int realMove = Math.max(randomMove, maxMove >> 1) * yDistance / Math.abs(yDistance);
  32. iXY[IDX_Y] = iXY[IDX_Y] - realMove;
  33. iXY[IDX_DIS_Y] = Math.abs(iXY[IDX_Y] - yCenter);
  34. // 已经调整过前i个需要再次排序
  35. sortXYList(listTxt, i + 1);
  36. }

// 第二次修正:修正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) {// Log.d("ANDROID_LAB", "compare:" +// listTxt.get(k).getText());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;}// Log.d("ANDROID_LAB", "break");break inner;}}}// Log.d("ANDROID_LAB", txt.getText() + " yMove=" + yMove);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);}

5.动画的实现。
每个TextView的动画都有包括三部分:伸缩动画ScaleAnimation、透明度渐变动画AlphaAnimation及位移动画TranslateAnimation。以上三个动画中除了位移动画是独立的,其它两种动画都是可以共用的。三种动画的组合使用AnimationSet拼装在一起同时作用在TextView上。动画的实现如下:

[java] view plaincopyprint?
  1. public AnimationSet getAnimationSet(int[] xy, int xCenter, int yCenter, int type) {
  2. AnimationSet animSet = new AnimationSet(true);
  3. animSet.setInterpolator(interpolator);
  4. if (type == OUTSIDE_TO_LOCATION) {
  5. animSet.addAnimation(animAlpha2Opaque);
  6. animSet.addAnimation(animScaleLarge2Normal);
  7. TranslateAnimation translate = new TranslateAnimation(
  8. (xy[IDX_X] + (xy[IDX_TXT_LENGTH] >> 1) - xCenter) << 1, 0, (xy[IDX_Y] - yCenter) << 1, 0);
  9. animSet.addAnimation(translate);
  10. } else if (type == LOCATION_TO_OUTSIDE) {
  11. animSet.addAnimation(animAlpha2Transparent);
  12. animSet.addAnimation(animScaleNormal2Large);
  13. TranslateAnimation translate = new TranslateAnimation(0,
  14. (xy[IDX_X] + (xy[IDX_TXT_LENGTH] >> 1) - xCenter) << 1, 0, (xy[IDX_Y] - yCenter) << 1);
  15. animSet.addAnimation(translate);
  16. } else if (type == LOCATION_TO_CENTER) {
  17. animSet.addAnimation(animAlpha2Transparent);
  18. animSet.addAnimation(animScaleNormal2Zero);
  19. TranslateAnimation translate = new TranslateAnimation(0, (-xy[IDX_X] + xCenter), 0, (-xy[IDX_Y] + yCenter));
  20. animSet.addAnimation(translate);
  21. } else if (type == CENTER_TO_LOCATION) {
  22. animSet.addAnimation(animAlpha2Opaque);
  23. animSet.addAnimation(animScaleZero2Normal);
  24. TranslateAnimation translate = new TranslateAnimation((-xy[IDX_X] + xCenter), 0, (-xy[IDX_Y] + yCenter), 0);
  25. animSet.addAnimation(translate);
  26. }
  27. animSet.setDuration(animDuration);
  28. return animSet;
  29. }

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;}

最后有个小点需要再次提醒下,使用KeywordsFlow时,在Eclipse开发环境下导出混淆包时,需要在proguard.cfg中添加:-keep public class * extends android.widget.FrameLayout
否则将会提示无法找到该类。
好了,文嗦嗦的东西到此结束,贴上Java代码如下,xml代码请根据效果图自己鼓捣吧。

[java] view plaincopyprint?
  1. ActKeywordAnim.java
  2. package lab.sodino.searchkeywordanim;
  3. import java.util.Random;
  4. import android.app.Activity;
  5. import android.content.Intent;
  6. import android.net.Uri;
  7. import android.os.Bundle;
  8. import android.view.View;
  9. import android.view.View.OnClickListener;
  10. import android.widget.Button;
  11. import android.widget.TextView;
  12. /**
  13. * @author Sodino E-mail:sodinoopen@hotmail.com
  14. * @version Time:2011-12-26 下午03:34:16
  15. */
  16. public class ActKeywordAnim extends Activity implements OnClickListener {
  17. public static final String[] keywords = { "QQ", "Sodino", "APK", "GFW", "铅笔",//
  18. "短信", "桌面精灵", "MacBook Pro", "平板电脑", "雅诗兰黛",//
  19. "卡西欧 TR-100", "笔记本", "SPY Mouse", "Thinkpad E40", "捕鱼达人",//
  20. "内存清理", "地图", "导航", "闹钟", "主题",//
  21. "通讯录", "播放器", "CSDN leak", "安全", "3D",//
  22. "美女", "天气", "4743G", "戴尔", "联想",//
  23. "欧朋", "浏览器", "愤怒的小鸟", "mmShow", "网易公开课",//
  24. "iciba", "油水关系", "网游App", "互联网", "365日历",//
  25. "脸部识别", "Chrome", "Safari", "中国版Siri", "A5处理器",//
  26. "iPhone4S", "摩托 ME525", "魅族 M9", "尼康 S2500" };
  27. private KeywordsFlow keywordsFlow;
  28. private Button btnIn, btnOut;
  29. public void onCreate(Bundle savedInstanceState) {
  30. super.onCreate(savedInstanceState);
  31. setContentView(R.layout.main);
  32. btnIn = (Button) findViewById(R.id.btnIn);
  33. btnOut = (Button) findViewById(R.id.btnOut);
  34. btnIn.setOnClickListener(this);
  35. btnOut.setOnClickListener(this);
  36. keywordsFlow = (KeywordsFlow) findViewById(R.id.keywordsFlow);
  37. keywordsFlow.setDuration(800l);
  38. keywordsFlow.setOnItemClickListener(this);
  39. // 添加
  40. feedKeywordsFlow(keywordsFlow, keywords);
  41. keywordsFlow.go2Show(KeywordsFlow.ANIMATION_IN);
  42. }
  43. private static void feedKeywordsFlow(KeywordsFlow keywordsFlow, String[] arr) {
  44. Random random = new Random();
  45. for (int i = 0; i < KeywordsFlow.MAX; i++) {
  46. int ran = random.nextInt(arr.length);
  47. String tmp = arr[ran];
  48. keywordsFlow.feedKeyword(tmp);
  49. }
  50. }
  51. @Override
  52. public void onClick(View v) {
  53. if (v == btnIn) {
  54. keywordsFlow.rubKeywords();
  55. // keywordsFlow.rubAllViews();
  56. feedKeywordsFlow(keywordsFlow, keywords);
  57. keywordsFlow.go2Show(KeywordsFlow.ANIMATION_IN);
  58. } else if (v == btnOut) {
  59. keywordsFlow.rubKeywords();
  60. // keywordsFlow.rubAllViews();
  61. feedKeywordsFlow(keywordsFlow, keywords);
  62. keywordsFlow.go2Show(KeywordsFlow.ANIMATION_OUT);
  63. } else if (v instanceof TextView) {
  64. String keyword = ((TextView) v).getText().toString();
  65. Intent intent = new Intent();
  66. intent.setAction(Intent.ACTION_VIEW);
  67. intent.addCategory(Intent.CATEGORY_DEFAULT);
  68. intent.setData(Uri.parse("http://www.google.com.hk/#q=" + keyword));
  69. startActivity(intent);
  70. }
  71. }
  72. }

ActKeywordAnim.javapackage lab.sodino.searchkeywordanim;import java.util.Random;import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView;/*** @author Sodino E-mail:sodinoopen@hotmail.com* @version Time:2011-12-26 下午03:34:16*/ public class ActKeywordAnim extends Activity implements OnClickListener {public static final String[] keywords = { "QQ", "Sodino", "APK", "GFW", "铅笔",//"短信", "桌面精灵", "MacBook Pro", "平板电脑", "雅诗兰黛",//"卡西欧 TR-100", "笔记本", "SPY Mouse", "Thinkpad E40", "捕鱼达人",//"内存清理", "地图", "导航", "闹钟", "主题",//"通讯录", "播放器", "CSDN leak", "安全", "3D",//"美女", "天气", "4743G", "戴尔", "联想",//"欧朋", "浏览器", "愤怒的小鸟", "mmShow", "网易公开课",//"iciba", "油水关系", "网游App", "互联网", "365日历",//"脸部识别", "Chrome", "Safari", "中国版Siri", "A5处理器",//"iPhone4S", "摩托 ME525", "魅族 M9", "尼康 S2500" };private KeywordsFlow keywordsFlow;private Button btnIn, btnOut;public void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);btnIn = (Button) findViewById(R.id.btnIn);btnOut = (Button) findViewById(R.id.btnOut);btnIn.setOnClickListener(this);btnOut.setOnClickListener(this);keywordsFlow = (KeywordsFlow) findViewById(R.id.keywordsFlow);keywordsFlow.setDuration(800l);keywordsFlow.setOnItemClickListener(this);// 添加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);}}@Overridepublic void onClick(View v) {if (v == btnIn) {keywordsFlow.rubKeywords();// keywordsFlow.rubAllViews();feedKeywordsFlow(keywordsFlow, keywords);keywordsFlow.go2Show(KeywordsFlow.ANIMATION_IN);} else if (v == btnOut) {keywordsFlow.rubKeywords();// keywordsFlow.rubAllViews();feedKeywordsFlow(keywordsFlow, keywords);keywordsFlow.go2Show(KeywordsFlow.ANIMATION_OUT);} else if (v instanceof TextView) {String keyword = ((TextView) v).getText().toString();Intent intent = new Intent();intent.setAction(Intent.ACTION_VIEW);intent.addCategory(Intent.CATEGORY_DEFAULT);intent.setData(Uri.parse("http://www.google.com.hk/#q=" + keyword));startActivity(intent);}} }

[java] view plaincopyprint?
  1. KeywordsFlow.java
  2. package lab.sodino.searchkeywordanim;
  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.TypedValue;
  10. import android.view.Gravity;
  11. import android.view.View;
  12. import android.view.ViewTreeObserver.OnGlobalLayoutListener;
  13. import android.view.animation.AlphaAnimation;
  14. import android.view.animation.Animation;
  15. import android.view.animation.Animation.AnimationListener;
  16. import android.view.animation.AnimationSet;
  17. import android.view.animation.AnimationUtils;
  18. import android.view.animation.Interpolator;
  19. import android.view.animation.ScaleAnimation;
  20. import android.view.animation.TranslateAnimation;
  21. import android.widget.FrameLayout;
  22. import android.widget.TextView;
  23. /**
  24. * 注意,出包时出混淆包,应在proguard.cfg中加入:<br/>
  25. * -keep public class * extends android.widget.FrameLayout<br/>
  26. *
  27. * @author Sodino E-mail:sodinoopen@hotmail.com
  28. * @version Time:2011-12-26 下午03:34:16
  29. */
  30. public class KeywordsFlow extends FrameLayout implements OnGlobalLayoutListener {
  31. public static final int IDX_X = 0;
  32. public static final int IDX_Y = 1;
  33. public static final int IDX_TXT_LENGTH = 2;
  34. public static final int IDX_DIS_Y = 3;
  35. /** 由外至内的动画。 */
  36. public static final int ANIMATION_IN = 1;
  37. /** 由内至外的动画。 */
  38. public static final int ANIMATION_OUT = 2;
  39. /** 位移动画类型:从外围移动到坐标点。 */
  40. public static final int OUTSIDE_TO_LOCATION = 1;
  41. /** 位移动画类型:从坐标点移动到外围。 */
  42. public static final int LOCATION_TO_OUTSIDE = 2;
  43. /** 位移动画类型:从中心点移动到坐标点。 */
  44. public static final int CENTER_TO_LOCATION = 3;
  45. /** 位移动画类型:从坐标点移动到中心点。 */
  46. public static final int LOCATION_TO_CENTER = 4;
  47. public static final long ANIM_DURATION = 800l;
  48. public static final int MAX = 10;
  49. public static final int TEXT_SIZE_MAX = 25;
  50. public static final int TEXT_SIZE_MIN = 15;
  51. private OnClickListener itemClickListener;
  52. private static Interpolator interpolator;
  53. private static AlphaAnimation animAlpha2Opaque;
  54. private static AlphaAnimation animAlpha2Transparent;
  55. private static ScaleAnimation animScaleLarge2Normal, animScaleNormal2Large, animScaleZero2Normal,
  56. animScaleNormal2Zero;
  57. /** 存储显示的关键字。 */
  58. private Vector<String> vecKeywords;
  59. private int width, height;
  60. /**
  61. * go2Show()中被赋值为true,标识开发人员触发其开始动画显示。<br/>
  62. * 本标识的作用是防止在填充keywrods未完成的过程中获取到width和height后提前启动动画。<br/>
  63. * 在show()方法中其被赋值为false。<br/>
  64. * 真正能够动画显示的另一必要条件:width 和 height不为0。<br/>
  65. */
  66. private boolean enableShow;
  67. private Random random;
  68. /**
  69. * @see ANIMATION_IN
  70. * @see ANIMATION_OUT
  71. * @see OUTSIDE_TO_LOCATION
  72. * @see LOCATION_TO_OUTSIDE
  73. * @see LOCATION_TO_CENTER
  74. * @see CENTER_TO_LOCATION
  75. * */
  76. private int txtAnimInType, txtAnimOutType;
  77. /** 最近一次启动动画显示的时间。 */
  78. private long lastStartAnimationTime;
  79. /** 动画运行时间。 */
  80. private long animDuration;
  81. public KeywordsFlow(Context context, AttributeSet attrs, int defStyle) {
  82. super(context, attrs, defStyle);
  83. init();
  84. }
  85. public KeywordsFlow(Context context, AttributeSet attrs) {
  86. super(context, attrs);
  87. init();
  88. }
  89. public KeywordsFlow(Context context) {
  90. super(context);
  91. init();
  92. }
  93. private void init() {
  94. lastStartAnimationTime = 0l;
  95. animDuration = ANIM_DURATION;
  96. random = new Random();
  97. vecKeywords = new Vector<String>(MAX);
  98. getViewTreeObserver().addOnGlobalLayoutListener(this);
  99. interpolator = AnimationUtils.loadInterpolator(getContext(), android.R.anim.decelerate_interpolator);
  100. animAlpha2Opaque = new AlphaAnimation(0.0f, 1.0f);
  101. animAlpha2Transparent = new AlphaAnimation(1.0f, 0.0f);
  102. animScaleLarge2Normal = new ScaleAnimation(2, 1, 2, 1);
  103. animScaleNormal2Large = new ScaleAnimation(1, 2, 1, 2);
  104. animScaleZero2Normal = new ScaleAnimation(0, 1, 0, 1);
  105. animScaleNormal2Zero = new ScaleAnimation(1, 0, 1, 0);
  106. }
  107. public long getDuration() {
  108. return animDuration;
  109. }
  110. public void setDuration(long duration) {
  111. animDuration = duration;
  112. }
  113. public boolean feedKeyword(String keyword) {
  114. boolean result = false;
  115. if (vecKeywords.size() < MAX) {
  116. result = vecKeywords.add(keyword);
  117. }
  118. return result;
  119. }
  120. /**
  121. * 开始动画显示。<br/>
  122. * 之前已经存在的TextView将会显示退出动画。<br/>
  123. *
  124. * @return 正常显示动画返回true;反之为false。返回false原因如下:<br/>
  125. * 1.时间上不允许,受lastStartAnimationTime的制约;<br/>
  126. * 2.未获取到width和height的值。<br/>
  127. */
  128. public boolean go2Show(int animType) {
  129. if (System.currentTimeMillis() - lastStartAnimationTime > animDuration) {
  130. enableShow = true;
  131. if (animType == ANIMATION_IN) {
  132. txtAnimInType = OUTSIDE_TO_LOCATION;
  133. txtAnimOutType = LOCATION_TO_CENTER;
  134. } else if (animType == ANIMATION_OUT) {
  135. txtAnimInType = CENTER_TO_LOCATION;
  136. txtAnimOutType = LOCATION_TO_OUTSIDE;
  137. }
  138. disapper();
  139. boolean result = show();
  140. return result;
  141. }
  142. return false;
  143. }
  144. private void disapper() {
  145. int size = getChildCount();
  146. for (int i = size - 1; i >= 0; i--) {
  147. final TextView txt = (TextView) getChildAt(i);
  148. if (txt.getVisibility() == View.GONE) {
  149. removeView(txt);
  150. continue;
  151. }
  152. FrameLayout.LayoutParams layParams = (LayoutParams) txt.getLayoutParams();
  153. // Log.d("ANDROID_LAB", txt.getText() + " leftM=" +
  154. // layParams.leftMargin + " topM=" + layParams.topMargin
  155. // + " width=" + txt.getWidth());
  156. int[] xy = new int[] { layParams.leftMargin, layParams.topMargin, txt.getWidth() };
  157. AnimationSet animSet = getAnimationSet(xy, (width >> 1), (height >> 1), txtAnimOutType);
  158. txt.startAnimation(animSet);
  159. animSet.setAnimationListener(new AnimationListener() {
  160. public void onAnimationStart(Animation animation) {
  161. }
  162. public void onAnimationRepeat(Animation animation) {
  163. }
  164. public void onAnimationEnd(Animation animation) {
  165. txt.setOnClickListener(null);
  166. txt.setClickable(false);
  167. txt.setVisibility(View.GONE);
  168. }
  169. });
  170. }
  171. }
  172. private boolean show() {
  173. if (width > 0 && height > 0 && vecKeywords != null && vecKeywords.size() > 0 && enableShow) {
  174. enableShow = false;
  175. lastStartAnimationTime = System.currentTimeMillis();
  176. int xCenter = width >> 1, yCenter = height >> 1;
  177. int size = vecKeywords.size();
  178. int xItem = width / size, yItem = height / size;
  179. // Log.d("ANDROID_LAB", "--------------------------width=" + width +
  180. // " height=" + height + " xItem=" + xItem
  181. // + " yItem=" + yItem + "---------------------------");
  182. LinkedList<Integer> listX = new LinkedList<Integer>(), listY = new LinkedList<Integer>();
  183. for (int i = 0; i < size; i++) {
  184. // 准备随机候选数,分别对应x/y轴位置
  185. listX.add(i * xItem);
  186. listY.add(i * yItem + (yItem >> 2));
  187. }
  188. // TextView[] txtArr = new TextView[size];
  189. LinkedList<TextView> listTxtTop = new LinkedList<TextView>();
  190. LinkedList<TextView> listTxtBottom = new LinkedList<TextView>();
  191. for (int i = 0; i < size; i++) {
  192. String keyword = vecKeywords.get(i);
  193. // 随机颜色
  194. int ranColor = 0xff000000 | random.nextInt(0x0077ffff);
  195. // 随机位置,糙值
  196. int xy[] = randomXY(random, listX, listY, xItem);
  197. // 随机字体大小
  198. int txtSize = TEXT_SIZE_MIN + random.nextInt(TEXT_SIZE_MAX - TEXT_SIZE_MIN + 1);
  199. // 实例化TextView
  200. final TextView txt = new TextView(getContext());
  201. txt.setOnClickListener(itemClickListener);
  202. txt.setText(keyword);
  203. txt.setTextColor(ranColor);
  204. txt.setTextSize(TypedValue.COMPLEX_UNIT_SP, txtSize);
  205. txt.setShadowLayer(2, 2, 2, 0xff696969);
  206. txt.setGravity(Gravity.CENTER);
  207. // 获取文本长度
  208. Paint paint = txt.getPaint();
  209. int strWidth = (int) Math.ceil(paint.measureText(keyword));
  210. xy[IDX_TXT_LENGTH] = strWidth;
  211. // 第一次修正:修正x坐标
  212. if (xy[IDX_X] + strWidth > width - (xItem >> 1)) {
  213. int baseX = width - strWidth;
  214. // 减少文本右边缘一样的概率
  215. xy[IDX_X] = baseX - xItem + random.nextInt(xItem >> 1);
  216. } else if (xy[IDX_X] == 0) {
  217. // 减少文本左边缘一样的概率
  218. xy[IDX_X] = Math.max(random.nextInt(xItem), xItem / 3);
  219. }
  220. xy[IDX_DIS_Y] = Math.abs(xy[IDX_Y] - yCenter);
  221. txt.setTag(xy);
  222. if (xy[IDX_Y] > yCenter) {
  223. listTxtBottom.add(txt);
  224. } else {
  225. listTxtTop.add(txt);
  226. }
  227. }
  228. attach2Screen(listTxtTop, xCenter, yCenter, yItem);
  229. attach2Screen(listTxtBottom, xCenter, yCenter, yItem);
  230. return true;
  231. }
  232. return false;
  233. }
  234. /** 修正TextView的Y坐标将将其添加到容器上。 */
  235. private void attach2Screen(LinkedList<TextView> listTxt, int xCenter, int yCenter, int yItem) {
  236. int size = listTxt.size();
  237. sortXYList(listTxt, size);
  238. for (int i = 0; i < size; i++) {
  239. TextView txt = listTxt.get(i);
  240. int[] iXY = (int[]) txt.getTag();
  241. // Log.d("ANDROID_LAB", "fix[ " + txt.getText() + " ] x:" +
  242. // iXY[IDX_X] + " y:" + iXY[IDX_Y] + " r2="
  243. // + iXY[IDX_DIS_Y]);
  244. // 第二次修正:修正y坐标
  245. int yDistance = iXY[IDX_Y] - yCenter;
  246. // 对于最靠近中心点的,其值不会大于yItem<br/>
  247. // 对于可以一路下降到中心点的,则该值也是其应调整的大小<br/>
  248. int yMove = Math.abs(yDistance);
  249. inner: for (int k = i - 1; k >= 0; k--) {
  250. int[] kXY = (int[]) listTxt.get(k).getTag();
  251. int startX = kXY[IDX_X];
  252. int endX = startX + kXY[IDX_TXT_LENGTH];
  253. // y轴以中心点为分隔线,在同一侧
  254. if (yDistance * (kXY[IDX_Y] - yCenter) > 0) {
  255. // Log.d("ANDROID_LAB", "compare:" +
  256. // listTxt.get(k).getText());
  257. if (isXMixed(startX, endX, iXY[IDX_X], iXY[IDX_X] + iXY[IDX_TXT_LENGTH])) {
  258. int tmpMove = Math.abs(iXY[IDX_Y] - kXY[IDX_Y]);
  259. if (tmpMove > yItem) {
  260. yMove = tmpMove;
  261. } else if (yMove > 0) {
  262. // 取消默认值。
  263. yMove = 0;
  264. }
  265. // Log.d("ANDROID_LAB", "break");
  266. break inner;
  267. }
  268. }
  269. }
  270. // Log.d("ANDROID_LAB", txt.getText() + " yMove=" + yMove);
  271. if (yMove > yItem) {
  272. int maxMove = yMove - yItem;
  273. int randomMove = random.nextInt(maxMove);
  274. int realMove = Math.max(randomMove, maxMove >> 1) * yDistance / Math.abs(yDistance);
  275. iXY[IDX_Y] = iXY[IDX_Y] - realMove;
  276. iXY[IDX_DIS_Y] = Math.abs(iXY[IDX_Y] - yCenter);
  277. // 已经调整过前i个需要再次排序
  278. sortXYList(listTxt, i + 1);
  279. }
  280. FrameLayout.LayoutParams layParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT,
  281. FrameLayout.LayoutParams.WRAP_CONTENT);
  282. layParams.gravity = Gravity.LEFT | Gravity.TOP;
  283. layParams.leftMargin = iXY[IDX_X];
  284. layParams.topMargin = iXY[IDX_Y];
  285. addView(txt, layParams);
  286. // 动画
  287. AnimationSet animSet = getAnimationSet(iXY, xCenter, yCenter, txtAnimInType);
  288. txt.startAnimation(animSet);
  289. }
  290. }
  291. public AnimationSet getAnimationSet(int[] xy, int xCenter, int yCenter, int type) {
  292. AnimationSet animSet = new AnimationSet(true);
  293. animSet.setInterpolator(interpolator);
  294. if (type == OUTSIDE_TO_LOCATION) {
  295. animSet.addAnimation(animAlpha2Opaque);
  296. animSet.addAnimation(animScaleLarge2Normal);
  297. TranslateAnimation translate = new TranslateAnimation(
  298. (xy[IDX_X] + (xy[IDX_TXT_LENGTH] >> 1) - xCenter) << 1, 0, (xy[IDX_Y] - yCenter) << 1, 0);
  299. animSet.addAnimation(translate);
  300. } else if (type == LOCATION_TO_OUTSIDE) {
  301. animSet.addAnimation(animAlpha2Transparent);
  302. animSet.addAnimation(animScaleNormal2Large);
  303. TranslateAnimation translate = new TranslateAnimation(0,
  304. (xy[IDX_X] + (xy[IDX_TXT_LENGTH] >> 1) - xCenter) << 1, 0, (xy[IDX_Y] - yCenter) << 1);
  305. animSet.addAnimation(translate);
  306. } else if (type == LOCATION_TO_CENTER) {
  307. animSet.addAnimation(animAlpha2Transparent);
  308. animSet.addAnimation(animScaleNormal2Zero);
  309. TranslateAnimation translate = new TranslateAnimation(0, (-xy[IDX_X] + xCenter), 0, (-xy[IDX_Y] + yCenter));
  310. animSet.addAnimation(translate);
  311. } else if (type == CENTER_TO_LOCATION) {
  312. animSet.addAnimation(animAlpha2Opaque);
  313. animSet.addAnimation(animScaleZero2Normal);
  314. TranslateAnimation translate = new TranslateAnimation((-xy[IDX_X] + xCenter), 0, (-xy[IDX_Y] + yCenter), 0);
  315. animSet.addAnimation(translate);
  316. }
  317. animSet.setDuration(animDuration);
  318. return animSet;
  319. }
  320. /**
  321. * 根据与中心点的距离由近到远进行冒泡排序。
  322. *
  323. * @param endIdx
  324. * 起始位置。
  325. * @param txtArr
  326. * 待排序的数组。
  327. *
  328. */
  329. private void sortXYList(LinkedList<TextView> listTxt, int endIdx) {
  330. for (int i = 0; i < endIdx; i++) {
  331. for (int k = i + 1; k < endIdx; k++) {
  332. if (((int[]) listTxt.get(k).getTag())[IDX_DIS_Y] < ((int[]) listTxt.get(i).getTag())[IDX_DIS_Y]) {
  333. TextView iTmp = listTxt.get(i);
  334. TextView kTmp = listTxt.get(k);
  335. listTxt.set(i, kTmp);
  336. listTxt.set(k, iTmp);
  337. }
  338. }
  339. }
  340. }
  341. /** A线段与B线段所代表的直线在X轴映射上是否有交集。 */
  342. private boolean isXMixed(int startA, int endA, int startB, int endB) {
  343. boolean result = false;
  344. if (startB >= startA && startB <= endA) {
  345. result = true;
  346. } else if (endB >= startA && endB <= endA) {
  347. result = true;
  348. } else if (startA >= startB && startA <= endB) {
  349. result = true;
  350. } else if (endA >= startB && endA <= endB) {
  351. result = true;
  352. }
  353. return result;
  354. }
  355. private int[] randomXY(Random ran, LinkedList<Integer> listX, LinkedList<Integer> listY, int xItem) {
  356. int[] arr = new int[4];
  357. arr[IDX_X] = listX.remove(ran.nextInt(listX.size()));
  358. arr[IDX_Y] = listY.remove(ran.nextInt(listY.size()));
  359. return arr;
  360. }
  361. public void onGlobalLayout() {
  362. int tmpW = getWidth();
  363. int tmpH = getHeight();
  364. if (width != tmpW || height != tmpH) {
  365. width = tmpW;
  366. height = tmpH;
  367. show();
  368. }
  369. }
  370. public Vector<String> getKeywords() {
  371. return vecKeywords;
  372. }
  373. public void rubKeywords() {
  374. vecKeywords.clear();
  375. }
  376. /** 直接清除所有的TextView。在清除之前不会显示动画。 */
  377. public void rubAllViews() {
  378. removeAllViews();
  379. }
  380. public void setOnItemClickListener(OnClickListener listener) {
  381. itemClickListener = listener;
  382. }
  383. // public void onDraw(Canvas canvas) {
  384. // super.onDraw(canvas);
  385. // Paint p = new Paint();
  386. // p.setColor(Color.BLACK);
  387. // canvas.drawCircle((width >> 1) - 2, (height >> 1) - 2, 4, p);
  388. // p.setColor(Color.RED);
  389. // }
  390. }

Android实现搜索关键字飞入飞出效果相关推荐

  1. Android搜索关键字飞入飞出效果(播放器的搜索界面)

    好多应用在搜索界面都有关键字飞入飞出的效果.我自己也实现了下.先上效果图: 实现该效果需要解决以下五点: 1.布局的选用. 2.确定动画区域,即布局的宽高. 3.对关键字坐标的随机分配. 4.对随机分 ...

  2. Android搜索关键字飞入飞出效果

    实现该效果需要解决以下五点: 1.布局的选用. 2.确定动画区域,即布局的宽高. 3.对关键字坐标的随机分配. 4.对随机分配的坐标进行向中心靠拢. 5.动画的实现. 本文内容归CSDN博客博主Sod ...

  3. Android中自定义控件之飞入飞出布局及随机布局实现方式

    本文主要介绍Android中飞入飞出布局及随机布局的实现方式,具体的效果如下: 实现飞入飞出效果 FlyLayout.java /*** Created by MG_ZXC on 2018/3/24. ...

  4. HTMO DOM部分---小练习;列表之间移动、日期选择、好友选中、滑动效果、滚动条效果、飞入飞出效果。...

    一:列表之间数据移动 第一个列表里面有内容,第二个里面没有 实现功能: 点击左侧列表选中一项内容,点击按钮,复制到右侧 点击复制所有按钮,将左侧列表所有数据,复制到右侧 扩展功能:右侧列表实现去重复 ...

  5. Android-搜索关键字飞入飞出动画

    博客说明:记录一写有用的代码 1.自定义控件类: import java.util.LinkedList; import java.util.Random; import java.util.Vect ...

  6. html字体飞入效果并定住,css3+vue彩色文字飞入飞出动画效果

    一款好看的css3+vue实现的彩色文字飞入飞出动画效果,点击文字飞出动画,然后又飞入回来,很棒的vue文字动画特效. 查看演示 下载资源: 39 次 下载资源 下载积分: 10 积分 js代码 Vu ...

  7. 自定义RecyclerView动画——实现remove飞出效果

    目录 前言 创建ItemAnimator 处理重叠 总结 源码 前言 我们经常会遇到在一个list中删除一条数据,这时候一般会有一个飞出的动画效果,如下图: 在RecyclerView中可以通过set ...

  8. Android 地图搜索商家,检索关键字(高德地图,百度地图),地址搜索

    1.高德地图 实现效果                                               打印数据             实现步骤: 1.首先去高德开放平台注册应用 高德开 ...

  9. 基于vue与element-ui写出的关于搜索框搜索关键字,下方关键字高亮的demo

    这是一个基于vue与element-ui写出的关于搜索框搜索关键字,下方关键字高亮的demo 希望对大家有所帮助 效果如下: <template><!-- 测试 -->< ...

最新文章

  1. linux avd 界面,Android Studio创建AVD
  2. Web安全CSRF攻击与防御
  3. java 打印机类printer_Spring案例打印机的实现过程详解
  4. html浮动垂直居中对齐,css如何设置垂直居中对齐?
  5. 主成分分析碎石图_因子分析碎石图
  6. windows聚焦壁纸不更新_如何解决Win10聚焦锁屏壁纸不自动更新的问题
  7. ArcGIS中ERROR 999999报错Configuration RasterCommander ImageServer can not be started解决
  8. java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id null at org.spr
  9. 认识和理解计算机语言,如何理解所有的编程语言和语言
  10. SublimeText3和EverEdit 的一些使用感受
  11. 从微信浏览器跳转到手机浏览器(ios和android)
  12. 笔记本合上盖子就断网怎么办?
  13. 计算机添加本地安全组用户名和密码错误,u租号总是密码错误-共享用户名和密码正确总提示错误...
  14. 项目总结-网络舆情分析
  15. Gopher China 2019 讲师专访-Grab/地图团队资深架构师胡泊
  16. sýnesis™ Lite for Snort provides basic analytics for Snort IDS/IPS alert logs using the Elastic Stac
  17. 大学c语言活动策划,大学校园文化班级趣味运动会活动方案
  18. 解决新版edge浏览器(109版本)无法正常使用
  19. 关于把war包放在Tomcat的webapps目录下 启动服务 前端访问报404
  20. 计算机基础教程张福炎pdf,《大学计算机信息技术教程》南京大学出版社(第4版第3次修订)张福炎 孙志挥编第3章 习题课...

热门文章

  1. 安徽文达信息工程学院计算机二级成绩查询,安徽文达信息工程学院教务网络管理系统成绩查询选课登录入口...
  2. 2021年中国数字营销十大趋势
  3. 通信历史和原理科普:电话和手机是如何打通的?
  4. SCU-4438 Censor(字符串哈希||KMP)
  5. 基于halcon与c#联合的视觉处理软件
  6. 无穷小在袁萌脑海中的第一次闪光
  7. RadioButtonList
  8. 初学python第二天
  9. iode、iodc、aode、aodc的区别与联系
  10. Go汇编语法和MatrixOne使用介绍