转载请声明出处http://blog.csdn.net/zhongkejingwang/article/details/38534853
标签云就是在搜索的时候给用户提供推荐的一些关键字。这个水波纹的标签云是我在实习期间写的...当时没用上,发出来记录一下。先看一下效果图:
可以上下循环滚动,触碰时随手指滑动而滚动,点击界面会暂停2秒钟。
这个标签云的实现思路是定义一个Layout,动态的管理左中右三列List数据的位置,左右两边的List运动轨迹是对称的抛物线,中间的List走直线。由于需要响应每个Tag的点击事件,所以这里用的是一个TextView的List。Tag的透明度Alpha和Size距离中间越近就越大,如果选用线性渐变的话会在中间处出现剧变,所以渐变曲线选用抛物线,这样就可以很平滑的变化了,这个抛物线渐变和上一篇文章滚动选择器PickerView的text渐变是一样的。了解了这些就可以看代码了:
FlowLayout.java:
package com.jingchen.tagclouddemo; import java.util.ArrayList; import java.util.List; import java.util.Timer; import java.util.TimerTask; import android.annotation.SuppressLint; import android.content.Context; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; /** * 由于用到了Android3.0的API,所以只能在3.0及以上版本编译通过 * * @author chenjing * */ public class FlowLayout extends RelativeLayout implements OnClickListener { private float minTextSize = 18; private float maxTextSize = 25; private float minAlpha = 0.2f; private float maxAlpha = 1f; private int mWidth, mHeight; private int textMargin = (int) (4.5 * minTextSize); /** * list的head数据的Y值 */ private int firstTextY; private boolean isInit = true; /** * 滚动速度 */ private int moveSpeed = 2; /** * 抛物线顶点到零点高度占View总长度的比值 */ private float scaleArcTopPoint = 3; /** * 是否自动滚动 */ private boolean isAutoMove = true; private float lastX, lastY; private boolean isClick = false; Timer timer; MyTimerTask mTask; // 左中右三列数据 List<TextView> mTextViews, leftTextViews, rightTextViews; private Context mContext; public FlowLayout(Context context) { super(context); init(context); } public FlowLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } public FlowLayout(Context context, AttributeSet attrs) { super(context, attrs); init(context); } private void init(Context context) { timer = new Timer(); mTask = new MyTimerTask(handler); mTextViews = new ArrayList<TextView>(); leftTextViews = new ArrayList<TextView>(); rightTextViews = new ArrayList<TextView>(); mContext = context; } public void addText(String text) { TextView tv = createTextView(text); mTextViews.add(tv); addView(tv); } public void addLeftText(String text) { TextView tv = createTextView(text); leftTextViews.add(tv); addView(tv); } public void addRightText(String text) { TextView tv = createTextView(text); rightTextViews.add(tv); addView(tv); } private TextView createTextView(String text) { TextView tv = new TextView(mContext); tv.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); tv.setText(text); tv.setTextColor(getResources().getColor(R.color.white)); tv.setTextSize(minTextSize); tv.setGravity(Gravity.CENTER); tv.setOnClickListener(this); // 给TextView设置OnTouchListener为了防止手指落点在TextView上时父控件无法响应事件,onTouch的内容和onTouchEvent的内容差不多 tv.setOnTouchListener(touchListener); return tv; } private OnTouchListener touchListener = new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { float x = event.getX(); float y = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: isClick = true; lastX = event.getX(); lastY = event.getY(); stop(); isAutoMove = false; break; case MotionEvent.ACTION_MOVE: float length = (float) Math.sqrt(Math.pow(x - lastX, 2) + Math.pow(y - lastY, 2)); if (length > 10) isClick = false; float y_length = event.getY() - lastY; if (y_length < 0) { isMoveUp = true; moveUp((int) -y_length); } else if (canDown && y_length > 0) { isMoveUp = false; moveDown((int) y_length); } lastX = event.getX(); lastY = event.getY(); break; case MotionEvent.ACTION_UP: if (isClick) v.performClick(); else { start(); } isAutoMove = true; break; } return true; } }; public void start() { if (mTask != null) { mTask.cancel(); mTask = null; } mTask = new MyTimerTask(handler); timer.schedule(mTask, 0, 10); } public void stop() { if (mTask != null) { mTask.cancel(); mTask = null; } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int counts = getChildCount(); // 测量子控件 for (int i = 0; i < counts; i++) { View view = getChildAt(i); measureChild(view, widthMeasureSpec, heightMeasureSpec); } setMeasuredDimension(widthMeasureSpec, heightMeasureSpec); } @Override public void dispatchWindowFocusChanged(boolean hasFocus) { super.dispatchWindowFocusChanged(hasFocus); if (isInit) { mHeight = getHeight(); mWidth = getWidth(); // 从底部开始往上滚动 firstTextY = mHeight; isInit = false; start(); } } private void moveUp(int speed) { firstTextY -= speed; if (firstTextY < -textMargin) { // list的head数据被隐藏了,将head放置到尾部,可以循环滚动了 canDown = true; firstTextY = mTextViews.get(1).getTop(); TextView tv = mTextViews.get(0); mTextViews.remove(0); mTextViews.add(tv); tv = leftTextViews.get(0); leftTextViews.remove(0); leftTextViews.add(tv); tv = rightTextViews.get(0); rightTextViews.remove(0); rightTextViews.add(tv); } FlowLayout.this.requestLayout(); } private void moveDown(int speed) { firstTextY += speed; if (firstTextY > textMargin) { firstTextY = -textMargin; TextView tv = mTextViews.get(mTextViews.size() - 1); mTextViews.remove(mTextViews.size() - 1); mTextViews.add(0, tv); tv = leftTextViews.get(leftTextViews.size() - 1); leftTextViews.remove(leftTextViews.size() - 1); leftTextViews.add(0, tv); tv = rightTextViews.get(rightTextViews.size() - 1); rightTextViews.remove(rightTextViews.size() - 1); rightTextViews.add(0, tv); } FlowLayout.this.requestLayout(); } @SuppressLint("HandlerLeak") Handler handler = new Handler() { @Override public void handleMessage(Message msg) { synchronized (FlowLayout.this) { if (isAutoMove) { if (isMoveUp) moveUp(moveSpeed); else moveDown(moveSpeed); } } } }; class MyTimerTask extends TimerTask { Handler Taskhandler; public MyTimerTask(Handler handler) { Taskhandler = handler; } @Override public void run() { Taskhandler.sendMessage(Taskhandler.obtainMessage()); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (!isInit) { layoutAll(mTextViews, 0); int temp = firstTextY; firstTextY += textMargin; layoutAll(leftTextViews, -1); layoutAll(rightTextViews, 1); firstTextY = temp; } } private boolean isMoveUp = true; private boolean canDown = false; @Override public boolean onTouchEvent(MotionEvent event) { float x = event.getX(); float y = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: isClick = true; lastX = event.getX(); lastY = event.getY(); // 手按下后停止自由滚动,随手的滑动而滚动 stop(); isAutoMove = false; break; case MotionEvent.ACTION_MOVE: float length = (float) Math.sqrt(Math.pow(x - lastX, 2) + Math.pow(y - lastY, 2)); if (length > 10) isClick = false; float y_length = event.getY() - lastY; // 手动move if (y_length < 0) { isMoveUp = true; moveUp((int) -y_length); } else if (canDown && y_length > 0) { isMoveUp = false; moveDown((int) y_length); } lastX = event.getX(); lastY = event.getY(); break; case MotionEvent.ACTION_UP: if (isClick) { // 点击View后停2秒再开始 stop(); delayStartHandler.sendEmptyMessageDelayed(0, 2000); } else { start(); } isAutoMove = true; break; } return true; } /** * @param textViews * @param type * -1,0,1分别代表左中右list */ private void layoutAll(List<TextView> textViews, int type) { int temp_y = firstTextY; for (int i = 0; i < textViews.size(); i++) { TextView temp = textViews.get(i); // 根据y值计算x坐标上的偏移量,type为-1时往左偏,抛物线开口向右,0的时候走直线,1的时候和-1对称 int detaX = type * (int) (-mWidth * 4 / scaleArcTopPoint / Math.pow(mHeight, 2) * Math.pow(mHeight / 2.0 - temp_y, 2) + mWidth / scaleArcTopPoint); float scale = (float) (1 - 4 * Math.pow(mHeight / 2.0 - temp_y, 2) / Math.pow(mHeight, 2)); if (scale < 0) scale = 0; float textScale = (float) ((minTextSize + scale * (maxTextSize - minTextSize)) * 1.0 / minTextSize); temp.setScaleX(textScale); temp.setScaleY(textScale); temp.setAlpha(minAlpha + scale * (maxAlpha - minAlpha)); temp.layout((mWidth - temp.getMeasuredWidth()) / 2 + detaX, temp_y, (mWidth + temp.getMeasuredWidth()) / 2 + detaX, temp_y + temp.getMeasuredHeight()); temp_y += 2 * textMargin; } } Handler delayStartHandler = new Handler() { @Override public void handleMessage(Message msg) { start(); } }; @Override public void onClick(View v) { TextView tv = (TextView) v; Toast.makeText(mContext, tv.getText(), Toast.LENGTH_SHORT).show(); stop(); delayStartHandler.sendEmptyMessageDelayed(0, 2000); } }
代码中改变TextView的Size时用到了setScaleX和setScaleY这两个方法,Android3.0才有的,所以只能是3.0的平台编译,如果不这样改变Size而是直接setTextSize的话变化就没有那么流畅。每个TextView的X轴偏移量都是根据其Y轴位置偏离中心线的距离计算的。代码中已经有了相关注释,不是很难。
MainActivity的布局:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <com.jingchen.tagclouddemo.FlowLayout android:id="@+id/tagcloudview" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#000000" /> </RelativeLayout>MainActivity的代码:
package com.jingchen.tagclouddemo; import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.os.Bundle; /** * @author chenjing * */ public class MainActivity extends Activity { FlowLayout layout; int i = 0; List<String> textList; List<String> leftextList; List<String> righttextList; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); layout = (FlowLayout) findViewById(R.id.tagcloudview); layout.setBackgroundResource(R.color.black); textList = new ArrayList<String>(); leftextList = new ArrayList<String>(); righttextList = new ArrayList<String>(); addTexts(); for (int i = 0; i < textList.size(); i++) { layout.addText(textList.get(i)); } for (int i = 0; i < leftextList.size(); i++) { layout.addLeftText(leftextList.get(i)); } for (int i = 0; i < righttextList.size(); i++) { layout.addRightText(righttextList.get(i)); } } private void addTexts() { textList.add("一路狂奔"); textList.add("后宫:帝王之妻"); textList.add("宝贝和我"); textList.add("甜心巧克力"); textList.add("恐怖故事"); textList.add("百万爱情宝贝"); textList.add("别跟我谈高富帅"); textList.add("甜蜜十八岁"); textList.add("终结者"); leftextList.add("百万爱情宝贝"); leftextList.add("别跟我谈高富帅"); leftextList.add("甜蜜十八岁"); leftextList.add("金钱的味道"); leftextList.add("艳遇"); leftextList.add("痛症"); leftextList.add("危险关系"); leftextList.add("夺宝联盟"); leftextList.add("101次求婚"); leftextList.add("富春山居图"); righttextList.add("艳遇"); righttextList.add("痛症"); righttextList.add("危险关系"); righttextList.add("今天"); righttextList.add("小时代"); righttextList.add("致我们将死的青春"); righttextList.add("金钱的味道"); righttextList.add("101次求婚"); } @Override protected void onDestroy() { layout.stop(); super.onDestroy(); } }
在MainActivity中set三列数据后就可以start了,很简单的吧?
源码下载