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

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

   转载请声明出处http://blog.csdn.net/zhongkejingwang/article/details/38534853

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


Android自定义控件实战——水波纹标签云TagCloud_第1张图片

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

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

FlowLayout.java:

[java]  view plain copy
  1. package com.jingchen.tagclouddemo;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5. import java.util.Timer;  
  6. import java.util.TimerTask;  
  7.   
  8. import android.annotation.SuppressLint;  
  9. import android.content.Context;  
  10. import android.os.Handler;  
  11. import android.os.Message;  
  12. import android.util.AttributeSet;  
  13. import android.view.Gravity;  
  14. import android.view.MotionEvent;  
  15. import android.view.View;  
  16. import android.view.View.OnClickListener;  
  17. import android.widget.RelativeLayout;  
  18. import android.widget.TextView;  
  19. import android.widget.Toast;  
  20.   
  21. /** 
  22.  * 由于用到了Android3.0的API,所以只能在3.0及以上版本编译通过 
  23.  *  
  24.  * @author chenjing 
  25.  *  
  26.  */  
  27. public class FlowLayout extends RelativeLayout implements OnClickListener  
  28. {  
  29.     private float minTextSize = 18;  
  30.     private float maxTextSize = 25;  
  31.   
  32.     private float minAlpha = 0.2f;  
  33.     private float maxAlpha = 1f;  
  34.   
  35.     private int mWidth, mHeight;  
  36.     private int textMargin = (int) (4.5 * minTextSize);  
  37.     /** 
  38.      * list的head数据的Y值 
  39.      */  
  40.     private int firstTextY;  
  41.     private boolean isInit = true;  
  42.     /** 
  43.      * 滚动速度 
  44.      */  
  45.     private int moveSpeed = 2;  
  46.     /** 
  47.      * 抛物线顶点到零点高度占View总长度的比值 
  48.      */  
  49.     private float scaleArcTopPoint = 3;  
  50.     /** 
  51.      * 是否自动滚动 
  52.      */  
  53.     private boolean isAutoMove = true;  
  54.     private float lastX, lastY;  
  55.     private boolean isClick = false;  
  56.     Timer timer;  
  57.     MyTimerTask mTask;  
  58.     // 左中右三列数据  
  59.     List<TextView> mTextViews, leftTextViews, rightTextViews;  
  60.     private Context mContext;  
  61.   
  62.     public FlowLayout(Context context)  
  63.     {  
  64.         super(context);  
  65.         init(context);  
  66.     }  
  67.   
  68.     public FlowLayout(Context context, AttributeSet attrs, int defStyle)  
  69.     {  
  70.         super(context, attrs, defStyle);  
  71.         init(context);  
  72.     }  
  73.   
  74.     public FlowLayout(Context context, AttributeSet attrs)  
  75.     {  
  76.         super(context, attrs);  
  77.         init(context);  
  78.     }  
  79.   
  80.     private void init(Context context)  
  81.     {  
  82.         timer = new Timer();  
  83.         mTask = new MyTimerTask(handler);  
  84.         mTextViews = new ArrayList<TextView>();  
  85.         leftTextViews = new ArrayList<TextView>();  
  86.         rightTextViews = new ArrayList<TextView>();  
  87.         mContext = context;  
  88.     }  
  89.   
  90.     public void addText(String text)  
  91.     {  
  92.         TextView tv = createTextView(text);  
  93.         mTextViews.add(tv);  
  94.         addView(tv);  
  95.     }  
  96.   
  97.     public void addLeftText(String text)  
  98.     {  
  99.         TextView tv = createTextView(text);  
  100.         leftTextViews.add(tv);  
  101.         addView(tv);  
  102.     }  
  103.   
  104.     public void addRightText(String text)  
  105.     {  
  106.         TextView tv = createTextView(text);  
  107.         rightTextViews.add(tv);  
  108.         addView(tv);  
  109.     }  
  110.   
  111.     private TextView createTextView(String text)  
  112.     {  
  113.         TextView tv = new TextView(mContext);  
  114.         tv.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,  
  115.                 LayoutParams.WRAP_CONTENT));  
  116.         tv.setText(text);  
  117.         tv.setTextColor(getResources().getColor(R.color.white));  
  118.         tv.setTextSize(minTextSize);  
  119.         tv.setGravity(Gravity.CENTER);  
  120.         tv.setOnClickListener(this);  
  121.         // 给TextView设置OnTouchListener为了防止手指落点在TextView上时父控件无法响应事件,onTouch的内容和onTouchEvent的内容差不多  
  122.         tv.setOnTouchListener(touchListener);  
  123.         return tv;  
  124.     }  
  125.   
  126.     private OnTouchListener touchListener = new OnTouchListener()  
  127.     {  
  128.   
  129.         @Override  
  130.         public boolean onTouch(View v, MotionEvent event)  
  131.         {  
  132.             float x = event.getX();  
  133.             float y = event.getY();  
  134.             switch (event.getAction())  
  135.             {  
  136.             case MotionEvent.ACTION_DOWN:  
  137.                 isClick = true;  
  138.                 lastX = event.getX();  
  139.                 lastY = event.getY();  
  140.                 stop();  
  141.                 isAutoMove = false;  
  142.                 break;  
  143.             case MotionEvent.ACTION_MOVE:  
  144.                 float length = (float) Math.sqrt(Math.pow(x - lastX, 2)  
  145.                         + Math.pow(y - lastY, 2));  
  146.                 if (length > 10)  
  147.                     isClick = false;  
  148.                 float y_length = event.getY() - lastY;  
  149.                 if (y_length < 0)  
  150.                 {  
  151.                     isMoveUp = true;  
  152.                     moveUp((int) -y_length);  
  153.                 } else if (canDown && y_length > 0)  
  154.                 {  
  155.                     isMoveUp = false;  
  156.                     moveDown((int) y_length);  
  157.                 }  
  158.                 lastX = event.getX();  
  159.                 lastY = event.getY();  
  160.                 break;  
  161.   
  162.             case MotionEvent.ACTION_UP:  
  163.                 if (isClick)  
  164.                     v.performClick();  
  165.                 else  
  166.                 {  
  167.                     start();  
  168.                 }  
  169.                 isAutoMove = true;  
  170.                 break;  
  171.   
  172.             }  
  173.             return true;  
  174.         }  
  175.     };  
  176.   
  177.     public void start()  
  178.     {  
  179.         if (mTask != null)  
  180.         {  
  181.             mTask.cancel();  
  182.             mTask = null;  
  183.         }  
  184.         mTask = new MyTimerTask(handler);  
  185.         timer.schedule(mTask, 010);  
  186.     }  
  187.   
  188.     public void stop()  
  189.     {  
  190.         if (mTask != null)  
  191.         {  
  192.             mTask.cancel();  
  193.             mTask = null;  
  194.         }  
  195.     }  
  196.   
  197.     @Override  
  198.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)  
  199.     {  
  200.         int counts = getChildCount();  
  201.         // 测量子控件  
  202.         for (int i = 0; i < counts; i++)  
  203.         {  
  204.             View view = getChildAt(i);  
  205.             measureChild(view, widthMeasureSpec, heightMeasureSpec);  
  206.         }  
  207.         setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);  
  208.     }  
  209.   
  210.     @Override  
  211.     public void dispatchWindowFocusChanged(boolean hasFocus)  
  212.     {  
  213.         super.dispatchWindowFocusChanged(hasFocus);  
  214.         if (isInit)  
  215.         {  
  216.             mHeight = getHeight();  
  217.             mWidth = getWidth();  
  218.             // 从底部开始往上滚动  
  219.             firstTextY = mHeight;  
  220.             isInit = false;  
  221.             start();  
  222.         }  
  223.     }  
  224.   
  225.     private void moveUp(int speed)  
  226.     {  
  227.         firstTextY -= speed;  
  228.         if (firstTextY < -textMargin)  
  229.         {  
  230.             // list的head数据被隐藏了,将head放置到尾部,可以循环滚动了  
  231.             canDown = true;  
  232.             firstTextY = mTextViews.get(1).getTop();  
  233.             TextView tv = mTextViews.get(0);  
  234.             mTextViews.remove(0);  
  235.             mTextViews.add(tv);  
  236.             tv = leftTextViews.get(0);  
  237.             leftTextViews.remove(0);  
  238.             leftTextViews.add(tv);  
  239.             tv = rightTextViews.get(0);  
  240.             rightTextViews.remove(0);  
  241.             rightTextViews.add(tv);  
  242.         }  
  243.         FlowLayout.this.requestLayout();  
  244.     }  
  245.   
  246.     private void moveDown(int speed)  
  247.     {  
  248.         firstTextY += speed;  
  249.         if (firstTextY > textMargin)  
  250.         {  
  251.             firstTextY = -textMargin;  
  252.             TextView tv = mTextViews.get(mTextViews.size() - 1);  
  253.             mTextViews.remove(mTextViews.size() - 1);  
  254.             mTextViews.add(0, tv);  
  255.             tv = leftTextViews.get(leftTextViews.size() - 1);  
  256.             leftTextViews.remove(leftTextViews.size() - 1);  
  257.             leftTextViews.add(0, tv);  
  258.             tv = rightTextViews.get(rightTextViews.size() - 1);  
  259.             rightTextViews.remove(rightTextViews.size() - 1);  
  260.             rightTextViews.add(0, tv);  
  261.         }  
  262.         FlowLayout.this.requestLayout();  
  263.     }  
  264.   
  265.     @SuppressLint("HandlerLeak")  
  266.     Handler handler = new Handler()  
  267.     {  
  268.   
  269.         @Override  
  270.         public void handleMessage(Message msg)  
  271.         {  
  272.             synchronized (FlowLayout.this)  
  273.             {  
  274.                 if (isAutoMove)  
  275.                 {  
  276.                     if (isMoveUp)  
  277.                         moveUp(moveSpeed);  
  278.                     else  
  279.                         moveDown(moveSpeed);  
  280.                 }  
  281.             }  
  282.         }  
  283.   
  284.     };  
  285.   
  286.     class MyTimerTask extends TimerTask  
  287.     {  
  288.         Handler Taskhandler;  
  289.   
  290.         public MyTimerTask(Handler handler)  
  291.         {  
  292.             Taskhandler = handler;  
  293.         }  
  294.   
  295.         @Override  
  296.         public void run()  
  297.         {  
  298.             Taskhandler.sendMessage(Taskhandler.obtainMessage());  
  299.         }  
  300.   
  301.     }  
  302.   
  303.     @Override  
  304.     protected void onLayout(boolean changed, int l, int t, int r, int b)  
  305.     {  
  306.         if (!isInit)  
  307.         {  
  308.             layoutAll(mTextViews, 0);  
  309.             int temp = firstTextY;  
  310.             firstTextY += textMargin;  
  311.             layoutAll(leftTextViews, -1);  
  312.             layoutAll(rightTextViews, 1);  
  313.             firstTextY = temp;  
  314.         }  
  315.     }  
  316.   
  317.     private boolean isMoveUp = true;  
  318.     private boolean canDown = false;  
  319.   
  320.     @Override  
  321.     public boolean onTouchEvent(MotionEvent event)  
  322.     {  
  323.         float x = event.getX();  
  324.         float y = event.getY();  
  325.         switch (event.getAction())  
  326.         {  
  327.         case MotionEvent.ACTION_DOWN:  
  328.             isClick = true;  
  329.             lastX = event.getX();  
  330.             lastY = event.getY();  
  331.             // 手按下后停止自由滚动,随手的滑动而滚动  
  332.             stop();  
  333.             isAutoMove = false;  
  334.             break;  
  335.         case MotionEvent.ACTION_MOVE:  
  336.             float length = (float) Math.sqrt(Math.pow(x - lastX, 2)  
  337.                     + Math.pow(y - lastY, 2));  
  338.             if (length > 10)  
  339.                 isClick = false;  
  340.             float y_length = event.getY() - lastY;  
  341.             // 手动move  
  342.             if (y_length < 0)  
  343.             {  
  344.                 isMoveUp = true;  
  345.                 moveUp((int) -y_length);  
  346.             } else if (canDown && y_length > 0)  
  347.             {  
  348.                 isMoveUp = false;  
  349.                 moveDown((int) y_length);  
  350.             }  
  351.             lastX = event.getX();  
  352.             lastY = event.getY();  
  353.             break;  
  354.   
  355.         case MotionEvent.ACTION_UP:  
  356.             if (isClick)  
  357.             {  
  358.                 // 点击View后停2秒再开始  
  359.                 stop();  
  360.                 delayStartHandler.sendEmptyMessageDelayed(02000);  
  361.             } else  
  362.             {  
  363.                 start();  
  364.             }  
  365.             isAutoMove = true;  
  366.             break;  
  367.   
  368.         }  
  369.         return true;  
  370.     }  
  371.   
  372.     /** 
  373.      * @param textViews 
  374.      * @param type 
  375.      *            -1,0,1分别代表左中右list 
  376.      */  
  377.     private void layoutAll(List<TextView> textViews, int type)  
  378.     {  
  379.         int temp_y = firstTextY;  
  380.         for (int i = 0; i < textViews.size(); i++)  
  381.         {  
  382.             TextView temp = textViews.get(i);  
  383.             // 根据y值计算x坐标上的偏移量,type为-1时往左偏,抛物线开口向右,0的时候走直线,1的时候和-1对称  
  384.             int detaX = type  
  385.                     * (int) (-mWidth * 4 / scaleArcTopPoint  
  386.                             / Math.pow(mHeight, 2)  
  387.                             * Math.pow(mHeight / 2.0 - temp_y, 2) + mWidth  
  388.                             / scaleArcTopPoint);  
  389.             float scale = (float) (1 - 4 * Math.pow(mHeight / 2.0 - temp_y, 2)  
  390.                     / Math.pow(mHeight, 2));  
  391.             if (scale < 0)  
  392.                 scale = 0;  
  393.             float textScale = (float) ((minTextSize + scale  
  394.                     * (maxTextSize - minTextSize)) * 1.0 / minTextSize);  
  395.             temp.setScaleX(textScale);  
  396.             temp.setScaleY(textScale);  
  397.             temp.setAlpha(minAlpha + scale * (maxAlpha - minAlpha));  
  398.             temp.layout((mWidth - temp.getMeasuredWidth()) / 2 + detaX, temp_y,  
  399.                     (mWidth + temp.getMeasuredWidth()) / 2 + detaX, temp_y  
  400.                             + temp.getMeasuredHeight());  
  401.             temp_y += 2 * textMargin;  
  402.         }  
  403.     }  
  404.   
  405.     Handler delayStartHandler = new Handler()  
  406.     {  
  407.   
  408.         @Override  
  409.         public void handleMessage(Message msg)  
  410.         {  
  411.             start();  
  412.         }  
  413.   
  414.     };  
  415.   
  416.     @Override  
  417.     public void onClick(View v)  
  418.     {  
  419.         TextView tv = (TextView) v;  
  420.         Toast.makeText(mContext, tv.getText(), Toast.LENGTH_SHORT).show();  
  421.         stop();  
  422.         delayStartHandler.sendEmptyMessageDelayed(02000);  
  423.     }  
  424. }  
 

  代码中改变TextView的Size时用到了setScaleX和setScaleY这两个方法,Android3.0才有的,所以只能是3.0的平台编译,如果不这样改变Size而是直接setTextSize的话变化就没有那么流畅。每个TextView的X轴偏移量都是根据其Y轴位置偏离中心线的距离计算的。代码中已经有了相关注释,不是很难。

