zoukankan      html  css  js  c++  java
  • Android自己定义实现循环滚轮控件WheelView

    首先呈上效果图



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

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

    1、可以循环滚动。当向上或者向下滑动到临界值的时候,则循环開始滚动

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

    3、继承自View进行绘制


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

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

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

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

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

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

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

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


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

    package cc.wxf.view.wheel;
    
    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.util.AttributeSet;
    import android.view.MotionEvent;
    import android.view.View;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * Created by ccwxf on 2016/3/31.
     */
    public class WheelView extends View {
    
        public static final int FONT_COLOR = Color.BLACK;
        public static final int FONT_SIZE = 30;
        public static final int PADDING = 10;
        public static final int SHOW_COUNT = 3;
        public static final int SELECT = 0;
        //整体宽度、高度、Item的高度
        private int width;
        private int height;
        private int itemHeight;
        //须要显示的行数
        private int showCount = SHOW_COUNT;
        //当前默认选择的位置
        private int select = SELECT;
        //字体颜色、大小、补白
        private int fontColor = FONT_COLOR;
        private int fontSize = FONT_SIZE;
        private int padding = PADDING;
        //文本列表
        private List<String> lists;
        //选中项的辅助文本,可为空
        private String selectTip;
        //每一项Item和选中项
        private List<WheelItem> wheelItems = new ArrayList<WheelItem>();
        private WheelSelect wheelSelect = null;
        //手点击的Y坐标
        private float mTouchY;
        //监听器
        private OnWheelViewItemSelectListener listener;
    
        public WheelView(Context context) {
            super(context);
        }
    
        public WheelView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public WheelView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        /**
         * 设置字体的颜色,不设置的话默觉得黑色
         * @param fontColor
         * @return
         */
        public WheelView fontColor(int fontColor){
            this.fontColor = fontColor;
            return this;
        }
    
        /**
         * 设置字体的大小,不设置的话默觉得30
         * @param fontSize
         * @return
         */
        public WheelView fontSize(int fontSize){
            this.fontSize = fontSize;
            return this;
        }
    
        /**
         * 设置文本到上下两边的补白。不合适的话默觉得10
         * @param padding
         * @return
         */
        public WheelView padding(int padding){
            this.padding = padding;
            return this;
        }
    
        /**
         * 设置选中项的复制文本,能够不设置
         * @param selectTip
         * @return
         */
        public WheelView selectTip(String selectTip){
            this.selectTip = selectTip;
            return this;
        }
    
        /**
         * 设置文本列表,必须且必须在build方法之前设置
         * @param lists
         * @return
         */
        public WheelView lists(List<String> lists){
            this.lists = lists;
            return this;
        }
    
        /**
         * 设置显示行数,不设置的话默觉得3
         * @param showCount
         * @return
         */
        public WheelView showCount(int showCount){
            if(showCount % 2 == 0){
                throw new IllegalStateException("the showCount must be odd");
            }
            this.showCount = showCount;
            return this;
        }
    
        /**
         * 设置默认选中的文本的索引,不设置默觉得0
         * @param select
         * @return
         */
        public WheelView select(int select){
            this.select = select;
            return this;
        }
    
        /**
         * 最后调用的方法。推断是否有必要函数没有被调用
         * @return
         */
        public WheelView build(){
            if(lists == null){
                throw new IllegalStateException("this method must invoke after the method [lists]");
            }
            return this;
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            //得到整体宽度
            width = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
            // 得到每个Item的高度
            Paint mPaint = new Paint();
            mPaint.setTextSize(fontSize);
            Paint.FontMetrics metrics =  mPaint.getFontMetrics();
            itemHeight = (int) (metrics.bottom - metrics.top) + 2 * padding;
            //初始化每个WheelItem
            initWheelItems(width, itemHeight);
            //初始化WheelSelect
            wheelSelect = new WheelSelect(showCount / 2 * itemHeight, width, itemHeight, selectTip, fontColor, fontSize, padding);
            //得到全部的高度
            height = itemHeight * showCount;
            super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
        }
    
        /**
         * 创建显示个数+2个WheelItem
         * @param width
         * @param itemHeight
         */
        private void initWheelItems(int width, int itemHeight) {
            wheelItems.clear();
            for(int i = 0; i < showCount + 2; i++){
                int startY = itemHeight * (i - 1);
                int stringIndex = select - showCount / 2 - 1 + i;
                if(stringIndex < 0){
                    stringIndex = lists.size() + stringIndex;
                }
                wheelItems.add(new WheelItem(startY, width, itemHeight, fontColor, fontSize, lists.get(stringIndex)));
            }
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()){
                case MotionEvent.ACTION_DOWN:
                    mTouchY = event.getY();
                    return true;
                case MotionEvent.ACTION_MOVE:
                    float dy = event.getY() - mTouchY;
                    mTouchY = event.getY();
                    handleMove(dy);
                    break;
                case MotionEvent.ACTION_UP:
                    handleUp();
                    break;
            }
            return super.onTouchEvent(event);
        }
    
        /**
         * 处理移动操作
         * @param dy
         */
        private void handleMove(float dy) {
            //调整坐标
            for(WheelItem item : wheelItems){
                item.adjust(dy);
            }
            invalidate();
            //调整
            adjust();
        }
    
        /**
         * 处理抬起操作
         */
        private void handleUp(){
            int index = -1;
            //得到应该选择的那一项
            for(int i = 0; i < wheelItems.size(); i++){
                WheelItem item = wheelItems.get(i);
                //假设startY在selectItem的中点上面,则将该项作为选择项
                if(item.getStartY() > wheelSelect.getStartY() && item.getStartY() < (wheelSelect.getStartY() + itemHeight / 2)){
                    index = i;
                    break;
                }
                //假设startY在selectItem的中点以下,则将上一项作为选择项
                if(item.getStartY() >= (wheelSelect.getStartY() + itemHeight / 2) && item.getStartY() < (wheelSelect.getStartY() + itemHeight)){
                    index = i - 1;
                    break;
                }
            }
            //假设没找到或者其它因素。直接返回
            if(index == -1){
                return;
            }
            //得到偏移的位移
            float dy = wheelSelect.getStartY() - wheelItems.get(index).getStartY();
            //调整坐标
            for(WheelItem item : wheelItems){
                item.adjust(dy);
            }
            invalidate();
            // 调整
            adjust();
            //设置选择项
            int stringIndex = lists.indexOf(wheelItems.get(index).getText());
            if(stringIndex != -1){
                select = stringIndex;
                if(listener != null){
                    listener.onItemSelect(select);
                }
            }
        }
    
        /**
         * 调整Item移动和循环显示
         */
        private void adjust(){
            //假设向下滑动超出半个Item的高度,则调整容器
            if(wheelItems.get(0).getStartY() >= -itemHeight / 2 ){
                //移除最后一个Item重用
                WheelItem item = wheelItems.remove(wheelItems.size() - 1);
                //设置起点Y坐标
                item.setStartY(wheelItems.get(0).getStartY() - itemHeight);
                //得到文本在容器中的索引
                int index = lists.indexOf(wheelItems.get(0).getText());
                if(index == -1){
                    return;
                }
                index -= 1;
                if(index < 0){
                    index = lists.size() + index;
                }
                //设置文本
                item.setText(lists.get(index));
                //加入到最開始
                wheelItems.add(0, item);
                invalidate();
                return;
            }
            //假设向上滑超出半个Item的高度,则调整容器
            if(wheelItems.get(0).getStartY() <= (-itemHeight / 2 - itemHeight)){
                //移除第一个Item重用
                WheelItem item = wheelItems.remove(0);
                //设置起点Y坐标
                item.setStartY(wheelItems.get(wheelItems.size() - 1).getStartY() + itemHeight);
                //得到文本在容器中的索引
                int index = lists.indexOf(wheelItems.get(wheelItems.size() - 1).getText());
                if(index == -1){
                    return;
                }
                index += 1;
                if(index >= lists.size()){
                    index = 0;
                }
                //设置文本
                item.setText(lists.get(index));
                //加入到最后面
                wheelItems.add(item);
                invalidate();
                return;
            }
        }
    
        /**
         * 得到当前的选择项
         */
        public int getSelectItem(){
            return select;
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            //绘制每一项Item
            for(WheelItem item : wheelItems){
                item.onDraw(canvas);
            }
            //绘制阴影
            if(wheelSelect != null){
                wheelSelect.onDraw(canvas);
            }
        }
    
        /**
         * 设置监听器
         * @param listener
         * @return
         */
        public WheelView listener(OnWheelViewItemSelectListener listener){
            this.listener = listener;
            return this;
        }
    
        public interface OnWheelViewItemSelectListener{
            void onItemSelect(int index);
        }
    }
    


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

    package cc.wxf.view.wheel;
    
    import android.graphics.Canvas;
    import android.graphics.Paint;
    import android.graphics.RectF;
    
    /**
     * Created by ccwxf on 2016/3/31.
     */
    public class WheelItem {
        // 起点Y坐标、宽度、高度
        private float startY;
        private int width;
        private int height;
        //四点坐标
        private RectF rect = new RectF();
        //字体大小、颜色
        private int fontColor;
        private int fontSize;
        private String text;
        private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    
        public WheelItem(float startY, int width, int height, int fontColor, int fontSize, String text) {
            this.startY = startY;
            this.width = width;
            this.height = height;
            this.fontColor = fontColor;
            this.fontSize = fontSize;
            this.text = text;
            adjust(0);
        }
    
        /**
         * 依据Y坐标的变化值。调整四点坐标值
         * @param dy
         */
        public void adjust(float dy){
            startY += dy;
            rect.left = 0;
            rect.top = startY;
            rect.right = width;
            rect.bottom = startY + height;
        }
    
        public float getStartY() {
            return startY;
        }
    
        /**
         * 直接设置Y坐标属性,调整四点坐标属性
         * @param startY
         */
        public void setStartY(float startY) {
            this.startY = startY;
            rect.left = 0;
            rect.top = startY;
            rect.right = width;
            rect.bottom = startY + height;
        }
    
        public void setText(String text) {
            this.text = text;
        }
    
        public String getText() {
            return text;
        }
    
        public void onDraw(Canvas mCanvas){
            //设置钢笔属性
            mPaint.setTextSize(fontSize);
            mPaint.setColor(fontColor);
            //得到字体的宽度
            int textWidth = (int)mPaint.measureText(text);
            //drawText的绘制起点是左下角,y轴起点为baseLine
            Paint.FontMetrics metrics =  mPaint.getFontMetrics();
            int baseLine = (int)(rect.centerY() + (metrics.bottom - metrics.top) / 2 - metrics.bottom);
            //居中绘制
            mCanvas.drawText(text, rect.centerX() - textWidth / 2, baseLine, mPaint);
        }
    }
    


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

    package cc.wxf.view.wheel;
    
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.Rect;
    
    /**
     * Created by ccwxf on 2016/4/1.
     */
    public class WheelSelect {
        //黑框背景颜色
        public static final int COLOR_BACKGROUND = Color.parseColor("#77777777");
        //黑框的Y坐标起点、宽度、高度
        private int startY;
        private int width;
        private int height;
        //四点坐标
        private Rect rect = new Rect();
        //须要选择文本的颜色、大小、补白
        private String selectText;
        private int fontColor;
        private int fontSize;
        private int padding;
        private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    
        public WheelSelect(int startY, int width, int height, String selectText, int fontColor, int fontSize, int padding) {
            this.startY = startY;
            this.width = width;
            this.height = height;
            this.selectText = selectText;
            this.fontColor = fontColor;
            this.fontSize = fontSize;
            this.padding = padding;
            rect.left = 0;
            rect.top = startY;
            rect.right = width;
            rect.bottom = startY + height;
        }
    
        public int getStartY() {
            return startY;
        }
    
        public void setStartY(int startY) {
            this.startY = startY;
        }
    
        public void onDraw(Canvas mCanvas) {
            //绘制背景
            mPaint.setStyle(Paint.Style.FILL);
            mPaint.setColor(COLOR_BACKGROUND);
            mCanvas.drawRect(rect, mPaint);
            //绘制提醒文字
            if(selectText != null){
                //设置钢笔属性
                mPaint.setTextSize(fontSize);
                mPaint.setColor(fontColor);
                //得到字体的宽度
                int textWidth = (int)mPaint.measureText(selectText);
                //drawText的绘制起点是左下角,y轴起点为baseLine
                Paint.FontMetrics metrics =  mPaint.getFontMetrics();
                int baseLine = (int)(rect.centerY() + (metrics.bottom - metrics.top) / 2 - metrics.bottom);
                //在靠右边绘制文本
                mCanvas.drawText(selectText, rect.right - padding - textWidth, baseLine, mPaint);
            }
        }
    }
    


    源码就三个文件,非常easy。凝视也非常具体,接下来就是使用文件了:

            final WheelView wheelView = (WheelView) findViewById(R.id.wheelView);
            final List<String> lists = new ArrayList<>();
            for(int i = 0; i < 20; i++){
                lists.add("test:" + i);
            }
            wheelView.lists(lists).fontSize(35).showCount(5).selectTip("年").select(0).listener(new WheelView.OnWheelViewItemSelectListener() {
                @Override
                public void onItemSelect(int index) {
                    Log.d("cc", "current select:" + wheelView.getSelectItem() + " index :" + index + ",result=" + lists.get(index));
                }
            }).build();

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

    demoproject就不提供了,使用很easy。

  • 相关阅读:
    U盘分区 将一个u盘分为3个区
    InnoDB索引最通俗的解释
    Centos7 安全加固
    final/static
    Java继承,方法重写
    UnrealEngine4血溅效果
    UnrealEngine4第一人称射击游戏之触碰掉血与掉盔甲功能实现
    UnrealEngine4第一人称射击游戏UI
    String字符串
    构造方法
  • 原文地址:https://www.cnblogs.com/mfmdaoyou/p/7226953.html
Copyright © 2011-2022 走看看