android滚轮选择器

首先呈上效果图



现在很多地方都用到了滚轮布局WheelView,比如在选择生日的时候,风格类似系统提供的DatePickerDialog,开源的控件也有很多,不过大部分都是根据当前项目的需求绘制的界面,因此我就自己写了一款比较符合自己项目的WheelView。

首先这个控件有以下的需求:

1、能够循环滚动,当向上或者向下滑动到临界值的时候,则循环开始滚动

2、中间的一块有一块半透明的选择区,滑动结束时,哪一块在这个选择区,就选择这快。

3、继承自View进行绘制


然后进行一些关键点的讲解:

1、整体控件继承自View,在onDraw中进行绘制。整体包含三个模块,整个View、每一块的条目、中间选择区的条目(额外绘制一块灰色区域)。

2、通过动态设置或者默认设置的可显示条目数,在最上和最下再各加入一块,意思就是一共绘制showCount+2个条目。

3、当最上面的条目数滑动超过条目高度的一半时,进行动态条目更新:将最下面的条目删除加入第一个条目、将第一个条目删除加入最下面的条目。

4、外界可设置条目显示数、字体大小、颜色、选择区提示文字(图中那个年字)、默认选择项、padding补白等等。

5、在onTouchEvent中,得到手指滑动的渐变值,动态更新当前所有的条目。

6、在onMeasure中动态计算宽度,所有条目的宽度、高度、起始Y坐标等等。

7、通过当前条目和被选择条目的坐标,超过一半则视为被选择,并且滑动到对应的位置。


下面的是WheelView代码,主要是计算初始值、得到外面设置的值:

