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

 

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



下面各個擊破:

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);
當監聽事件被觸發時,即可獲取而已的寬高。
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軸座標的修正為如下:
// 獲取文本長度
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軸上應該朝中心點靠攏。實現代碼如下:
// 第二次修正:修正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上。動畫的實現如下:
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
 
 
創作者介紹
創作者 shadow 的頭像
shadow

資訊園

shadow 發表在 痞客邦 留言(0) 人氣()