Source code analysis of the WheelView component

We all know that there is a control in iOS---Wheel View, which is usually used to set the time/date, which is very convenient, but the Android SDK does not provide similar controls. Here is an introduction to how Android implements WheelView.

 

Code download : 

-- GitHub : https://github.com/han1202012/WheelViewDemo.git 

-- CSDN : http://download.csdn.net/detail/han1202012/8208997 ;

 

 

Blog summary :

 

Blog post content: This article completely analyzes all the source code of WheelView, including its adapter type, two callback interfaces (callback for selecting item change, and callback for starting and ending scrolling), and a detailed analysis of the source code of WheelView theme, including component width and height measurement, The addition of the gesture listener and the precise drawing method are the main purposes. It took nearly a week and it feels very worthwhile, so I will share it with you here;

 

How to use WheelView: Create WheelView component --> set the number of displayed items --> set the loop --> set the adapter --> set the listener;

 

Custom component width and height acquisition strategy: MeasureSpec maximum mode takes the smaller of the default value and the given value, the undefined mode takes the default value, and the precise mode takes the given value;

 

The custom component maintains various callback listener strategies: maintain the collection, place the listener in the collection, traverse the collection elements when calling back the interface, and call back the interface method of each element;

 

Custom component gesture listener addition method: create a gesture listener, pass the gesture listener into the gesture detector, and call back the onTouchEvent() method of the gesture listener in the onTouchEvent() method;

 

 

1. Introduction to WheelView

 

 

1. WheelView effect

 

Implement a WheelView control similar to IOS in Android: as shown in the figure 

 

 

2. WheelView usage process

 

 

(1) Introduction to the basic process

 

获取组件 --> 设置显示条目数 --> 设置循环 --> 设置适配器 --> 设置条目改变监听器 --> 设置滚动监听器

 

a. 创建 WheelView 组件 : 使用 构造方法 或者 从布局文件获取 WheelView 组件;

b. 设置显示条目数 : 调用 WheelView 组件对象的 setVisibleItems 方法 设置;

c. 设置是否循环 : 设置 WheelView 是否循环, 调用 setCyclic() 方法设置;

d. 设置适配器 : 调用 WheelView 组件的 setAdapter() 方法设置;

e. 设置条目改变监听器 : 调用 WheelView 组件对象的 addChangingListener() 方法设置; 

f. 设置滚动监听器 : 调用 WheelView 组件对象的 addScrollingListener() 方法设置;

 

 

(2) 代码实例

 

a. 创建 WheelView 对象 : 

 

        //创建 WheelView 组件
        final WheelView wheelLeft = new WheelView(context);


b. 设置 WheelView 显示条目数 : 

 

        //设置 WheelView 组件最多显示 5 个元素
        wheelLeft.setVisibleItems(5);


c. 设置 WheelView 是否滚动循环 : 

 

        //设置 WheelView 元素是否循环滚动
        wheelLeft.setCyclic(false);


d. 设置 WheelView 适配器 

 

        //设置 WheelView 适配器
        wheelLeft.setAdapter(new ArrayWheelAdapter<String>(left));


e. 设置条目改变监听器 : 

 

        //为左侧的 WheelView 设置条目改变监听器
        wheelLeft.addChangingListener(new OnWheelChangedListener() {
            @Override
            public void onChanged(WheelView wheel, int oldValue, int newValue) {
            	//设置右侧的 WheelView 的适配器
                wheelRight.setAdapter(new ArrayWheelAdapter<String>(right[newValue]));
                wheelRight.setCurrentItem(right[newValue].length / 2);
            }
        });


f. 设置滚动监听器 : 

 

        wheelLeft.addScrollingListener(new OnWheelScrollListener() {
			
			@Override
			public void onScrollingStarted(WheelView wheel) {
				// TODO Auto-generated method stub
				
			}
			
			@Override
			public void onScrollingFinished(WheelView wheel) {
				// TODO Auto-generated method stub
				
			}
		});

 

 

 

二. WheelView  适配器 监听器 相关接口分析

 

 

1. 适配器 分析

 

 

这里定义了一个适配器接口, 以及两个适配器类, 一个用于任意类型的数据集适配, 一个用于数字适配;

 

适配器操作 : 在 WheelView.java 中通过 setAdapter(WheelAdapter adapter) 和 getAdapter() 方法设置 获取 适配器;