[java]  view plain  copy
 
  1. package cc.wxf.view.wheel;  
  2.   
  3. import android.content.Context;  
  4. import android.graphics.Canvas;  
  5. import android.graphics.Color;  
  6. import android.graphics.Paint;  
  7. import android.util.AttributeSet;  
  8. import android.view.MotionEvent;  
  9. import android.view.View;  
  10.   
  11. import java.util.ArrayList;  
  12. import java.util.List;  
  13.   
  14. /** 
  15.  * Created by ccwxf on 2016/3/31. 
  16.  */  
  17. public class WheelView extends View {  
  18.   
  19.     public static final int FONT_COLOR = Color.BLACK;  
  20.     public static final int FONT_SIZE = 30;  
  21.     public static final int PADDING = 10;  
  22.     public static final int SHOW_COUNT = 3;  
  23.     public static final int SELECT = 0;  
  24.     //总体宽度、高度、Item的高度  
  25.     private int width;  
  26.     private int height;  
  27.     private int itemHeight;  
  28.     //需要显示的行数  
  29.     private int showCount = SHOW_COUNT;  
  30.     //当前默认选择的位置  
  31.     private int select = SELECT;  
  32.     //字体颜色、大小、补白  
  33.     private int fontColor = FONT_COLOR;  
  34.     private int fontSize = FONT_SIZE;  
  35.     private int padding = PADDING;  
  36.     //文本列表  
  37.     private List lists;  
  38.     //选中项的辅助文本,可为空  
  39.     private String selectTip;  
  40.     //每一项Item和选中项  
  41.     private List wheelItems = new ArrayList();  
  42.     private WheelSelect wheelSelect = null;  
  43.     //手点击的Y坐标  
  44.     private float mTouchY;  
  45.     //监听器  
  46.     private OnWheelViewItemSelectListener listener;  
  47.   
  48.     public WheelView(Context context) {  
  49.         super(context);  
  50.     }  
  51.   
  52.     public WheelView(Context context, AttributeSet attrs) {  
  53.         super(context, attrs);  
  54.     }  
  55.   
  56.     public WheelView(Context context, AttributeSet attrs, int defStyleAttr) {  
  57.         super(context, attrs, defStyleAttr);  
  58.     }  
  59.   
  60.     /** 
  61.      * 设置字体的颜色,不设置的话默认为黑色 
  62.      * @param fontColor 
  63.      * @return 
  64.      */  
  65.     public WheelView fontColor(int fontColor){  
  66.         this.fontColor = fontColor;  
  67.         return this;  
  68.     }  
  69.   
  70.     /** 
  71.      * 设置字体的大小,不设置的话默认为30 
  72.      * @param fontSize 
  73.      * @return 
  74.      */  
  75.     public WheelView fontSize(int fontSize){  
  76.         this.fontSize = fontSize;  
  77.         return this;  
  78.     }  
  79.   
  80.     /** 
  81.      * 设置文本到上下两边的补白,不合适的话默认为10 
  82.      * @param padding 
  83.      * @return 
  84.      */  
  85.     public WheelView padding(int padding){  
  86.         this.padding = padding;  
  87.         return this;  
  88.     }  
  89.   
  90.     /** 
  91.      * 设置选中项的复制文本,可以不设置 
  92.      * @param selectTip 
  93.      * @return 
  94.      */  
  95.     public WheelView selectTip(String selectTip){  
  96.         this.selectTip = selectTip;  
  97.         return this;  
  98.     }  
  99.   
  100.     /** 
  101.      * 设置文本列表,必须且必须在build方法之前设置 
  102.      * @param lists 
  103.      * @return 
  104.      */  
  105.     public WheelView lists(List lists){  
  106.         this.lists = lists;  
  107.         return this;  
  108.     }  
  109.   
  110.     /** 
  111.      * 设置显示行数,不设置的话默认为3 
  112.      * @param showCount 
  113.      * @return 
  114.      */  
  115.     public WheelView showCount(int showCount){  
  116.         if(showCount % 2 == 0){  
  117.             throw new IllegalStateException("the showCount must be odd");  
  118.         }  
  119.         this.showCount = showCount;  
  120.         return this;  
  121.     }  
  122.   
  123.     /** 
  124.      * 设置默认选中的文本的索引,不设置默认为0 
  125.      * @param select 
  126.      * @return 
  127.      */  
  128.     public WheelView select(int select){  
  129.         this.select = select;  
  130.         return this;  
  131.     }  
  132.   
  133.     /** 
  134.      * 最后调用的方法,判断是否有必要函数没有被调用 
  135.      * @return 
  136.      */  
  137.     public WheelView build(){  
  138.         if(lists == null){  
  139.             throw new IllegalStateException("this method must invoke after the method [lists]");  
  140.         }  
  141.         return this;  
  142.     }  
  143.   
  144.     @Override  
  145.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  146.         //得到总体宽度  
  147.         width = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();  
  148.         // 得到每一个Item的高度  
  149.         Paint mPaint = new Paint();  
  150.         mPaint.setTextSize(fontSize);  
  151.         Paint.FontMetrics metrics =  mPaint.getFontMetrics();  
  152.         itemHeight = (int) (metrics.bottom - metrics.top) + 2 * padding;  
  153.         //初始化每一个WheelItem  
  154.         initWheelItems(width, itemHeight);  
  155.         //初始化WheelSelect  
  156.         wheelSelect = new WheelSelect(showCount / 2 * itemHeight, width, itemHeight, selectTip, fontColor, fontSize, padding);  
  157.         //得到所有的高度  
  158.         height = itemHeight * showCount;  
  159.         super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));  
  160.     }  
  161.   
  162.     /** 
  163.      * 创建显示个数+2个WheelItem 
  164.      * @param width 
  165.      * @param itemHeight 
  166.      */  
  167.     private void initWheelItems(int width, int itemHeight) {  
  168.         wheelItems.clear();  
  169.         for(int i = 0; i < showCount + 2; i++){  
  170.             int startY = itemHeight * (i - 1);  
  171.             int stringIndex = select - showCount / 2 - 1 + i;  
  172.             if(stringIndex < 0){  
  173.                 stringIndex = lists.size() + stringIndex;  
  174.             }  
  175.             wheelItems.add(new WheelItem(startY, width, itemHeight, fontColor, fontSize, lists.get(stringIndex)));  
  176.         }  
  177.     }  
  178.   
  179.     @Override  
  180.     public boolean onTouchEvent(MotionEvent event) {  
  181.         switch (event.getAction()){  
  182.             case MotionEvent.ACTION_DOWN:  
  183.                 mTouchY = event.getY();  
  184.                 return true;  
  185.             case MotionEvent.ACTION_MOVE:  
  186.                 float dy = event.getY() - mTouchY;  
  187.                 mTouchY = event.getY();  
  188.                 handleMove(dy);  
  189.                 break;  
  190.             case MotionEvent.ACTION_UP:  
  191.                 handleUp();  
  192.                 break;  
  193.         }  
  194.         return super.onTouchEvent(event);  
  195.     }  
  196.   
  197.     /** 
  198.      * 处理移动操作 
  199.      * @param dy 
  200.      */  
  201.     private void handleMove(float dy) {  
  202.         //调整坐标  
  203.         for(WheelItem item : wheelItems){  
  204.             item.adjust(dy);  
  205.         }  
  206.         invalidate();  
  207.         //调整  
  208.         adjust();  
  209.     }  
  210.   
  211.     /** 
  212.      * 处理抬起操作 
  213.      */  
  214.     private void handleUp(){  
  215.         int index = -1;  
  216.         //得到应该选择的那一项  
  217.         for(int i = 0; i < wheelItems.size(); i++){  
  218.             WheelItem item = wheelItems.get(i);  
  219.             //如果startY在selectItem的中点上面,则将该项作为选择项  
  220.             if(item.getStartY() > wheelSelect.getStartY() && item.getStartY() < (wheelSelect.getStartY() + itemHeight / 2)){  
  221.                 index = i;  
  222.                 break;  
  223.             }  
  224.             //如果startY在selectItem的中点下面,则将上一项作为选择项  
  225.             if(item.getStartY() >= (wheelSelect.getStartY() + itemHeight / 2) && item.getStartY() < (wheelSelect.getStartY() + itemHeight)){  
  226.                 index = i - 1;  
  227.                 break;  
  228.             }  
  229.         }  
  230.         //如果没找到或者其他因素,直接返回  
  231.         if(index == -1){  
  232.             return;  
  233.         }  
  234.         //得到偏移的位移  
  235.         float dy = wheelSelect.getStartY() - wheelItems.get(index).getStartY();  
  236.         //调整坐标  
  237.         for(WheelItem item : wheelItems){  
  238.             item.adjust(dy);  
  239.         }  
  240.         invalidate();  
  241.         // 调整  
  242.         adjust();  
  243.         //设置选择项  
  244.         int stringIndex = lists.indexOf(wheelItems.get(index).getText());  
  245.         if(stringIndex != -1){  
  246.             select = stringIndex;  
  247.             if(listener != null){  
  248.                 listener.onItemSelect(select);  
  249.             }  
  250.         }  
  251.     }  
  252.   
  253.     /** 
  254.      * 调整Item移动和循环显示 
  255.      */  
  256.     private void adjust(){  
  257.         //如果向下滑动超出半个Item的高度,则调整容器  
  258.         if(wheelItems.get(0).getStartY() >= -itemHeight / 2 ){  
  259.             //移除最后一个Item重用  
  260.             WheelItem item = wheelItems.remove(wheelItems.size() - 1);  
  261.             //设置起点Y坐标  
  262.             item.setStartY(wheelItems.get(0).getStartY() - itemHeight);  
  263.             //得到文本在容器中的索引  
  264.             int index = lists.indexOf(wheelItems.get(0).getText());  
  265.             if(index == -1){  
  266.                 return;  
  267.             }  
  268.             index -= 1;  
  269.             if(index < 0){  
  270.                 index = lists.size() + index;  
  271.             }  
  272.             //设置文本  
  273.             item.setText(lists.get(index));  
  274.             //添加到最开始  
  275.             wheelItems.add(0, item);  
  276.             invalidate();  
  277.             return;  
  278.         }  
  279.         //如果向上滑超出半个Item的高度,则调整容器  
  280.         if(wheelItems.get(0).getStartY() <= (-itemHeight / 2 - itemHeight)){  
  281.             //移除第一个Item重用  
  282.             WheelItem item = wheelItems.remove(0);  
  283.             //设置起点Y坐标  
  284.             item.setStartY(wheelItems.get(wheelItems.size() - 1).getStartY() + itemHeight);  
  285.             //得到文本在容器中的索引  
  286.             int index = lists.indexOf(wheelItems.get(wheelItems.size() - 1).getText());  
  287.             if(index == -1){  
  288.                 return;  
  289.             }  
  290.             index += 1;  
  291.             if(index >= lists.size()){  
  292.                 index = 0;  
  293.             }  
  294.             //设置文本  
  295.             item.setText(lists.get(index));  
  296.             //添加到最后面  
  297.             wheelItems.add(item);  
  298.             invalidate();  
  299.             return;  
  300.         }  
  301.     }  
  302.   
  303.     /** 
  304.      * 得到当前的选择项 
  305.      */  
  306.     public int getSelectItem(){  
  307.         return select;  
  308.     }  
  309.   
  310.     @Override  
  311.     protected void onDraw(Canvas canvas) {  
  312.         //绘制每一项Item  
  313.         for(WheelItem item : wheelItems){  
  314.             item.onDraw(canvas);  
  315.         }  
  316.         //绘制阴影  
  317.         if(wheelSelect != null){  
  318.             wheelSelect.onDraw(canvas);  
  319.         }  
  320.     }  
  321.   
  322.     /** 
  323.      * 设置监听器 
  324.      * @param listener 
  325.      * @return 
  326.      */  
  327.     public WheelView listener(OnWheelViewItemSelectListener listener){  
  328.         this.listener = listener;  
  329.         return this;  
  330.     }  
  331.   
  332.     public interface OnWheelViewItemSelectListener{  
  333.         void onItemSelect(int index);  
  334.     }  
  335. }  