MainActivity的布局:

[html]  view plain copy
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:layout_width="match_parent"  
  3.     android:layout_height="match_parent" >  
  4.   
  5.     <com.jingchen.tagclouddemo.FlowLayout  
  6.         android:id="@+id/tagcloudview"  
  7.         android:layout_width="match_parent"  
  8.         android:layout_height="match_parent"  
  9.         android:background="#000000" />  
  10.   
  11. </RelativeLayout>  
MainActivity的代码:

[java]  view plain copy
  1. package com.jingchen.tagclouddemo;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5.   
  6. import android.app.Activity;  
  7. import android.os.Bundle;  
  8.   
  9. /** 
  10.  * @author chenjing 
  11.  *  
  12.  */  
  13. public class MainActivity extends Activity  
  14. {  
  15.     FlowLayout layout;  
  16.     int i = 0;  
  17.     List<String> textList;  
  18.     List<String> leftextList;  
  19.     List<String> righttextList;  
  20.   
  21.     @Override  
  22.     protected void onCreate(Bundle savedInstanceState)  
  23.     {  
  24.         super.onCreate(savedInstanceState);  
  25.         setContentView(R.layout.activity_main);  
  26.         layout = (FlowLayout) findViewById(R.id.tagcloudview);  
  27.         layout.setBackgroundResource(R.color.black);  
  28.         textList = new ArrayList<String>();  
  29.         leftextList = new ArrayList<String>();  
  30.         righttextList = new ArrayList<String>();  
  31.         addTexts();  
  32.         for (int i = 0; i < textList.size(); i++)  
  33.         {  
  34.             layout.addText(textList.get(i));  
  35.         }  
  36.         for (int i = 0; i < leftextList.size(); i++)  
  37.         {  
  38.             layout.addLeftText(leftextList.get(i));  
  39.         }  
  40.         for (int i = 0; i < righttextList.size(); i++)  
  41.         {  
  42.             layout.addRightText(righttextList.get(i));  
  43.         }  
  44.     }  
  45.   
  46.     private void addTexts()  
  47.     {  
  48.         textList.add("一路狂奔");  
  49.         textList.add("后宫:帝王之妻");  
  50.         textList.add("宝贝和我");  
  51.         textList.add("甜心巧克力");  
  52.         textList.add("恐怖故事");  
  53.         textList.add("百万爱情宝贝");  
  54.         textList.add("别跟我谈高富帅");  
  55.         textList.add("甜蜜十八岁");  
  56.         textList.add("终结者");  
  57.   
  58.         leftextList.add("百万爱情宝贝");  
  59.         leftextList.add("别跟我谈高富帅");  
  60.         leftextList.add("甜蜜十八岁");  
  61.         leftextList.add("金钱的味道");  
  62.         leftextList.add("艳遇");  
  63.         leftextList.add("痛症");  
  64.         leftextList.add("危险关系");  
  65.         leftextList.add("夺宝联盟");  
  66.         leftextList.add("101次求婚");  
  67.         leftextList.add("富春山居图");  
  68.   
  69.         righttextList.add("艳遇");  
  70.         righttextList.add("痛症");  
  71.         righttextList.add("危险关系");  
  72.         righttextList.add("今天");  
  73.         righttextList.add("小时代");  
  74.         righttextList.add("致我们将死的青春");  
  75.         righttextList.add("金钱的味道");  
  76.         righttextList.add("101次求婚");  
  77.     }  
  78.   
  79.     @Override  
  80.     protected void onDestroy()  
  81.     {  
  82.         layout.stop();  
  83.         super.onDestroy();  
  84.     }  
  85.   
  86. }  

在MainActivity中set三列数据后就可以start了,很简单的吧?

源码下载


你可能感兴趣的:(Android自定义控件实战——水波纹标签云TagCloud)