转载请声明出处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"MainActivity的代码:
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>
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了,很简单的吧?