然后是每一个条目类,根据当前的坐标进行绘制,根据渐变值改变坐标等:

[java]  view plain  copy
 
  1. package cc.wxf.view.wheel;  
  2.   
  3. import android.graphics.Canvas;  
  4. import android.graphics.Paint;  
  5. import android.graphics.RectF;  
  6.   
  7. /** 
  8.  * Created by ccwxf on 2016/3/31. 
  9.  */  
  10. public class WheelItem {  
  11.     // 起点Y坐标、宽度、高度  
  12.     private float startY;  
  13.     private int width;  
  14.     private int height;  
  15.     //四点坐标  
  16.     private RectF rect = new RectF();  
  17.     //字体大小、颜色  
  18.     private int fontColor;  
  19.     private int fontSize;  
  20.     private String text;  
  21.     private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
  22.   
  23.     public WheelItem(float startY, int width, int height, int fontColor, int fontSize, String text) {  
  24.         this.startY = startY;  
  25.         this.width = width;  
  26.         this.height = height;  
  27.         this.fontColor = fontColor;  
  28.         this.fontSize = fontSize;  
  29.         this.text = text;  
  30.         adjust(0);  
  31.     }  
  32.   
  33.     /** 
  34.      * 根据Y坐标的变化值,调整四点坐标值 
  35.      * @param dy 
  36.      */  
  37.     public void adjust(float dy){  
  38.         startY += dy;  
  39.         rect.left = 0;  
  40.         rect.top = startY;  
  41.         rect.right = width;  
  42.         rect.bottom = startY + height;  
  43.     }  
  44.   
  45.     public float getStartY() {  
  46.         return startY;  
  47.     }  
  48.   
  49.     /** 
  50.      * 直接设置Y坐标属性,调整四点坐标属性 
  51.      * @param startY 
  52.      */  
  53.     public void setStartY(float startY) {  
  54.         this.startY = startY;  
  55.         rect.left = 0;  
  56.         rect.top = startY;  
  57.         rect.right = width;  
  58.         rect.bottom = startY + height;  
  59.     }  
  60.   
  61.     public void setText(String text) {  
  62.         this.text = text;  
  63.     }  
  64.   
  65.     public String getText() {  
  66.         return text;  
  67.     }  
  68.   
  69.     public void onDraw(Canvas mCanvas){  
  70.         //设置钢笔属性  
  71.         mPaint.setTextSize(fontSize);  
  72.         mPaint.setColor(fontColor);  
  73.         //得到字体的宽度  
  74.         int textWidth = (int)mPaint.measureText(text);  
  75.         //drawText的绘制起点是左下角,y轴起点为baseLine  
  76.         Paint.FontMetrics metrics =  mPaint.getFontMetrics();  
  77.         int baseLine = (int)(rect.centerY() + (metrics.bottom - metrics.top) / 2 - metrics.bottom);  
  78.         //居中绘制  
  79.         mCanvas.drawText(text, rect.centerX() - textWidth / 2, baseLine, mPaint);  
  80.     }  
  81. }  


