閱讀163 返回首頁    go 技術社區[雲棲]


Android實現搜索關鍵字飛入飛出效果

效果圖:



實現該效果需要解決以下五點:

1.布局的選用。
2.確定動畫區域,即布局的寬高。
3.對關鍵字坐標的隨機分配。
4.對隨機分配的坐標進行向中心靠攏。
5.動畫的實現。

本文內容歸CSDN博客博主Sodino 所有
轉載請注明出處:https://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);
當監聽事件被觸發時,即可獲取而已的寬高。

  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軸坐標的修正為如下:

  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軸上應該朝中心點靠攏。實現代碼如下:
  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上。動畫的實現如下:
  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代碼請根據效果圖自己鼓搗吧。

  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("https://www.google.com.hk/#q=" + keyword));
  69. startActivity(intent);
  70. }
  71. }
  72. }
ActKeywordAnim.java package 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); } } @Override public 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("https://www.google.com.hk/#q=" + keyword)); startActivity(intent); } } }

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

最後更新:2017-04-02 18:44:46

  上一篇:go Android getDecorView用途——屏幕截圖
  下一篇:go 11個重要的數據庫設計規則