现在的位置: 首页 > 综合 > 正文

Android自定义控件实战——水波纹标签云TagCloud

2018年05月28日 ⁄ 综合 ⁄ 共 9717字 ⁄ 字号 评论关闭

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

源码下载


抱歉!评论已关闭.