最后是选择项,就是额外得在中间区域绘制一块灰色区域:

[java]  view plain  copy
 
  1. package cc.wxf.view.wheel;  
  2.   
  3. import android.graphics.Canvas;  
  4. import android.graphics.Color;  
  5. import android.graphics.Paint;  
  6. import android.graphics.Rect;  
  7.   
  8. /** 
  9.  * Created by ccwxf on 2016/4/1. 
  10.  */  
  11. public class WheelSelect {  
  12.     //黑框背景颜色  
  13.     public static final int COLOR_BACKGROUND = Color.parseColor("#77777777");  
  14.     //黑框的Y坐标起点、宽度、高度  
  15.     private int startY;  
  16.     private int width;  
  17.     private int height;  
  18.     //四点坐标  
  19.     private Rect rect = new Rect();  
  20.     //需要选择文本的颜色、大小、补白  
  21.     private String selectText;  
  22.     private int fontColor;  
  23.     private int fontSize;  
  24.     private int padding;  
  25.     private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
  26.   
  27.     public WheelSelect(int startY, int width, int height, String selectText, int fontColor, int fontSize, int padding) {  
  28.         this.startY = startY;  
  29.         this.width = width;  
  30.         this.height = height;  
  31.         this.selectText = selectText;  
  32.         this.fontColor = fontColor;  
  33.         this.fontSize = fontSize;  
  34.         this.padding = padding;  
  35.         rect.left = 0;  
  36.         rect.top = startY;  
  37.         rect.right = width;  
  38.         rect.bottom = startY + height;  
  39.     }  
  40.   
  41.     public int getStartY() {  
  42.         return startY;  
  43.     }  
  44.   
  45.     public void setStartY(int startY) {  
  46.         this.startY = startY;  
  47.     }  
  48.   
  49.     public void onDraw(Canvas mCanvas) {  
  50.         //绘制背景  
  51.         mPaint.setStyle(Paint.Style.FILL);  
  52.         mPaint.setColor(COLOR_BACKGROUND);  
  53.         mCanvas.drawRect(rect, mPaint);  
  54.         //绘制提醒文字  
  55.         if(selectText != null){  
  56.             //设置钢笔属性  
  57.             mPaint.setTextSize(fontSize);  
  58.             mPaint.setColor(fontColor);  
  59.             //得到字体的宽度  
  60.             int textWidth = (int)mPaint.measureText(selectText);  
  61.             //drawText的绘制起点是左下角,y轴起点为baseLine  
  62.             Paint.FontMetrics metrics =  mPaint.getFontMetrics();  
  63.             int baseLine = (int)(rect.centerY() + (metrics.bottom - metrics.top) / 2 - metrics.bottom);  
  64.             //在靠右边绘制文本  
  65.             mCanvas.drawText(selectText, rect.right - padding - textWidth, baseLine, mPaint);  
  66.         }  
  67.     }  
  68. }  


源代码就三个文件,很简单,注释也很详细,接下来就是使用文件了:

[java]  view plain  copy
 
  1. final WheelView wheelView = (WheelView) findViewById(R.id.wheelView);  
  2. final List lists = new ArrayList<>();  
  3. for(int i = 0; i < 20; i++){  
  4.     lists.add("test:" + i);  
  5. }  
  6. wheelView.lists(lists).fontSize(35).showCount(5).selectTip("年").select(0).listener(new WheelView.OnWheelViewItemSelectListener() {  
  7.     @Override  
  8.     public void onItemSelect(int index) {  
  9.         Log.d("cc""current select:" + wheelView.getSelectItem() + " index :" + index + ",result=" + lists.get(index));  
  10.     }  
  11. }).build();  

这个控件说简单也简单,说复杂也挺复杂,从最基础的onDraw实现,可以非常高灵活度地定制各自的需求。

demo工程就不提供了,使用非常简单。



转载至:http://blog.csdn.net/cc_lova_wxf/article/details/51063381?locationNum=3&fps=1

你可能感兴趣的:(android滚轮选择器)