• Android自定义控件实战——滚动选择器PickerView


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

      手机里设置闹钟需要选择时间,那个选择时间的控件就是滚动选择器,前几天用手机刷了MIUI,发现自带的那个时间选择器效果挺好看的,于是就自己仿写了一个,权当练手。先来看效果:

                                                                     

    效果还行吧?实现思路就是自定义一个PickerView,单独滚动的是一个PickerView,显然上图中有分和秒的选择所以在布局里用了两个PickerView。由于这里不涉及到text的点击事件,所以只需要继承View就行了,直接把text用canvas画上去。PickerView的实现的主要难点:

    难点1:

            字体随距离的渐变。可以看到,text随离中心位置的距离变化而变化,这里变化的是透明度alpha和字体大小TexSize,这两个值我都设置了Max和Min值,通过其与中心点的距离计算scale。我用的是变化曲线是抛物线scale=1-ax^2(x<=Height/4),scale = 0(x>Height/4),a=(4/Height)^2。x就是距离View中心的偏移量。用图片表示如下:


    难点2:

         text的居中。绘制text的时候不仅要使其在x方向上居中,还要在y方向上居中,在x方向上比较简单,设置Paint的Align为Align.CENTER就行了,但是y方向上很蛋疼,需要计算text的baseline。

    难点3:

        循环滚动。为了解决循环滚动的问题我把存放text的List从中间往上下摊开,通过不断地moveHeadToTail和moveTailToHead使选中的text始终是list的中间position的值。

      

         以上就是几个难点,了解了之后可以来看PickerView的代码了:

    [java] view plain copy
    1. package com.jingchen.timerpicker;  
    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.content.Context;  
    9. import android.graphics.Canvas;  
    10. import android.graphics.Paint;  
    11. import android.graphics.Paint.Align;  
    12. import android.graphics.Paint.FontMetricsInt;  
    13. import android.graphics.Paint.Style;  
    14. import android.os.Handler;  
    15. import android.os.Message;  
    16. import android.util.AttributeSet;  
    17. import android.view.MotionEvent;  
    18. import android.view.View;  
    19.   
    20. /** 
    21.  * 滚动选择器 
    22.  *  
    23.  * @author chenjing 
    24.  *  
    25.  */  
    26. public class PickerView extends View  
    27. {  
    28.   
    29.     public static final String TAG = "PickerView";  
    30.     /** 
    31.      * text之间间距和minTextSize之比 
    32.      */  
    33.     public static final float MARGIN_ALPHA = 2.8f;  
    34.     /** 
    35.      * 自动回滚到中间的速度 
    36.      */  
    37.     public static final float SPEED = 2;  
    38.   
    39.     private List<String> mDataList;  
    40.     /** 
    41.      * 选中的位置,这个位置是mDataList的中心位置,一直不变 
    42.      */  
    43.     private int mCurrentSelected;  
    44.     private Paint mPaint;  
    45.   
    46.     private float mMaxTextSize = 80;  
    47.     private float mMinTextSize = 40;  
    48.   
    49.     private float mMaxTextAlpha = 255;  
    50.     private float mMinTextAlpha = 120;  
    51.   
    52.     private int mColorText = 0x333333;  
    53.   
    54.     private int mViewHeight;  
    55.     private int mViewWidth;  
    56.   
    57.     private float mLastDownY;  
    58.     /** 
    59.      * 滑动的距离 
    60.      */  
    61.     private float mMoveLen = 0;  
    62.     private boolean isInit = false;  
    63.     private onSelectListener mSelectListener;  
    64.     private Timer timer;  
    65.     private MyTimerTask mTask;  
    66.   
    67.     Handler updateHandler = new Handler()  
    68.     {  
    69.   
    70.         @Override  
    71.         public void handleMessage(Message msg)  
    72.         {  
    73.             if (Math.abs(mMoveLen) < SPEED)  
    74.             {  
    75.                 mMoveLen = 0;  
    76.                 if (mTask != null)  
    77.                 {  
    78.                     mTask.cancel();  
    79.                     mTask = null;  
    80.                     performSelect();  
    81.                 }  
    82.             } else  
    83.                 // 这里mMoveLen / Math.abs(mMoveLen)是为了保有mMoveLen的正负号,以实现上滚或下滚  
    84.                 mMoveLen = mMoveLen - mMoveLen / Math.abs(mMoveLen) * SPEED;  
    85.             invalidate();  
    86.         }  
    87.   
    88.     };  
    89.   
    90.     public PickerView(Context context)  
    91.     {  
    92.         super(context);  
    93.         init();  
    94.     }  
    95.   
    96.     public PickerView(Context context, AttributeSet attrs)  
    97.     {  
    98.         super(context, attrs);  
    99.         init();  
    100.     }  
    101.   
    102.     public void setOnSelectListener(onSelectListener listener)  
    103.     {  
    104.         mSelectListener = listener;  
    105.     }  
    106.   
    107.     private void performSelect()  
    108.     {  
    109.         if (mSelectListener != null)  
    110.             mSelectListener.onSelect(mDataList.get(mCurrentSelected));  
    111.     }  
    112.   
    113.     public void setData(List<String> datas)  
    114.     {  
    115.         mDataList = datas;  
    116.         mCurrentSelected = datas.size() / 2;  
    117.         invalidate();  
    118.     }  
    119.   
    120.     public void setSelected(int selected)  
    121.     {  
    122.         mCurrentSelected = selected;  
    123.     }  
    124.   
    125.     private void moveHeadToTail()  
    126.     {  
    127.         String head = mDataList.get(0);  
    128.         mDataList.remove(0);  
    129.         mDataList.add(head);  
    130.     }  
    131.   
    132.     private void moveTailToHead()  
    133.     {  
    134.         String tail = mDataList.get(mDataList.size() - 1);  
    135.         mDataList.remove(mDataList.size() - 1);  
    136.         mDataList.add(0, tail);  
    137.     }  
    138.   
    139.     @Override  
    140.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)  
    141.     {  
    142.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
    143.         mViewHeight = getMeasuredHeight();  
    144.         mViewWidth = getMeasuredWidth();  
    145.         // 按照View的高度计算字体大小  
    146.         mMaxTextSize = mViewHeight / 4.0f;  
    147.         mMinTextSize = mMaxTextSize / 2f;  
    148.         isInit = true;  
    149.         invalidate();  
    150.     }  
    151.   
    152.     private void init()  
    153.     {  
    154.         timer = new Timer();  
    155.         mDataList = new ArrayList<String>();  
    156.         mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
    157.         mPaint.setStyle(Style.FILL);  
    158.         mPaint.setTextAlign(Align.CENTER);  
    159.         mPaint.setColor(mColorText);  
    160.     }  
    161.   
    162.     @Override  
    163.     protected void onDraw(Canvas canvas)  
    164.     {  
    165.         super.onDraw(canvas);  
    166.         // 根据index绘制view  
    167.         if (isInit)  
    168.             drawData(canvas);  
    169.     }  
    170.   
    171.     private void drawData(Canvas canvas)  
    172.     {  
    173.         // 先绘制选中的text再往上往下绘制其余的text  
    174.         float scale = parabola(mViewHeight / 4.0f, mMoveLen);  
    175.         float size = (mMaxTextSize - mMinTextSize) * scale + mMinTextSize;  
    176.         mPaint.setTextSize(size);  
    177.         mPaint.setAlpha((int) ((mMaxTextAlpha - mMinTextAlpha) * scale + mMinTextAlpha));  
    178.         // text居中绘制,注意baseline的计算才能达到居中,y值是text中心坐标  
    179.         float x = (float) (mViewWidth / 2.0);  
    180.         float y = (float) (mViewHeight / 2.0 + mMoveLen);  
    181.         FontMetricsInt fmi = mPaint.getFontMetricsInt();  
    182.         float baseline = (float) (y - (fmi.bottom / 2.0 + fmi.top / 2.0));  
    183.   
    184.         canvas.drawText(mDataList.get(mCurrentSelected), x, baseline, mPaint);  
    185.         // 绘制上方data  
    186.         for (int i = 1; (mCurrentSelected - i) >= 0; i++)  
    187.         {  
    188.             drawOtherText(canvas, i, -1);  
    189.         }  
    190.         // 绘制下方data  
    191.         for (int i = 1; (mCurrentSelected + i) < mDataList.size(); i++)  
    192.         {  
    193.             drawOtherText(canvas, i, 1);  
    194.         }  
    195.   
    196.     }  
    197.   
    198.     /** 
    199.      * @param canvas 
    200.      * @param position 
    201.      *            距离mCurrentSelected的差值 
    202.      * @param type 
    203.      *            1表示向下绘制,-1表示向上绘制 
    204.      */  
    205.     private void drawOtherText(Canvas canvas, int position, int type)  
    206.     {  
    207.         float d = (float) (MARGIN_ALPHA * mMinTextSize * position + type  
    208.                 * mMoveLen);  
    209.         float scale = parabola(mViewHeight / 4.0f, d);  
    210.         float size = (mMaxTextSize - mMinTextSize) * scale + mMinTextSize;  
    211.         mPaint.setTextSize(size);  
    212.         mPaint.setAlpha((int) ((mMaxTextAlpha - mMinTextAlpha) * scale + mMinTextAlpha));  
    213.         float y = (float) (mViewHeight / 2.0 + type * d);  
    214.         FontMetricsInt fmi = mPaint.getFontMetricsInt();  
    215.         float baseline = (float) (y - (fmi.bottom / 2.0 + fmi.top / 2.0));  
    216.         canvas.drawText(mDataList.get(mCurrentSelected + type * position),  
    217.                 (float) (mViewWidth / 2.0), baseline, mPaint);  
    218.     }  
    219.   
    220.     /** 
    221.      * 抛物线 
    222.      *  
    223.      * @param zero 
    224.      *            零点坐标 
    225.      * @param x 
    226.      *            偏移量 
    227.      * @return scale 
    228.      */  
    229.     private float parabola(float zero, float x)  
    230.     {  
    231.         float f = (float) (1 - Math.pow(x / zero, 2));  
    232.         return f < 0 ? 0 : f;  
    233.     }  
    234.   
    235.     @Override  
    236.     public boolean onTouchEvent(MotionEvent event)  
    237.     {  
    238.         switch (event.getActionMasked())  
    239.         {  
    240.         case MotionEvent.ACTION_DOWN:  
    241.             doDown(event);  
    242.             break;  
    243.         case MotionEvent.ACTION_MOVE:  
    244.             doMove(event);  
    245.             break;  
    246.         case MotionEvent.ACTION_UP:  
    247.             doUp(event);  
    248.             break;  
    249.         }  
    250.         return true;  
    251.     }  
    252.   
    253.     private void doDown(MotionEvent event)  
    254.     {  
    255.         if (mTask != null)  
    256.         {  
    257.             mTask.cancel();  
    258.             mTask = null;  
    259.         }  
    260.         mLastDownY = event.getY();  
    261.     }  
    262.   
    263.     private void doMove(MotionEvent event)  
    264.     {  
    265.   
    266.         mMoveLen += (event.getY() - mLastDownY);  
    267.   
    268.         if (mMoveLen > MARGIN_ALPHA * mMinTextSize / 2)  
    269.         {  
    270.             // 往下滑超过离开距离  
    271.             moveTailToHead();  
    272.             mMoveLen = mMoveLen - MARGIN_ALPHA * mMinTextSize;  
    273.         } else if (mMoveLen < -MARGIN_ALPHA * mMinTextSize / 2)  
    274.         {  
    275.             // 往上滑超过离开距离  
    276.             moveHeadToTail();  
    277.             mMoveLen = mMoveLen + MARGIN_ALPHA * mMinTextSize;  
    278.         }  
    279.   
    280.         mLastDownY = event.getY();  
    281.         invalidate();  
    282.     }  
    283.   
    284.     private void doUp(MotionEvent event)  
    285.     {  
    286.         // 抬起手后mCurrentSelected的位置由当前位置move到中间选中位置  
    287.         if (Math.abs(mMoveLen) < 0.0001)  
    288.         {  
    289.             mMoveLen = 0;  
    290.             return;  
    291.         }  
    292.         if (mTask != null)  
    293.         {  
    294.             mTask.cancel();  
    295.             mTask = null;  
    296.         }  
    297.         mTask = new MyTimerTask(updateHandler);  
    298.         timer.schedule(mTask, 010);  
    299.     }  
    300.   
    301.     class MyTimerTask extends TimerTask  
    302.     {  
    303.         Handler handler;  
    304.   
    305.         public MyTimerTask(Handler handler)  
    306.         {  
    307.             this.handler = handler;  
    308.         }  
    309.   
    310.         @Override  
    311.         public void run()  
    312.         {  
    313.             handler.sendMessage(handler.obtainMessage());  
    314.         }  
    315.   
    316.     }  
    317.   
    318.     public interface onSelectListener  
    319.     {  
    320.         void onSelect(String text);  
    321.     }  
    322. }  


    代码里的注释都写的很清楚了。接下来,我们就用写好的PickerView实现文章开头的图片效果吧~

    首先看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.     android:background="#000000" >  
    5.   
    6.     <RelativeLayout  
    7.         android:layout_width="wrap_content"  
    8.         android:layout_height="wrap_content"  
    9.         android:layout_centerInParent="true"  
    10.         android:background="#ffffff" >  
    11.   
    12.         <com.jingchen.timerpicker.PickerView  
    13.             android:id="@+id/minute_pv"  
    14.             android:layout_width="80dp"  
    15.             android:layout_height="160dp" />  
    16.   
    17.         <TextView  
    18.             android:id="@+id/minute_tv"  
    19.             android:layout_width="wrap_content"  
    20.             android:layout_height="wrap_content"  
    21.             android:layout_centerVertical="true"  
    22.             android:layout_toRightOf="@id/minute_pv"  
    23.             android:text="分"  
    24.             android:textColor="#ffaa33"  
    25.             android:textSize="26sp"  
    26.             android:textStyle="bold" />  
    27.   
    28.         <com.jingchen.timerpicker.PickerView  
    29.             android:id="@+id/second_pv"  
    30.             android:layout_width="80dp"  
    31.             android:layout_height="160dp"  
    32.             android:layout_toRightOf="@id/minute_tv" />  
    33.   
    34.         <TextView  
    35.             android:id="@+id/second_tv"  
    36.             android:layout_width="wrap_content"  
    37.             android:layout_height="wrap_content"  
    38.             android:layout_centerVertical="true"  
    39.             android:layout_toRightOf="@id/second_pv"  
    40.             android:text="秒"  
    41.             android:textColor="#ffaa33"  
    42.             android:textSize="26sp"  
    43.             android:textStyle="bold" />  
    44.     </RelativeLayout>  
    45.   
    46. </RelativeLayout>  
    两个PickerView两个TextView,很简单。

    下面是MainActivity的代码:

    [java] view plain copy
    1. package com.jingchen.timerpicker;  
    2.   
    3. import java.util.ArrayList;  
    4. import java.util.List;  
    5.   
    6. import com.jingchen.timerpicker.PickerView.onSelectListener;  
    7.   
    8. import android.app.Activity;  
    9. import android.os.Bundle;  
    10. import android.view.Menu;  
    11. import android.widget.TextView;  
    12. import android.widget.Toast;  
    13.   
    14. public class MainActivity extends Activity  
    15. {  
    16.   
    17.     PickerView minute_pv;  
    18.     PickerView second_pv;  
    19.   
    20.     @Override  
    21.     protected void onCreate(Bundle savedInstanceState)  
    22.     {  
    23.         super.onCreate(savedInstanceState);  
    24.         setContentView(R.layout.activity_main);  
    25.         minute_pv = (PickerView) findViewById(R.id.minute_pv);  
    26.         second_pv = (PickerView) findViewById(R.id.second_pv);  
    27.         List<String> data = new ArrayList<String>();  
    28.         List<String> seconds = new ArrayList<String>();  
    29.         for (int i = 0; i < 10; i++)  
    30.         {  
    31.             data.add("0" + i);  
    32.         }  
    33.         for (int i = 0; i < 60; i++)  
    34.         {  
    35.             seconds.add(i < 10 ? "0" + i : "" + i);  
    36.         }  
    37.         minute_pv.setData(data);  
    38.         minute_pv.setOnSelectListener(new onSelectListener()  
    39.         {  
    40.   
    41.             @Override  
    42.             public void onSelect(String text)  
    43.             {  
    44.                 Toast.makeText(MainActivity.this"选择了 " + text + " 分",  
    45.                         Toast.LENGTH_SHORT).show();  
    46.             }  
    47.         });  
    48.         second_pv.setData(seconds);  
    49.         second_pv.setOnSelectListener(new onSelectListener()  
    50.         {  
    51.   
    52.             @Override  
    53.             public void onSelect(String text)  
    54.             {  
    55.                 Toast.makeText(MainActivity.this"选择了 " + text + " 秒",  
    56.                         Toast.LENGTH_SHORT).show();  
    57.             }  
    58.         });  
    59.     }  
    60.   
    61.     @Override  
    62.     public boolean onCreateOptionsMenu(Menu menu)  
    63.     {  
    64.         getMenuInflater().inflate(R.menu.main, menu);  
    65.         return true;  
    66.     }  
    67.   
    68. }  

    OK了,自定义自己的TimerPicker就是这么简单~

    源码下载

    来源:http://blog.csdn.net/zhongkejingwang/article/details/38513301





  • 相关阅读:
    Tsinghua 2018 DSA PA3简要题解
    Tsinghua 2018 DSA PA2简要题解
    Python logging系统
    Surface RT2使用情况
    隔壁信概大作业xjb写——同化棋ATAXX
    XJTUOJ #1080 qz的不卡常数
    XJTUOJ #1081 JM的赃物被盗
    XJTUOJ #1078 JM的恶有恶报
    洛谷P5425 [USACO19OPEN]I Would Walk 500 Miles G
    洛谷P2857 [USACO06FEB]Steady Cow Assignment G
  • 原文地址:https://www.cnblogs.com/jeffen/p/6958398.html
Copyright © 2020-2023  润新知