-- 适配器常用操作 : 在 WheelView 中定义了 getItem(), getItemsCount(), getMaxmiumLength() 方法获取 适配器的相关信息;

 

    /**
     * 获取该 WheelView 的适配器
     * 
     * @return 
     * 		返回适配器
     */
    public WheelAdapter getAdapter() {
        return adapter;
    }

    /**
     * 设置适配器
     * 
     * @param adapter
     *            要设置的适配器
     */
    public void setAdapter(WheelAdapter adapter) {
        this.adapter = adapter;
        invalidateLayouts();
        invalidate();
    }



 

 

(1) 适配器接口 ( interface WheelAdapter )

 

适配器接口 : WheelAdapter;

-- 接口作用 : 该接口是所有适配器的接口, 适配器类都需要实现该接口;

 

接口抽象方法介绍 : 

-- getItemsCount() : 获取适配器数据集合中元素个数;

 

    /**
     * 获取条目的个数
     * 
     * @return 
     * 		WheelView 的条目个数
     */
    public int getItemsCount();



-- getItem(int index) : 获取适配器集合的中指定索引元素;

 

    /**
     * 根据索引位置获取 WheelView 的条目
     * 
     * @param index
     *            条目的索引
     * @return 
     * 		WheelView 上显示的条目的值
     */
    public String getItem(int index);



-- getMaximumLength() : 获取 WheelView 在界面上的显示宽度;

 

    /**
     * 获取条目的最大长度. 用来定义 WheelView 的宽度. 如果返回 -1, 就会使用默认宽度
     * 
     * @return 
     * 		条目的最大宽度 或者 -1
     */
    public int getMaximumLength();



 

(2) 数组适配器 ( class ArrayWheelAdapter<T> implements WheelAdapter )

 

适配器作用 : 该适配器可以传入任何数据类型的数组, 可以是 字符串数组, 也可以是任何对象的数组, 传入的数组作为适配器的数据源;

 

成员变量分析 : 

-- 数据源 : 

 

    /** 适配器的数据源 */
    private T items[];



-- WheelView 最大宽度 : 

 

    /** WheelView 的宽度 */
    private int length;



 

构造方法分析 : 

-- ArrayWheelAdapter(T items[], int length) : 传入 T 类型 对象数组, 以及 WheelView 的宽度;

 

    /**
     * 构造方法
     * 
     * @param items
     *            适配器数据源 集合 T 类型的数组
     * @param length
     *            适配器数据源 集合 T 数组长度
     */
    public ArrayWheelAdapter(T items[], int length) {
        this.items = items;
        this.length = length;
    }



-- ArrayWheelAdapter(T items[]) : 传入 T 类型对象数组, 宽度使用默认的宽度;

 

    /**
     * 构造方法
     * 
     * @param items
     *            适配器数据源集合 T 类型数组
     */
    public ArrayWheelAdapter(T items[]) {
        this(items, DEFAULT_LENGTH);
    }



实现的父类方法分析 :

--  getItem(int index) : 根据索引获取数组中对应位置的对象的字符串类型;

 

    @Override
    public String getItem(int index) {
    	//如果这个索引值合法, 就返回 item 数组对应的元素的字符串形式
        if (index >= 0 && index < items.length) {
            return items[index].toString();
        }
        return null;
    }



-- getItemsCount() : 获取数据集广大小, 直接返回数组大小;

 

    @Override
    public int getItemsCount() {
    	//返回 item 数组的长度
        return items.length;
    }



-- getMaximumLength() : 获取 WheelView 的最大宽度;

 

    @Override
    public int getMaximumLength() {
    	//返回 item 元素的宽度
        return length;
    }



 

(3) 数字适配器 ( class NumericWheelAdapter implements WheelAdapter )

 

NumericWheelAdapter 适配器作用 : 数字作为 WheelView 适配器的显示值;

 

 

成员变量分析 : 

-- 最小值 : WheelView 数值显示的最小值;

 

    /** 设置的最小值 */
    private int minValue;



-- 最大值 : WheelView 数值显示的最大值;

 

    /** 设置的最大值 */
    private int maxValue;


-- 格式化字符串 : 用于字符串的格式化;

 

    /** 格式化字符串, 用于格式化 货币, 科学计数, 十六进制 等格式 */
    private String format;



构造方法分析 : 

-- NumericWheelAdapter() : 默认的构造方法, 使用默认的最大最小值;

 

    /**
     * 默认的构造方法, 使用默认的最大最小值
     */
    public NumericWheelAdapter() {
        this(DEFAULT_MIN_VALUE, DEFAULT_MAX_VALUE);
    }



