您的位置:首页 > 移动开发 > Android开发

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

2015-05-07 17:37 316 查看


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

转载请声明出处/article/7618171.html

标签云就是在搜索的时候给用户提供推荐的一些关键字。这个水波纹的标签云是我在实习期间写的...当时没用上,发出来记录一下。先看一下效果图:



可以上下循环滚动,触碰时随手指滑动而滚动,点击界面会暂停2秒钟。

这个标签云的实现思路是定义一个Layout,动态的管理左中右三列List数据的位置,左右两边的List运动轨迹是对称的抛物线,中间的List走直线。由于需要响应每个Tag的点击事件,所以这里用的是一个TextView的List。Tag的透明度Alpha和Size距离中间越近就越大,如果选用线性渐变的话会在中间处出现剧变,所以渐变曲线选用抛物线,这样就可以很平滑的变化了,这个抛物线渐变和上一篇文章滚动选择器PickerView的text渐变是一样的。了解了这些就可以看代码了:

FlowLayout.java:

[java] view
plaincopy

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的布局:

[html] view
plaincopy

<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的代码:

[java] view
plaincopy

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了,很简单的吧?

源码下载
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: