好久没发东西了,快三个月了。忙,无他尔。接下来进入正题。
好多应用在搜索界面都有关键字飞入飞出的效果。我自己也实现了下。先上效果图:
实现该效果需要解决以下五点:
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;
-
- 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轴上应该朝中心点靠拢。实现代码如下:
-
- int yDistance = iXY[IDX_Y] - yCenter;
-
-
- 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];
-
- if (yDistance * (kXY[IDX_Y] - yCenter) > 0) {
-
-
- 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;
- }
-
- break inner;
- }
- }
- }
-
- 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);
-
- 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
否则将会提示无法找到该类。
好了,文嗦嗦的东西到此结束,贴上Java代码如下,xml代码请根据效果图自己鼓捣吧。
- 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;
-
-
-
-
-
- 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();
-
- feedKeywordsFlow(keywordsFlow, keywords);
- keywordsFlow.go2Show(KeywordsFlow.ANIMATION_IN);
- } else if (v == btnOut) {
- keywordsFlow.rubKeywords();
-
- 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);
- }
- }
- }
- KeywordsFlow.java
-
- package lab.sodino.searchkeywordanim;
-
- import java.util.LinkedList;
- import java.util.Random;
- import java.util.Vector;
-
- import android.content.Context;
- import android.graphics.Paint;
- import android.util.AttributeSet;
- import android.util.TypedValue;
- import android.view.Gravity;
- import android.view.View;
- import android.view.ViewTreeObserver.OnGlobalLayoutListener;
- import android.view.animation.AlphaAnimation;
- import android.view.animation.Animation;
- import android.view.animation.Animation.AnimationListener;
- import android.view.animation.AnimationSet;
- import android.view.animation.AnimationUtils;
- import android.view.animation.Interpolator;
- import android.view.animation.ScaleAnimation;
- import android.view.animation.TranslateAnimation;
- import android.widget.FrameLayout;
- import android.widget.TextView;
-
-
-
-
-
-
-
-
- public class KeywordsFlow extends FrameLayout implements OnGlobalLayoutListener {
- public static final int IDX_X = 0;
- public static final int IDX_Y = 1;
- public static final int IDX_TXT_LENGTH = 2;
- public static final int IDX_DIS_Y = 3;
-
- public static final int ANIMATION_IN = 1;
-
- public static final int ANIMATION_OUT = 2;
-
- public static final int OUTSIDE_TO_LOCATION = 1;
-
- public static final int LOCATION_TO_OUTSIDE = 2;
-
- public static final int CENTER_TO_LOCATION = 3;
-
- public static final int LOCATION_TO_CENTER = 4;
- public static final long ANIM_DURATION = 800l;
- public static final int MAX = 10;
- public static final int TEXT_SIZE_MAX = 25;
- public static final int TEXT_SIZE_MIN = 15;
- private OnClickListener itemClickListener;
- private static Interpolator interpolator;
- private static AlphaAnimation animAlpha2Opaque;
- private static AlphaAnimation animAlpha2Transparent;
- private static ScaleAnimation animScaleLarge2Normal, animScaleNormal2Large, animScaleZero2Normal,
- animScaleNormal2Zero;
-
- private Vector<String> vecKeywords;
- private int width, height;
-
-
-
-
-
-
- private boolean enableShow;
- private Random random;
-
-
-
-
-
-
-
-
- private int txtAnimInType, txtAnimOutType;
-
- private long lastStartAnimationTime;
-
- private long animDuration;
-
- public KeywordsFlow(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- init();
- }
-
- public KeywordsFlow(Context context, AttributeSet attrs) {
- super(context, attrs);
- init();
- }
-
- public KeywordsFlow(Context context) {
- super(context);
- init();
- }
-
- private void init() {
- lastStartAnimationTime = 0l;
- animDuration = ANIM_DURATION;
- random = new Random();
- vecKeywords = new Vector<String>(MAX);
- getViewTreeObserver().addOnGlobalLayoutListener(this);
- interpolator = AnimationUtils.loadInterpolator(getContext(), android.R.anim.decelerate_interpolator);
- animAlpha2Opaque = new AlphaAnimation(0.0f, 1.0f);
- animAlpha2Transparent = new AlphaAnimation(1.0f, 0.0f);
- animScaleLarge2Normal = new ScaleAnimation(2, 1, 2, 1);
- animScaleNormal2Large = new ScaleAnimation(1, 2, 1, 2);
- animScaleZero2Normal = new ScaleAnimation(0, 1, 0, 1);
- animScaleNormal2Zero = new ScaleAnimation(1, 0, 1, 0);
- }
-
- public long getDuration() {
- return animDuration;
- }
-
- public void setDuration(long duration) {
- animDuration = duration;
- }
-
- public boolean feedKeyword(String keyword) {
- boolean result = false;
- if (vecKeywords.size() < MAX) {
- result = vecKeywords.add(keyword);
- }
- return result;
- }
-
-
-
-
-
-
-
-
-
- public boolean go2Show(int animType) {
- if (System.currentTimeMillis() - lastStartAnimationTime > animDuration) {
- enableShow = true;
- if (animType == ANIMATION_IN) {
- txtAnimInType = OUTSIDE_TO_LOCATION;
- txtAnimOutType = LOCATION_TO_CENTER;
- } else if (animType == ANIMATION_OUT) {
- txtAnimInType = CENTER_TO_LOCATION;
- txtAnimOutType = LOCATION_TO_OUTSIDE;
- }
- disapper();
- boolean result = show();
- return result;
- }
- return false;
- }
-
- private void disapper() {
- int size = getChildCount();
- for (int i = size - 1; i >= 0; i--) {
- final TextView txt = (TextView) getChildAt(i);
- if (txt.getVisibility() == View.GONE) {
- removeView(txt);
- continue;
- }
- FrameLayout.LayoutParams layParams = (LayoutParams) txt.getLayoutParams();
-
-
-
- int[] xy = new int[] { layParams.leftMargin, layParams.topMargin, txt.getWidth() };
- AnimationSet animSet = getAnimationSet(xy, (width >> 1), (height >> 1), txtAnimOutType);
- txt.startAnimation(animSet);
- animSet.setAnimationListener(new AnimationListener() {
- public void onAnimationStart(Animation animation) {
- }
-
- public void onAnimationRepeat(Animation animation) {
- }
-
- public void onAnimationEnd(Animation animation) {
- txt.setOnClickListener(null);
- txt.setClickable(false);
- txt.setVisibility(View.GONE);
- }
- });
- }
- }
-
- private boolean show() {
- if (width > 0 && height > 0 && vecKeywords != null && vecKeywords.size() > 0 && enableShow) {
- enableShow = false;
- lastStartAnimationTime = System.currentTimeMillis();
- int xCenter = width >> 1, yCenter = height >> 1;
- int size = vecKeywords.size();
- int xItem = width / size, yItem = height / size;
-
-
-
- LinkedList<Integer> listX = new LinkedList<Integer>(), listY = new LinkedList<Integer>();
- for (int i = 0; i < size; i++) {
-
- listX.add(i * xItem);
- listY.add(i * yItem + (yItem >> 2));
- }
-
- LinkedList<TextView> listTxtTop = new LinkedList<TextView>();
- LinkedList<TextView> listTxtBottom = new LinkedList<TextView>();
- for (int i = 0; i < size; i++) {
- String keyword = vecKeywords.get(i);
-
- int ranColor = 0xff000000 | random.nextInt(0x0077ffff);
-
- int xy[] = randomXY(random, listX, listY, xItem);
-
- int txtSize = TEXT_SIZE_MIN + random.nextInt(TEXT_SIZE_MAX - TEXT_SIZE_MIN + 1);
-
- final TextView txt = new TextView(getContext());
- txt.setOnClickListener(itemClickListener);
- txt.setText(keyword);
- txt.setTextColor(ranColor);
- txt.setTextSize(TypedValue.COMPLEX_UNIT_SP, txtSize);
- txt.setShadowLayer(2, 2, 2, 0xff696969);
- txt.setGravity(Gravity.CENTER);
-
- Paint paint = txt.getPaint();
- int strWidth = (int) Math.ceil(paint.measureText(keyword));
- xy[IDX_TXT_LENGTH] = strWidth;
-
- 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);
- }
- xy[IDX_DIS_Y] = Math.abs(xy[IDX_Y] - yCenter);
- txt.setTag(xy);
- if (xy[IDX_Y] > yCenter) {
- listTxtBottom.add(txt);
- } else {
- listTxtTop.add(txt);
- }
- }
- attach2Screen(listTxtTop, xCenter, yCenter, yItem);
- attach2Screen(listTxtBottom, xCenter, yCenter, yItem);
- return true;
- }
- return false;
- }
-
-
- private void attach2Screen(LinkedList<TextView> listTxt, int xCenter, int yCenter, int yItem) {
- int size = listTxt.size();
- sortXYList(listTxt, size);
- for (int i = 0; i < size; i++) {
- TextView txt = listTxt.get(i);
- int[] iXY = (int[]) txt.getTag();
-
-
-
-
- int yDistance = iXY[IDX_Y] - yCenter;
-
-
- 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];
-
- if (yDistance * (kXY[IDX_Y] - yCenter) > 0) {
-
-
- 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;
- }
-
- break inner;
- }
- }
- }
-
- 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);
-
- sortXYList(listTxt, i + 1);
- }
- FrameLayout.LayoutParams layParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT,
- FrameLayout.LayoutParams.WRAP_CONTENT);
- layParams.gravity = Gravity.LEFT | Gravity.TOP;
- layParams.leftMargin = iXY[IDX_X];
- layParams.topMargin = iXY[IDX_Y];
- addView(txt, layParams);
-
- AnimationSet animSet = getAnimationSet(iXY, xCenter, yCenter, txtAnimInType);
- txt.startAnimation(animSet);
- }
- }
-
- 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;
- }
-
-
-
-
-
-
-
-
-
-
- private void sortXYList(LinkedList<TextView> listTxt, int endIdx) {
- for (int i = 0; i < endIdx; i++) {
- for (int k = i + 1; k < endIdx; k++) {
- if (((int