-- NumericWheelAdapter(int minValue, int maxValue) : 传入一个最大最小值;

 

    /**
     * 构造方法
     * 
     * @param minValue
     *            最小值
     * @param maxValue
     *            最大值
     */
    public NumericWheelAdapter(int minValue, int maxValue) {
        this(minValue, maxValue, null);
    }



-- NumericWheelAdapter(int minValue, int maxValue, String format) : 传入最大最小值, 以及数字格式化方式;

 

    /**
     * 构造方法
     * 
     * @param minValue
     *            最小值
     * @param maxValue
     *            最大值
     * @param format
     *            格式化字符串
     */
    public NumericWheelAdapter(int minValue, int maxValue, String format) {
        this.minValue = minValue;
        this.maxValue = maxValue;
        this.format = format;
    }



实现的父类方法 : 

-- 获取条目 : 如果需要格式化, 先进行格式化;

 

    @Override
    public String getItem(int index) {
    	String result = "";
        if (index >= 0 && index < getItemsCount()) {
            int value = minValue + index;
            //如果 format 不为 null, 那么格式化字符串, 如果为 null, 直接返回数字
            if(format != null){
            	result = String.format(format, value);
            }else{
            	result = Integer.toString(value);
            }
            return result;
        }
        return null;
    }


-- 获取元素个数 : 

 

    @Override
    public int getItemsCount() {
    	//返回数字总个数
        return maxValue - minValue + 1;
    }


-- 获取 WheelView 最大宽度 : 

 

    @Override
    public int getMaximumLength() {
    	//获取 最大值 和 最小值 中的 较大的数字
        int max = Math.max(Math.abs(maxValue), Math.abs(minValue));
        //获取这个数字 的 字符串形式的 字符串长度
        int maxLen = Integer.toString(max).length();
        if (minValue < 0) {
            maxLen++;
        }
        return maxLen;
    }



 

2. 监听器相关接口

 

 

(1) 条目改变监听器 ( interface OnWheelChangedListener )

 

监听器作用 : 在 WheelView 条目改变的时候, 回调该监听器的接口方法, 执行条目改变对应的操作;

 

接口方法介绍 : 

-- onChanged(WheelView wheel, int oldValue, int newValue) : 传入 WheelView 组件对象, 以及 旧的 和 新的 条目值索引;

 

    /**
     * 当前条目改变时回调该方法
     * 
     * @param wheel
     *            条目改变的 WheelView 对象
     * @param oldValue
     *            WheelView 旧的条目值
     * @param newValue
     *            WheelView 新的条目值
     */
    void onChanged(WheelView wheel, int oldValue, int newValue);



 

(2) 滚动监听器 ( interface OnWheelScrollListener )

 

滚动监听器作用 : 在 WheelView 滚动动作 开始 和 结束的时候回调对应的方法, 在对应方法中进行相应的操作;

 

 

接口方法介绍 : 

-- 开始滚动方法 : 在滚动开始的时候回调该方法;

 

    /**
     * 在 WheelView 滚动开始的时候回调该接口
     * 
     * @param wheel
     *            开始滚动的 WheelView 对象
     */
    void onScrollingStarted(WheelView wheel);



-- 停止滚动方法 : 在滚动结束的时候回调该方法;

 

    /**
     * 在 WheelView 滚动结束的时候回调该接口
     * 
     * @param wheel
     *            结束滚动的 WheelView 对象
     */
    void onScrollingFinished(WheelView wheel);



 

三. WheelView 解析

 

 

1. 触摸 点击 手势 动作操作控制组件 模块

 

 

(1) 创建手势监听器

 

手势监听器创建及对应方法 : 

-- onDown(MotionEvent e) : 在按下的时候回调该方法, e 参数是按下的事件;

-- onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) : 滚动的时候回调该方法, e1 滚动第一次按下事件, e2 当前滚动的触摸事件, X 上一次滚动到这一次滚动 x 轴距离, Y 上一次滚动到这一次滚动 y 轴距离;

-- onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) : 快速急冲滚动时回调的方法, e1 e2 与上面参数相同, velocityX 是手势在 x 轴的速度, velocityY 是手势在 y 轴的速度;

-- 代码示例 : 

 

        /*
         * 手势监听器监听到 滚动操作后回调
         * 
         * 参数解析 : 
         * MotionEvent e1 : 触发滚动时第一次按下的事件
         * MotionEvent e2 : 触发当前滚动的移动事件
         * float distanceX : 自从上一次调用 该方法 到这一次 x 轴滚动的距离, 
         * 				注意不是 e1 到 e2 的距离, e1 到 e2 的距离是从开始滚动到现在的滚动距离
         * float distanceY : 自从上一次回调该方法到这一次 y 轴滚动的距离
         * 
         * 返回值 : 如果事件成功触发, 执行完了方法中的操作, 返回true, 否则返回 false 
         * (non-Javadoc)
         * @see android.view.GestureDetector.SimpleOnGestureListener#onScroll(android.view.MotionEvent, android.view.MotionEvent, float, float)
         */
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        	//开始滚动, 并回调滚动监听器集合中监听器的 开始滚动方法
            startScrolling();
            doScroll((int) -distanceY);
            return true;
        }

        /*
         * 当一个急冲手势发生后 回调该方法, 会计算出该手势在 x 轴 y 轴的速率
         * 
         * 参数解析 : 
         * -- MotionEvent e1 : 急冲动作的第一次触摸事件;
         * -- MotionEvent e2 : 急冲动作的移动发生的时候的触摸事件;
         * -- float velocityX : x 轴的速率
         * -- float velocityY : y 轴的速率
         * 
         * 返回值 : 如果执行完毕返回 true, 否则返回false, 这个就是自己定义的
         * 
         * (non-Javadoc)
         * @see android.view.GestureDetector.SimpleOnGestureListener#onFling(android.view.MotionEvent, android.view.MotionEvent, float, float)
         */
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        	//计算上一次的 y 轴位置, 当前的条目高度 加上 剩余的 不够一行高度的那部分
            lastScrollY = currentItem * getItemHeight() + scrollingOffset;
            //如果可以循环最大值是无限大, 不能循环就是条目数的高度值
            int maxY = isCyclic ? 0x7FFFFFFF : adapter.getItemsCount() * getItemHeight();
            int minY = isCyclic ? -maxY : 0;
            /*
             * Scroll 开始根据一个急冲手势滚动, 滚动的距离与初速度有关
             * 参数介绍 : 
             * -- int startX : 开始时的 X轴位置
             * -- int startY : 开始时的 y轴位置
             * -- int velocityX : 急冲手势的 x 轴的初速度, 单位 px/s
             * -- int velocityY : 急冲手势的 y 轴的初速度, 单位 px/s
             * -- int minX : x 轴滚动的最小值
             * -- int maxX : x 轴滚动的最大值
             * -- int minY : y 轴滚动的最小值
             * -- int maxY : y 轴滚动的最大值
             */
            scroller.fling(0, lastScrollY, 0, (int) -velocityY / 2, 0, 0, minY, maxY);
            setNextMessage(MESSAGE_SCROLL);
            return true;
        }
    };



 

(2) 创建手势探测器

 

手势探测器创建 : 调用 其构造函数, 传入 上下文对象 和 手势监听器对象;

-- 禁止长按操作 : 调用 setIsLongpressEnabled(false) 方法, 禁止长按操作, 因为 长按操作会屏蔽滚动事件;

 

    	//创建一个手势处理
        gestureDetector = new GestureDetector(context, gestureListener);
        /*
         * 是否允许长按操作, 
         * 如果设置为 true 用户按下不松开, 会返回一个长按事件, 
         * 如果设置为 false, 按下不松开滑动的话 会收到滚动事件.
         */
        gestureDetector.setIsLongpressEnabled(false);



 

(3) 将手势探测器 与 组件结合

 

关联手势探测器 与 组件 : 在组件的 onTouchEvent(MotionEvent event) 方法中, 调用手势探测器的 gestureDetector.onTouchEvent(event) 方法即可;

 

    /*
     * 继承自 View 的触摸事件, 当出现触摸事件的时候, 就会回调该方法
     * (non-Javadoc)
     * @see android.view.View#onTouchEvent(android.view.MotionEvent)
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
    	//获取适配器
        WheelAdapter adapter = getAdapter();
        if (adapter == null) {
            return true;
        }

        /*
         * gestureDetector.onTouchEvent(event) : 分析给定的动作, 如果可用, 调用 手势检测器的 onTouchEvent 方法
         * -- 参数解析 : ev , 触摸事件
         * -- 返回值 : 如果手势监听器成功执行了该方法, 返回true, 如果执行出现意外 返回 false;
         */
        if (!gestureDetector.onTouchEvent(event) && event.getAction() == MotionEvent.ACTION_UP) {
            justify();
        }
        return true;
    }

 

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326850675&siteId=291194637