【Android 应用开发】 自定义组件 宽高适配方法, 手势监听器操作组件, 回调接口维护策略, 绘制方法分析 -- 基于 WheelView 组件分析自定义组件

  4952人阅读  评论(4)  收藏  举报
WheelView

目录(?)[+]


博客地址 http://blog.csdn.net/shulianghan/article/details/41520569


代码下载 : 

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

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



博客总结 :

 

博文内容 : 本文完整地分析了 WheelView 所有的源码, 包括其适配器类型两种回调接口 (选中条目改变回调, 和开始结束滚动回调), 以及详细的分析了 WheelView 主题源码, 其中 组件宽高测量手势监听器添加, 以及精准的绘图方法是主要目的, 花了将近1周时间, 感觉很值, 在这里分享给大家;


WheelView 使用方法 创建 WheelView 组件 --> 设置显示条目数 --> 设置循环 --> 设置适配器 --> 设置监听器 ;


自定义组件宽高获取策略 : MeasureSpec 最大模式 取 默认值 和 给定值中较小的那个未定义模式取默认值精准模式取 给定值;


自定义组件维护各种回调监听器策略 : 维护集合, 将监听器置于集合中, 回调接口时遍历集合元素, 回调每个元素的接口方法;


自定义组件手势监听器添加方法 : 创建手势监听器, 将手势监听器传入手势探测器, 在 onTouchEvent() 方法中回调手势监听器的 onTouchEvent()方法;



一. WheelView 简介



1. WheelView 效果


在 Android 中实现类似与 IOS 的 WheelView 控件 : 如图 




2. WheelView 使用流程



(1) 基本流程简介

 

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


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

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

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

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

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

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



(2) 代码实例


a. 创建 WheelView 对象 : 

[java]  view plain copy
  1. //创建 WheelView 组件  
  2. final WheelView wheelLeft = new WheelView(context);  

b. 设置 WheelView 显示条目数 : 

[java]  view plain copy
  1. //设置 WheelView 组件最多显示 5 个元素  
  2. wheelLeft.setVisibleItems(5);  

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

[java]  view plain copy
  1. //设置 WheelView 元素是否循环滚动  
  2. wheelLeft.setCyclic(false);  

d. 设置 WheelView 适配器 

[java]  view plain copy
  1. //设置 WheelView 适配器  
  2. wheelLeft.setAdapter(new ArrayWheelAdapter(left));  

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

[java]  view plain copy
  1. //为左侧的 WheelView 设置条目改变监听器  
  2. wheelLeft.addChangingListener(new OnWheelChangedListener() {  
  3.     @Override  
  4.     public void onChanged(WheelView wheel, int oldValue, int newValue) {  
  5.         //设置右侧的 WheelView 的适配器  
  6.         wheelRight.setAdapter(new ArrayWheelAdapter(right[newValue]));  
  7.         wheelRight.setCurrentItem(right[newValue].length / 2);  
  8.     }  
  9. });  

f. 设置滚动监听器 : 

[java]  view plain copy
  1.      wheelLeft.addScrollingListener(new OnWheelScrollListener() {  
  2.   
  3. @Override  
  4. public void onScrollingStarted(WheelView wheel) {  
  5.     // TODO Auto-generated method stub  
  6.       
  7. }  
  8.   
  9. @Override  
  10. public void onScrollingFinished(WheelView wheel) {  
  11.     // TODO Auto-generated method stub  
  12.       
  13. }  
  14. );  




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



1. 适配器 分析



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


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

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

[java]  view plain copy
  1. /** 
  2.  * 获取该 WheelView 的适配器 
  3.  *  
  4.  * @return  
  5.  *      返回适配器 
  6.  */  
  7. public WheelAdapter getAdapter() {  
  8.     return adapter;  
  9. }  
  10.   
  11. /** 
  12.  * 设置适配器 
  13.  *  
  14.  * @param adapter 
  15.  *            要设置的适配器 
  16.  */  
  17. public void setAdapter(WheelAdapter adapter) {  
  18.     this.adapter = adapter;  
  19.     invalidateLayouts();  
  20.     invalidate();  
  21. }  




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


适配器接口 : WheelAdapter;

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


接口抽象方法介绍 : 

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

[java]  view plain copy
  1. /** 
  2.  * 获取条目的个数 
  3.  *  
  4.  * @return  
  5.  *      WheelView 的条目个数 
  6.  */  
  7. public int getItemsCount();  


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

[java]  view plain copy
  1. /** 
  2.  * 根据索引位置获取 WheelView 的条目 
  3.  *  
  4.  * @param index 
  5.  *            条目的索引 
  6.  * @return  
  7.  *      WheelView 上显示的条目的值 
  8.  */  
  9. public String getItem(int index);  


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

[java]  view plain copy
  1. /** 
  2.  * 获取条目的最大长度. 用来定义 WheelView 的宽度. 如果返回 -1, 就会使用默认宽度 
  3.  *  
  4.  * @return  
  5.  *      条目的最大宽度 或者 -1 
  6.  */  
  7. public int getMaximumLength();  



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


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


成员变量分析 

-- 数据源 

[java]  view plain copy
  1. /** 适配器的数据源 */  
  2. private T items[];  


-- WheelView 最大宽度 

[java]  view plain copy
  1. /** WheelView 的宽度 */  
  2. private int length;  



构造方法分析 

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

[java]  view plain copy
  1. /** 
  2.  * 构造方法 
  3.  *  
  4.  * @param items 
  5.  *            适配器数据源 集合 T 类型的数组 
  6.  * @param length 
  7.  *            适配器数据源 集合 T 数组长度 
  8.  */  
  9. public ArrayWheelAdapter(T items[], int length) {  
  10.     this.items = items;  
  11.     this.length = length;  
  12. }  


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

[java]  view plain copy
  1. /** 
  2.  * 构造方法 
  3.  *  
  4.  * @param items 
  5.  *            适配器数据源集合 T 类型数组 
  6.  */  
  7. public ArrayWheelAdapter(T items[]) {  
  8.     this(items, DEFAULT_LENGTH);  
  9. }  


实现的父类方法分析 :

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

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


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

[java]  view plain copy
  1. @Override  
  2. public int getItemsCount() {  
  3.     //返回 item 数组的长度  
  4.     return items.length;  
  5. }  


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

[java]  view plain copy
  1. @Override  
  2. public int getMaximumLength() {  
  3.     //返回 item 元素的宽度  
  4.     return length;  
  5. }  



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


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



成员变量分析 : 

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

[java]  view plain copy
  1. /** 设置的最小值 */  
  2. private int minValue;  


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

[java]  view plain copy
  1. /** 设置的最大值 */  
  2. private int maxValue;  

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

[java]  view plain copy
  1. /** 格式化字符串, 用于格式化 货币, 科学计数, 十六进制 等格式 */  
  2. private String format;  


构造方法分析 : 

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

[java]  view plain copy
  1. /** 
  2.  * 默认的构造方法, 使用默认的最大最小值 
  3.  */  
  4. public NumericWheelAdapter() {  
  5.     this(DEFAULT_MIN_VALUE, DEFAULT_MAX_VALUE);  
  6. }  


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

[java]  view plain copy
  1. /** 
  2.  * 构造方法 
  3.  *  
  4.  * @param minValue 
  5.  *            最小值 
  6.  * @param maxValue 
  7.  *            最大值 
  8.  */  
  9. public NumericWheelAdapter(int minValue, int maxValue) {  
  10.     this(minValue, maxValue, null);  
  11. }  


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

[java]  view plain copy
  1. /** 
  2.  * 构造方法 
  3.  *  
  4.  * @param minValue 
  5.  *            最小值 
  6.  * @param maxValue 
  7.  *            最大值 
  8.  * @param format 
  9.  *            格式化字符串 
  10.  */  
  11. public NumericWheelAdapter(int minValue, int maxValue, String format) {  
  12.     this.minValue = minValue;  
  13.     this.maxValue = maxValue;  
  14.     this.format = format;  
  15. }  


实现的父类方法 : 

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

[java]  view plain copy
  1. @Override  
  2. public String getItem(int index) {  
  3.     String result = "";  
  4.     if (index >= 0 && index < getItemsCount()) {  
  5.         int value = minValue + index;  
  6.         //如果 format 不为 null, 那么格式化字符串, 如果为 null, 直接返回数字  
  7.         if(format != null){  
  8.             result = String.format(format, value);  
  9.         }else{  
  10.             result = Integer.toString(value);  
  11.         }  
  12.         return result;  
  13.     }  
  14.     return null;  
  15. }  

-- 获取元素个数 : 

[java]  view plain copy
  1. @Override  
  2. public int getItemsCount() {  
  3.     //返回数字总个数  
  4.     return maxValue - minValue + 1;  
  5. }  

-- 获取 WheelView 最大宽度 

[java]  view plain copy
  1. @Override  
  2. public int getMaximumLength() {  
  3.     //获取 最大值 和 最小值 中的 较大的数字  
  4.     int max = Math.max(Math.abs(maxValue), Math.abs(minValue));  
  5.     //获取这个数字 的 字符串形式的 字符串长度  
  6.     int maxLen = Integer.toString(max).length();  
  7.     if (minValue < 0) {  
  8.         maxLen++;  
  9.     }  
  10.     return maxLen;  
  11. }  



2. 监听器相关接口



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


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


接口方法介绍 : 

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

[java]  view plain copy
  1. /** 
  2.  * 当前条目改变时回调该方法 
  3.  *  
  4.  * @param wheel 
  5.  *            条目改变的 WheelView 对象 
  6.  * @param oldValue 
  7.  *            WheelView 旧的条目值 
  8.  * @param newValue 
  9.  *            WheelView 新的条目值 
  10.  */  
  11. void onChanged(WheelView wheel, int oldValue, int newValue);  



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


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



接口方法介绍 

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

[java]  view plain copy
  1. /** 
  2.  * 在 WheelView 滚动开始的时候回调该接口 
  3.  *  
  4.  * @param wheel 
  5.  *            开始滚动的 WheelView 对象 
  6.  */  
  7. void onScrollingStarted(WheelView wheel);  


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

[java]  view plain copy
  1. /** 
  2.  * 在 WheelView 滚动结束的时候回调该接口 
  3.  *  
  4.  * @param wheel 
  5.  *            结束滚动的 WheelView 对象 
  6.  */  
  7. 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 轴的速度;

-- 代码示例 : 

[java]  view plain copy
  1.     /* 
  2.      * 手势监听器监听到 滚动操作后回调 
  3.      *  
  4.      * 参数解析 :  
  5.      * MotionEvent e1 : 触发滚动时第一次按下的事件 
  6.      * MotionEvent e2 : 触发当前滚动的移动事件 
  7.      * float distanceX : 自从上一次调用 该方法 到这一次 x 轴滚动的距离,  
  8.      *              注意不是 e1 到 e2 的距离, e1 到 e2 的距离是从开始滚动到现在的滚动距离 
  9.      * float distanceY : 自从上一次回调该方法到这一次 y 轴滚动的距离 
  10.      *  
  11.      * 返回值 : 如果事件成功触发, 执行完了方法中的操作, 返回true, 否则返回 false  
  12.      * (non-Javadoc) 
  13.      * @see android.view.GestureDetector.SimpleOnGestureListener#onScroll(android.view.MotionEvent, android.view.MotionEvent, float, float) 
  14.      */  
  15.     public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {  
  16.         //开始滚动, 并回调滚动监听器集合中监听器的 开始滚动方法  
  17.         startScrolling();  
  18.         doScroll((int) -distanceY);  
  19.         return true;  
  20.     }  
  21.   
  22.     /* 
  23.      * 当一个急冲手势发生后 回调该方法, 会计算出该手势在 x 轴 y 轴的速率 
  24.      *  
  25.      * 参数解析 :  
  26.      * -- MotionEvent e1 : 急冲动作的第一次触摸事件; 
  27.      * -- MotionEvent e2 : 急冲动作的移动发生的时候的触摸事件; 
  28.      * -- float velocityX : x 轴的速率 
  29.      * -- float velocityY : y 轴的速率 
  30.      *  
  31.      * 返回值 : 如果执行完毕返回 true, 否则返回false, 这个就是自己定义的 
  32.      *  
  33.      * (non-Javadoc) 
  34.      * @see android.view.GestureDetector.SimpleOnGestureListener#onFling(android.view.MotionEvent, android.view.MotionEvent, float, float) 
  35.      */  
  36.     public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {  
  37.         //计算上一次的 y 轴位置, 当前的条目高度 加上 剩余的 不够一行高度的那部分  
  38.         lastScrollY = currentItem * getItemHeight() + scrollingOffset;  
  39.         //如果可以循环最大值是无限大, 不能循环就是条目数的高度值  
  40.         int maxY = isCyclic ? 0x7FFFFFFF : adapter.getItemsCount() * getItemHeight();  
  41.         int minY = isCyclic ? -maxY : 0;  
  42.         /* 
  43.          * Scroll 开始根据一个急冲手势滚动, 滚动的距离与初速度有关 
  44.          * 参数介绍 :  
  45.          * -- int startX : 开始时的 X轴位置 
  46.          * -- int startY : 开始时的 y轴位置 
  47.          * -- int velocityX : 急冲手势的 x 轴的初速度, 单位 px/s 
  48.          * -- int velocityY : 急冲手势的 y 轴的初速度, 单位 px/s 
  49.          * -- int minX : x 轴滚动的最小值 
  50.          * -- int maxX : x 轴滚动的最大值 
  51.          * -- int minY : y 轴滚动的最小值 
  52.          * -- int maxY : y 轴滚动的最大值 
  53.          */  
  54.         scroller.fling(0, lastScrollY, 0, (int) -velocityY / 200, minY, maxY);  
  55.         setNextMessage(MESSAGE_SCROLL);  
  56.         return true;  
  57.     }  
  58. };  



(2) 创建手势探测器


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

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

[java]  view plain copy
  1. //创建一个手势处理  
  2.    gestureDetector = new GestureDetector(context, gestureListener);  
  3.    /* 
  4.     * 是否允许长按操作,  
  5.     * 如果设置为 true 用户按下不松开, 会返回一个长按事件,  
  6.     * 如果设置为 false, 按下不松开滑动的话 会收到滚动事件. 
  7.     */  
  8.    gestureDetector.setIsLongpressEnabled(false);  



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


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

[java]  view plain copy
  1. /* 
  2.  * 继承自 View 的触摸事件, 当出现触摸事件的时候, 就会回调该方法 
  3.  * (non-Javadoc) 
  4.  * @see android.view.View#onTouchEvent(android.view.MotionEvent) 
  5.  */  
  6. @Override  
  7. public boolean onTouchEvent(MotionEvent event) {  
  8.     //获取适配器  
  9.     WheelAdapter adapter = getAdapter();  
  10.     if (adapter == null) {  
  11.         return true;  
  12.     }  
  13.   
  14.     /* 
  15.      * gestureDetector.onTouchEvent(event) : 分析给定的动作, 如果可用, 调用 手势检测器的 onTouchEvent 方法 
  16.      * -- 参数解析 : ev , 触摸事件 
  17.      * -- 返回值 : 如果手势监听器成功执行了该方法, 返回true, 如果执行出现意外 返回 false; 
  18.      */  
  19.     if (!gestureDetector.onTouchEvent(event) && event.getAction() == MotionEvent.ACTION_UP) {  
  20.         justify();  
  21.     }  
  22.     return true;  
  23. }  




2. Scroller 简介



(1) Scroller 简介 


Scroller 通用作用 : Scroller 组件并不是一个布局组件, 该组件是运行在后台的, 通过一些方法设定 Scroller 对象 的操作 或者 动画, 然后让 Scroller 运行在后台中 用于模拟滚动操作, 在适当的时机 获取该对象的坐标信息, 这些信息是在后台运算出来的;


Scroller 在本 View 中作用 : Android 的这个自定义的 WheelView 组件, 可以平滑的滚动, 当我们做一个加速滑动时, 会根据速度计算出滑动的距离, 这些数据都是在 Scroller 中计算出来的;



(2) 设定 Scroller 对象的动作参数 


终止滚动 : 

-- 终止滚动 跳转到目标位置 : 终止平缓的动画, 直接跳转到最终的 x y 轴的坐标位置;

[java]  view plain copy
  1. public void abortAnimation()  

-- 终止滚动 停止在当前位置 : 强行结束 Scroll 的滚动;

[java]  view plain copy
  1. public final void forceFinished(boolean finished)  


设置滚动参数 : 

-- 设置最终 x 轴坐标 : 

[java]  view plain copy
  1. public void setFinalX(int newX)  

-- 设置最终 y 轴坐标 : 

[java]  view plain copy
  1. public void setFinalY(int newY)  

-- 设置滚动摩擦力 : 

[java]  view plain copy
  1. public final void setFriction(float friction)  


设置动作 

-- 开始滚动 : 传入参数 开始 x 位置, 开始 y 位置, x 轴滚动距离, y 轴滚动距离;

[java]  view plain copy
  1. public void startScroll(int startX, int startY, int dx, int dy)  
--  开始滚动 设定时间 : 最后一个参数是时间, 单位是 ms;

[java]  view plain copy
  1. public void startScroll(int startX, int startY, int dx, int dy, int duration)  
--  急冲滚动 : 根据一个 急冲 手势进行滚动, 传入参数 : x轴开始位置, y轴开始位置, x 轴速度, y 轴速度, x 轴最小速度, x 轴最大速度, y 轴最小速度, y 轴最大速度;

[java]  view plain copy
  1. public void fling(int startX, int startY, int velocityX, int velocityY,  
  2.             int minX, int maxX, int minY, int maxY)  


延长滚动时间 : 延长滚动的时间, 让滚动滚的更远一些;

[java]  view plain copy
  1. public void extendDuration(int extend)  



(3) 获取 Scroll 后台运行参数 



获取当前数据 : 

-- 获取当前 x 轴坐标 : 

[java]  view plain copy
  1. public final int getCurrX()  

-- 获取当前 y 轴坐标 : 

[java]  view plain copy
  1. public final int getCurrY()  

-- 获取当前速度 : 

[java]  view plain copy
  1. public float getCurrVelocity()  


获取开始结束时的数据  : 

-- 获取开始 x 轴坐标 : 

[java]  view plain copy
  1. public final int getStartX()  

-- 获取开始 y 轴坐标 : 

[java]  view plain copy
  1. public final int getStartY()  

-- 获取最终 x 轴坐标 : 该参数只在急冲滚动时有效;

[java]  view plain copy
  1. public final int getFinalX()  

-- 获取最终 y 轴坐标 : 该参数只在急冲滚动时有效;

[java]  view plain copy
  1. public final int getFinalY()  


查看是否滚动完毕 : 

[java]  view plain copy
  1. public final boolean isFinished()  


获取从开始滚动到现在的时间 : 

[java]  view plain copy
  1. public int timePassed()  


获取新位置 : 调用该方法可以获取新位置, 如果返回 true 说明动画还没执行完毕;

[java]  view plain copy
  1. public boolean computeScrollOffset()  



(4) Scroll 在 WheelView 中的运用


Scroller 创建 

[java]  view plain copy
  1. //使用默认的 时间 和 插入器 创建一个滚动器  
  2. scroller = new Scroller(context);  


手势监听器 SimpleOnGestureListener 对象中的 onDown() 方法 : 如果滚动还在执行, 那么强行停止 Scroller 滚动;

[java]  view plain copy
  1. //按下操作  
  2.    public boolean onDown(MotionEvent e) {  
  3.     //如果滚动在执行  
  4.        if (isScrollingPerformed) {  
  5.         //滚动强制停止, 按下的时候不能继续滚动  
  6.            scroller.forceFinished(true);  
  7.            //清理信息  
  8.            clearMessages();  
  9.            return true;  
  10.        }  
  11.        return false;  
  12.    }  


当手势监听器 SimpleOnGestureListener 对象中有急冲动作时 onFling() 方法中 : 手势监听器监听到了 急冲动作, 那么 Scroller 也进行对应操作;

[java]  view plain copy
  1. public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {  
  2.     //计算上一次的 y 轴位置, 当前的条目高度 加上 剩余的 不够一行高度的那部分  
  3.     lastScrollY = currentItem * getItemHeight() + scrollingOffset;  
  4.     //如果可以循环最大值是无限大, 不能循环就是条目数的高度值  
  5.     int maxY = isCyclic ? 0x7FFFFFFF : adapter.getItemsCount() * getItemHeight();  
  6.     int minY = isCyclic ? -maxY : 0;  
  7.     /* 
  8.      * Scroll 开始根据一个急冲手势滚动, 滚动的距离与初速度有关 
  9.      * 参数介绍 :  
  10.      * -- int startX : 开始时的 X轴位置 
  11.      * -- int startY : 开始时的 y轴位置 
  12.      * -- int velocityX : 急冲手势的 x 轴的初速度, 单位 px/s 
  13.      * -- int velocityY : 急冲手势的 y 轴的初速度, 单位 px/s 
  14.      * -- int minX : x 轴滚动的最小值 
  15.      * -- int maxX : x 轴滚动的最大值 
  16.      * -- int minY : y 轴滚动的最小值 
  17.      * -- int maxY : y 轴滚动的最大值 
  18.      */  
  19.     scroller.fling(0, lastScrollY, 0, (int) -velocityY / 200, minY, maxY);  
  20.     setNextMessage(MESSAGE_SCROLL);  
  21.     return true;  
  22. }  


动画控制 Handler 中 : 

-- 滚动 : 获取当前 Scroller 的 y 轴位置, 与上一次的 y 轴位置对比, 如果 间距 delta 不为0, 就滚动;  

-- 查看是否停止 : 如果现在距离 到 最终距离 小于最小滚动距离, 强制停止;

-- 执行 msg.what 指令 : 如果需要停止, 强制停止, 否则调整坐标;

[java]  view plain copy
  1. /** 
  2.  * 动画控制器 
  3.  *  animation handler 
  4.  *   
  5.  *  可能会造成内存泄露 : 添加注解 HandlerLeak 
  6.  *  Handler 类应该应该为static类型,否则有可能造成泄露。 
  7.  *  在程序消息队列中排队的消息保持了对目标Handler类的应用。 
  8.  *  如果Handler是个内部类,那 么它也会保持它所在的外部类的引用。 
  9.  *  为了避免泄露这个外部类,应该将Handler声明为static嵌套类,并且使用对外部类的弱应用。 
  10.  */  
  11. @SuppressLint("HandlerLeak")  
  12. vate Handler animationHandler = new Handler() {  
  13.     public void handleMessage(Message msg) {  
  14.         //回调该方法获取当前位置, 如果返回true, 说明动画还没有执行完毕  
  15.         scroller.computeScrollOffset();  
  16.         //获取当前 y 位置  
  17.         int currY = scroller.getCurrY();  
  18.         //获取已经滚动了的位置, 使用上一次位置 减去 当前位置  
  19.         int delta = lastScrollY - currY;  
  20.         lastScrollY = currY;  
  21.         if (delta != 0) {  
  22.             //改变值不为 0 , 继续滚动  
  23.             doScroll(delta);  
  24.         }  
  25.   
  26.         /* 
  27.          * 如果滚动到了指定的位置, 滚动还没有停止 
  28.          * 这时需要强制停止 
  29.          */  
  30.         if (Math.abs(currY - scroller.getFinalY()) < MIN_DELTA_FOR_SCROLLING) {  
  31.             currY = scroller.getFinalY();  
  32.             scroller.forceFinished(true);  
  33.         }  
  34.           
  35.         /* 
  36.          * 如果滚动没有停止 
  37.          * 再向 Handler 发送一个停止 
  38.          */  
  39.         if (!scroller.isFinished()) {  
  40.             animationHandler.sendEmptyMessage(msg.what);  
  41.         } else if (msg.what == MESSAGE_SCROLL) {  
  42.             justify();  
  43.         } else {  
  44.             finishScrolling();  
  45.         }  
  46.     }  
  47. };  




3. StaticLayout 布局容器



(1) StaticLayout 解析


StaticLayout 解析 : 该组件用于显示文本, 一旦该文本被显示后, 就不能再编辑, 如果想要修改文本, 使用 DynamicLayout 布局即可; 

-- 使用场景 : 一般情况下不会使用该组件, 当想要自定义组件 或者 想要使用 Canvas 绘制文本时 才使用该布局;



常用方法解析 : 

-- 获取底部 Padding : 获取底部 到最后一行文字的 间隔, 单位是 px;

[java]  view plain copy
  1. public int getBottomPadding()  

-- 获取顶部 Padding : 

[java]  view plain copy
  1. public int getTopPadding()  

--  获取省略个数 : 获取某一行需要省略的字符个数;

[java]  view plain copy
  1. public int getEllipsisCount(int line)  
--  获取省略开始位置 : 获取某一行要省略的字符串的第一个位置索引;

[java]  view plain copy
  1. public int getEllipsisStart(int line)  
--  获取省略的宽度 : 获取某一行省略字符串的宽度, 单位 px;

[java]  view plain copy
  1. public int getEllipsisStart(int line)  
--  获取是否处理特殊符号 : 

[java]  view plain copy
  1. public boolean getLineContainsTab(int line)  
--  获取文字的行数 : 

[java]  view plain copy
  1. public int getLineCount()  
--  获取顶部位置 : 获取某一行顶部的位置;

[java]  view plain copy
  1. public int getLineTop(int line)  
--  获取某一行底部位置 : 

[java]  view plain copy
  1. public int getLineDescent(int line)  
--  获取行的方向 : 字符串从左至右 还是从右至左;

[java]  view plain copy
  1. public final Directions getLineDirections(int line)  
--  获取某行第一个字符索引 : 获取的是 某一行 第一个字符 在整个字符串的索引;

[java]  view plain copy
  1. public int getLineStart(int line)  
--  获取该行段落方向 : 获取该行文字方向, 左至右 或者 右至左;

[java]  view plain copy
  1. public int getParagraphDirection(int line)  
--  获取某个垂直位置显示的行数 : 

[java]  view plain copy
  1. public int getLineForVertical(int vertical)  



(2) 布局显示


布局创建 : 

-- 三种布局 : WheelView 中涉及到了三种 StaticLayout 布局, 普通条目布局 itemLayout, 选中条目布局 valueLayout, 标签布局 labelLayout;

-- 创建时机 : 在 View 组件 每次 onMeasure() 和 onDraw() 方法中都要重新创建对应布局;

-- 创建布局源码 : 

[java]  view plain copy
  1. /** 
  2.  * 创建布局 
  3.  *  
  4.  * @param widthItems 
  5.  *            布局条目宽度 
  6.  * @param widthLabel 
  7.  *            label 宽度 
  8.  */  
  9. private void createLayouts(int widthItems, int widthLabel) {  
  10.     /* 
  11.      * 创建普通条目布局 
  12.      * 如果 普通条目布局 为 null 或者 普通条目布局的宽度 大于 传入的宽度, 这时需要重新创建布局 
  13.      * 如果 普通条目布局存在, 并且其宽度小于传入的宽度, 此时需要将 
  14.      */  
  15.     if (itemsLayout == null || itemsLayout.getWidth() > widthItems) {  
  16.           
  17.         /* 
  18.          * android.text.StaticLayout.StaticLayout( 
  19.          * CharSequence source, TextPaint paint,  
  20.          * int width, Alignment align,  
  21.          * float spacingmult, float spacingadd, boolean includepad) 
  22.          * 传入参数介绍 :  
  23.          * CharSequence source : 需要分行显示的字符串 
  24.          * TextPaint paint : 绘制字符串的画笔 
  25.          * int width : 条目的宽度 
  26.          * Alignment align : Layout 的对齐方式, ALIGN_CENTER 居中对齐, ALIGN_NORMAL 左对齐, Alignment.ALIGN_OPPOSITE 右对齐 
  27.          * float spacingmult : 行间距, 1.5f 代表 1.5 倍字体高度 
  28.          * float spacingadd : 基础行距上增加多少 , 真实行间距 等于 spacingmult 和 spacingadd 的和 
  29.          * boolean includepad :  
  30.          */  
  31.         itemsLayout = new StaticLayout(buildText(isScrollingPerformed), itemsPaint, widthItems,  
  32.                 widthLabel > 0 ? Layout.Alignment.ALIGN_OPPOSITE : Layout.Alignment.ALIGN_CENTER, 1,  
  33.                 ADDITIONAL_ITEM_HEIGHT, false);  
  34.     } else {  
  35.         //调用 Layout 内置的方法 increaseWidthTo 将宽度提升到指定的宽度  
  36.         itemsLayout.increaseWidthTo(widthItems);  
  37.     }  
  38.   
  39.     /* 
  40.      * 创建选中条目 
  41.      */  
  42.     if (!isScrollingPerformed && (valueLayout == null || valueLayout.getWidth() > widthItems)) {  
  43.         String text = getAdapter() != null ? getAdapter().getItem(currentItem) : null;  
  44.         valueLayout = new StaticLayout(text != null ? text : "", valuePaint, widthItems,  
  45.                 widthLabel > 0 ? Layout.Alignment.ALIGN_OPPOSITE : Layout.Alignment.ALIGN_CENTER, 1,  
  46.                 ADDITIONAL_ITEM_HEIGHT, false);  
  47.     } else if (isScrollingPerformed) {  
  48.         valueLayout = null;  
  49.     } else {  
  50.         valueLayout.increaseWidthTo(widthItems);  
  51.     }  
  52.   
  53.     /* 
  54.      * 创建标签条目 
  55.      */  
  56.     if (widthLabel > 0) {  
  57.         if (labelLayout == null || labelLayout.getWidth() > widthLabel) {  
  58.             labelLayout = new StaticLayout(label, valuePaint, widthLabel, Layout.Alignment.ALIGN_NORMAL, 1,  
  59.                     ADDITIONAL_ITEM_HEIGHT, false);  
  60.         } else {  
  61.             labelLayout.increaseWidthTo(widthLabel);  
  62.         }  
  63.     }  
  64. }  



4. 监听器管理



监听器集合维护 

-- 定义监听器集合 : 在 View 组件中 定义一个 List 集合, 集合中存放 监听器元素;

[java]  view plain copy
  1. /** 条目改变监听器集合  封装了条目改变方法, 当条目改变时回调 */  
  2. private List changingListeners = new LinkedList();  
  3. /** 条目滚动监听器集合, 该监听器封装了 开始滚动方法, 结束滚动方法 */  
  4. private List scrollingListeners = new LinkedList();  


-- 提供对监听器集合的添加删除接口 : 提供 对集合 进行 添加 和 删除的接口;

[java]  view plain copy
  1. /** 
  2.  * 添加 WheelView 选择的元素改变监听器 
  3.  *  
  4.  * @param listener 
  5.  *            the listener 
  6.  */  
  7. public void addChangingListener(OnWheelChangedListener listener) {  
  8.     changingListeners.add(listener);  
  9. }  
  10.   
  11. /** 
  12.  * 移除 WheelView 元素改变监听器 
  13.  *  
  14.  * @param listener 
  15.  *            the listener 
  16.  */  
  17. public void removeChangingListener(OnWheelChangedListener listener) {  
  18.     changingListeners.remove(listener);  
  19. }  

--  调用监听器接口 : 

[java]  view plain copy
  1. /** 
  2.  * 回调元素改变监听器集合的元素改变监听器元素的元素改变方法 
  3.  *  
  4.  * @param oldValue 
  5.  *            旧的 WheelView选中的值 
  6.  * @param newValue 
  7.  *            新的 WheelView选中的值 
  8.  */  
  9. protected void notifyChangingListeners(int oldValue, int newValue) {  
  10.     for (OnWheelChangedListener listener : changingListeners) {  
  11.         listener.onChanged(this, oldValue, newValue);  
  12.     }  
  13. }  



5. 自定义 View 对象的宽高 



(1) onMeasure 方法 MeasureSpec 模式解析


常规处理方法 : 组件的宽高有三种情况, widthMeasureSpec 有三种模式 最大模式, 精准模式, 未定义模式;

-- 最大模式 : 在 组件的宽或高 warp_content 属性时, 会使用最大模式;

-- 精准模式 : 当给组件宽 或者高 定义一个值 或者 使用 match_parent 时, 会使用精准模式;


处理宽高的常规代码 

[java]  view plain copy
  1. @Override  
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  3.     super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  4.       
  5.     //获取宽度 和 高度的模式 和 大小  
  6.        int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
  7.        int heightMode = MeasureSpec.getMode(heightMeasureSpec);  
  8.        int widthSize = MeasureSpec.getSize(widthMeasureSpec);  
  9.        int heightSize = MeasureSpec.getSize(heightMeasureSpec);  
  10.          
  11.        Log.i(TAG, "宽度 : widthMode : " + getMode(widthMode) + " , widthSize : " + widthSize + "\n"   
  12.             + "高度 : heightMode : " + getMode(heightMode) + " , heightSize : " + heightSize);  
  13.          
  14.        int width = 0;  
  15.        int height = 0;  
  16.        /* 
  17.         * 精准模式 
  18.         *       精准模式下 高度就是精确的高度 
  19.         */  
  20.        if (heightMode == MeasureSpec.EXACTLY) {  
  21.            height = heightSize;  
  22.        //未定义模式 和 最大模式  
  23.        } else {  
  24.         //未定义模式下 获取布局需要的高度  
  25.            height = 100;  
  26.   
  27.            //最大模式下 获取 布局高度 和 布局所需高度的最小值  
  28.            if (heightMode == MeasureSpec.AT_MOST) {  
  29.                height = Math.min(height, heightSize);  
  30.            }  
  31.        }  
  32.          
  33.        if (widthMode == MeasureSpec.EXACTLY) {  
  34.            width = widthSize;  
  35.        } else {  
  36.            width = 100;  
  37.            if (heightMode == MeasureSpec.AT_MOST) {  
  38.                width = Math.min(width, widthSize);  
  39.            }  
  40.        }  
  41.   
  42.        Log.i(TAG, "最终结果 : 宽度 : " + width + " , 高度 : " + height);  
  43.          
  44.        setMeasuredDimension(width, height);  
  45.       
  46. }  
  47.   
  48.   
  49. public String getMode(int mode) {  
  50.     String modeName = "";  
  51.     if(mode == MeasureSpec.EXACTLY){  
  52.         modeName = "精准模式";  
  53.     }else if(mode == MeasureSpec.AT_MOST){  
  54.         modeName = "最大模式";  
  55.     }else if(mode == MeasureSpec.UNSPECIFIED){  
  56.         modeName = "未定义模式";  
  57.     }  
  58.           
  59.     return modeName;  
  60. }  



(2) 测试上述代码


使用下面的自定义组件测试 : 

[java]  view plain copy
  1. package cn.org.octopus.wheelview;  
  2.   
  3. import android.content.Context;  
  4. import android.graphics.Canvas;  
  5. import android.graphics.Color;  
  6. import android.util.AttributeSet;  
  7. import android.util.Log;  
  8. import android.view.View;  
  9.   
  10. public class MyView extends View {  
  11.   
  12.     public static final String TAG = "octopus.my.view";  
  13.       
  14.     public MyView(Context context, AttributeSet attrs) {  
  15.         super(context, attrs);  
  16.     }  
  17.   
  18.     public MyView(Context context) {  
  19.         super(context);  
  20.     }  
  21.   
  22.     public MyView(Context context, AttributeSet attrs, int defStyle) {  
  23.         super(context, attrs, defStyle);  
  24.     }  
  25.       
  26.       
  27.       
  28.       
  29.     @Override  
  30.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  31.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  32.           
  33.         //获取宽度 和 高度的模式 和 大小  
  34.         int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
  35.         int heightMode = MeasureSpec.getMode(heightMeasureSpec);  
  36.         int widthSize = MeasureSpec.getSize(widthMeasureSpec);  
  37.         int heightSize = MeasureSpec.getSize(heightMeasureSpec);  
  38.           
  39.         Log.i(TAG, "宽度 : widthMode : " + getMode(widthMode) + " , widthSize : " + widthSize + "\n"   
  40.                 + "高度 : heightMode : " + getMode(heightMode) + " , heightSize : " + heightSize);  
  41.           
  42.         int width = 0;  
  43.         int height = 0;  
  44.         /* 
  45.          * 精准模式 
  46.          *      精准模式下 高度就是精确的高度 
  47.          */  
  48.         if (heightMode == MeasureSpec.EXACTLY) {  
  49.             height = heightSize;  
  50.         //未定义模式 和 最大模式  
  51.         } else {  
  52.             //未定义模式下 获取布局需要的高度  
  53.             height = 100;  
  54.   
  55.             //最大模式下 获取 布局高度 和 布局所需高度的最小值  
  56.             if (heightMode == MeasureSpec.AT_MOST) {  
  57.                 height = Math.min(height, heightSize);  
  58.             }  
  59.         }  
  60.           
  61.         if (widthMode == MeasureSpec.EXACTLY) {  
  62.             width = widthSize;  
  63.         } else {  
  64.             width = 100;  
  65.             if (heightMode == MeasureSpec.AT_MOST) {  
  66.                 width = Math.min(width, widthSize);  
  67.             }  
  68.         }  
  69.   
  70.         Log.i(TAG, "最终结果 : 宽度 : " + width + " , 高度 : " + height);  
  71.           
  72.         setMeasuredDimension(width, height);  
  73.           
  74.     }  
  75.       
  76.       
  77.     public String getMode(int mode) {  
  78.         String modeName = "";  
  79.         if(mode == MeasureSpec.EXACTLY){  
  80.             modeName = "精准模式";  
  81.         }else if(mode == MeasureSpec.AT_MOST){  
  82.             modeName = "最大模式";  
  83.         }else if(mode == MeasureSpec.UNSPECIFIED){  
  84.             modeName = "未定义模式";  
  85.         }  
  86.               
  87.         return modeName;  
  88.     }  
  89.       
  90.     @Override  
  91.     protected void onDraw(Canvas canvas) {  
  92.         super.onDraw(canvas);  
  93.           
  94.         canvas.drawColor(Color.BLUE);  
  95.     }  
  96.   
  97. }  



给定具体值情况 : 

-- 组件信息 : 

[html]  view plain copy
  1. <cn.org.octopus.wheelview.MyView  
  2.     android:layout_width="300dip"  
  3.     android:layout_height="300dip"/>  
--  日志信息 : 

[plain]  view plain copy
  1. 11-30 01:40:24.304: I/octopus.my.view(2609): 宽度 : widthMode : 精准模式 , widthSize : 450  
  2. 11-30 01:40:24.304: I/octopus.my.view(2609): 高度 : heightMode : 最大模式 , heightSize : 850  
  3. 11-30 01:40:24.304: I/octopus.my.view(2609): 最终结果 : 宽度 : 450 , 高度 : 100  
  4. 11-30 01:40:24.304: I/octopus.my.view(2609): 宽度 : widthMode : 精准模式 , widthSize : 450  
  5. 11-30 01:40:24.304: I/octopus.my.view(2609): 高度 : heightMode : 精准模式 , heightSize : 450  
  6. 11-30 01:40:24.304: I/octopus.my.view(2609): 最终结果 : 宽度 : 450 , 高度 : 450  
  7. 11-30 01:40:24.335: I/octopus.my.view(2609): 宽度 : widthMode : 精准模式 , widthSize : 450  
  8. 11-30 01:40:24.335: I/octopus.my.view(2609): 高度 : heightMode : 最大模式 , heightSize : 850  
  9. 11-30 01:40:24.335: I/octopus.my.view(2609): 最终结果 : 宽度 : 450 , 高度 : 100  
  10. 11-30 01:40:24.335: I/octopus.my.view(2609): 宽度 : widthMode : 精准模式 , widthSize : 450  
  11. 11-30 01:40:24.335: I/octopus.my.view(2609): 高度 : heightMode : 精准模式 , heightSize : 450  
  12. 11-30 01:40:24.335: I/octopus.my.view(2609): 最终结果 : 宽度 : 450 , 高度 : 450  



warp_content 情况 : 

-- 组件信息 : 

[html]  view plain copy
  1. <cn.org.octopus.wheelview.MyView  
  2.     android:layout_width="wrap_content"  
  3.     android:layout_height="wrap_content"/>  
--  日志信息 : 

[html]  view plain copy
  1. 11-30 01:37:47.351: I/octopus.my.view(1803): 宽度 : widthMode : 最大模式 , widthSize : 492  
  2. 11-30 01:37:47.351: I/octopus.my.view(1803): 高度 : heightMode : 最大模式 , heightSize : 850  
  3. 11-30 01:37:47.351: I/octopus.my.view(1803): 最终结果 : 宽度 : 100 , 高度 : 100  
  4. 11-30 01:37:47.351: I/octopus.my.view(1803): 宽度 : widthMode : 精准模式 , widthSize : 100  
  5. 11-30 01:37:47.351: I/octopus.my.view(1803): 高度 : heightMode : 最大模式 , heightSize : 802  
  6. 11-30 01:37:47.351: I/octopus.my.view(1803): 最终结果 : 宽度 : 100 , 高度 : 100  
  7. 11-30 01:37:47.390: I/octopus.my.view(1803): 宽度 : widthMode : 最大模式 , widthSize : 492  
  8. 11-30 01:37:47.390: I/octopus.my.view(1803): 高度 : heightMode : 最大模式 , heightSize : 850  
  9. 11-30 01:37:47.390: I/octopus.my.view(1803): 最终结果 : 宽度 : 100 , 高度 : 100  
  10. 11-30 01:37:47.390: I/octopus.my.view(1803): 宽度 : widthMode : 精准模式 , widthSize : 100  
  11. 11-30 01:37:47.390: I/octopus.my.view(1803): 高度 : heightMode : 最大模式 , heightSize : 802  
  12. 11-30 01:37:47.390: I/octopus.my.view(1803): 最终结果 : 宽度 : 100 , 高度 : 100  



match_parent 情况 : 

-- 组件信息 : 

[html]  view plain copy
  1. <cn.org.octopus.wheelview.MyView  
  2.     android:layout_width="match_parent"  
  3.     android:layout_height="match_parent"/>  

-- 日志信息 : 

[html]  view plain copy
  1. 11-30 01:39:08.296: I/octopus.my.view(2249): 宽度 : widthMode : 精准模式 , widthSize : 492  
  2. 11-30 01:39:08.296: I/octopus.my.view(2249): 高度 : heightMode : 精准模式 , heightSize : 850  
  3. 11-30 01:39:08.296: I/octopus.my.view(2249): 最终结果 : 宽度 : 492 , 高度 : 850  
  4. 11-30 01:39:08.296: I/octopus.my.view(2249): 宽度 : widthMode : 精准模式 , widthSize : 492  
  5. 11-30 01:39:08.296: I/octopus.my.view(2249): 高度 : heightMode : 精准模式 , heightSize : 802  
  6. 11-30 01:39:08.296: I/octopus.my.view(2249): 最终结果 : 宽度 : 492 , 高度 : 802  
  7. 11-30 01:39:08.328: I/octopus.my.view(2249): 宽度 : widthMode : 精准模式 , widthSize : 492  
  8. 11-30 01:39:08.328: I/octopus.my.view(2249): 高度 : heightMode : 精准模式 , heightSize : 850  
  9. 11-30 01:39:08.328: I/octopus.my.view(2249): 最终结果 : 宽度 : 492 , 高度 : 850  
  10. 11-30 01:39:08.328: I/octopus.my.view(2249): 宽度 : widthMode : 精准模式 , widthSize : 492  
  11. 11-30 01:39:08.328: I/octopus.my.view(2249): 高度 : heightMode : 精准模式 , heightSize : 802  
  12. 11-30 01:39:08.328: I/octopus.my.view(2249): 最终结果 : 宽度 : 492 , 高度 : 802  


博客地址 http://blog.csdn.net/shulianghan/article/details/41520569#t17


代码下载 : 

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

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




四. 详细代码 



1. WheelAdapter


[java]  view plain copy
  1. package cn.org.octopus.wheelview.widget;  
  2.   
  3. /** 
  4.  * WheelView 适配器接口 
  5.  * @author han_shuliang([email protected]) 
  6.  * 
  7.  */  
  8. public interface WheelAdapter {  
  9.     /** 
  10.      * 获取条目的个数 
  11.      *  
  12.      * @return  
  13.      *      WheelView 的条目个数 
  14.      */  
  15.     public int getItemsCount();  
  16.   
  17.     /** 
  18.      * 根据索引位置获取 WheelView 的条目 
  19.      *  
  20.      * @param index 
  21.      *            条目的索引 
  22.      * @return  
  23.      *      WheelView 上显示的条目的值 
  24.      */  
  25.     public String getItem(int index);  
  26.   
  27.     /** 
  28.      * 获取条目的最大长度. 用来定义 WheelView 的宽度. 如果返回 -1, 就会使用默认宽度 
  29.      *  
  30.      * @return  
  31.      *      条目的最大宽度 或者 -1 
  32.      */  
  33.     public int getMaximumLength();  
  34. }  



2. ArrayWheelAdapter



[java]  view plain copy
  1. package cn.org.octopus.wheelview.widget;  
  2.   
  3. /** 
  4.  * WheelView 的适配器类 
  5.  *  
  6.  * @param  
  7.  *            元素类型 
  8.  */  
  9. public class ArrayWheelAdapter implements WheelAdapter {  
  10.   
  11.     /** 适配器的 元素集合(数据源) 默认长度为 -1 */  
  12.     public static final int DEFAULT_LENGTH = -1;  
  13.   
  14.     /** 适配器的数据源 */  
  15.     private T items[];  
  16.     /** WheelView 的宽度 */  
  17.     private int length;  
  18.   
  19.     /** 
  20.      * 构造方法 
  21.      *  
  22.      * @param items 
  23.      *            适配器数据源 集合 T 类型的数组 
  24.      * @param length 
  25.      *            适配器数据源 集合 T 数组长度 
  26.      */  
  27.     public ArrayWheelAdapter(T items[], int length) {  
  28.         this.items = items;  
  29.         this.length = length;  
  30.     }  
  31.   
  32.     /** 
  33.      * 构造方法 
  34.      *  
  35.      * @param items 
  36.      *            适配器数据源集合 T 类型数组 
  37.      */  
  38.     public ArrayWheelAdapter(T items[]) {  
  39.         this(items, DEFAULT_LENGTH);  
  40.     }  
  41.   
  42.       
  43.     @Override  
  44.     public String getItem(int index) {  
  45.         //如果这个索引值合法, 就返回 item 数组对应的元素的字符串形式  
  46.         if (index >= 0 && index < items.length) {  
  47.             return items[index].toString();  
  48.         }  
  49.         return null;  
  50.     }  
  51.   
  52.     @Override  
  53.     public int getItemsCount() {  
  54.         //返回 item 数组的长度  
  55.         return items.length;  
  56.     }  
  57.   
  58.     @Override  
  59.     public int getMaximumLength() {  
  60.         //返回 item 元素的宽度  
  61.         return length;  
  62.     }  
  63.   
  64. }  


3. NumericWheelAdapter


[java]  view plain copy
  1. package cn.org.octopus.wheelview.widget;  
  2.   
  3. /** 
  4.  * 显示数字的 WheelAdapter 
  5.  */  
  6. public class NumericWheelAdapter implements WheelAdapter {  
  7.   
  8.     /** 默认最小值 */  
  9.     public static final int DEFAULT_MAX_VALUE = 9;  
  10.   
  11.     /** 默认最大值 */  
  12.     private static final int DEFAULT_MIN_VALUE = 0;  
  13.   
  14.     /** 设置的最小值 */  
  15.     private int minValue;  
  16.     /** 设置的最大值 */  
  17.     private int maxValue;  
  18.   
  19.     /** 格式化字符串, 用于格式化 货币, 科学计数, 十六进制 等格式 */  
  20.     private String format;  
  21.   
  22.     /** 
  23.      * 默认的构造方法, 使用默认的最大最小值 
  24.      */  
  25.     public NumericWheelAdapter() {  
  26.         this(DEFAULT_MIN_VALUE, DEFAULT_MAX_VALUE);  
  27.     }  
  28.   
  29.     /** 
  30.      * 构造方法 
  31.      *  
  32.      * @param minValue 
  33.      *            最小值 
  34.      * @param maxValue 
  35.      *            最大值 
  36.      */  
  37.     public NumericWheelAdapter(int minValue, int maxValue) {  
  38.         this(minValue, maxValue, null);  
  39.     }  
  40.   
  41.     /** 
  42.      * 构造方法 
  43.      *  
  44.      * @param minValue 
  45.      *            最小值 
  46.      * @param maxValue 
  47.      *            最大值 
  48.      * @param format 
  49.      *            格式化字符串 
  50.      */  
  51.     public NumericWheelAdapter(int minValue, int maxValue, String format) {  
  52.         this.minValue = minValue;  
  53.         this.maxValue = maxValue;  
  54.         this.format = format;  
  55.     }  
  56.   
  57.     @Override  
  58.     public String getItem(int index) {  
  59.         String result = "";  
  60.         if (index >= 0 && index < getItemsCount()) {  
  61.             int value = minValue + index;  
  62.             //如果 format 不为 null, 那么格式化字符串, 如果为 null, 直接返回数字  
  63.             if(format != null){  
  64.                 result = String.format(format, value);  
  65.             }else{  
  66.                 result = Integer.toString(value);  
  67.             }  
  68.             return result;  
  69.         }  
  70.         return null;  
  71.     }  
  72.   
  73.     @Override  
  74.     public int getItemsCount() {  
  75.         //返回数字总个数  
  76.         return maxValue - minValue + 1;  
  77.     }  
  78.   
  79.     @Override  
  80.     public int getMaximumLength() {  
  81.         //获取 最大值 和 最小值 中的 较大的数字  
  82.         int max = Math.max(Math.abs(maxValue), Math.abs(minValue));  
  83.         //获取这个数字 的 字符串形式的 字符串长度  
  84.         int maxLen = Integer.toString(max).length();  
  85.         if (minValue < 0) {  
  86.             maxLen++;  
  87.         }  
  88.         return maxLen;  
  89.     }  
  90. }  



4. OnWheelChangedListener


[java]  view plain copy
  1. package cn.org.octopus.wheelview.widget;  
  2.   
  3. /** 
  4.  * 条目改变监听器 
  5.  */  
  6. public interface OnWheelChangedListener {  
  7.     /** 
  8.      * 当前条目改变时回调该方法 
  9.      *  
  10.      * @param wheel 
  11.      *            条目改变的 WheelView 对象 
  12.      * @param oldValue 
  13.      *            WheelView 旧的条目值 
  14.      * @param newValue 
  15.      *            WheelView 新的条目值 
  16.      */  
  17.     void onChanged(WheelView wheel, int oldValue, int newValue);  
  18. }  




5. OnWheelScrollListener



[java]  view plain copy
  1. package cn.org.octopus.wheelview.widget;  
  2.   
  3. /** 
  4.  * WheelView 滚动监听器 
  5.  */  
  6. public interface OnWheelScrollListener {  
  7.     /** 
  8.      * 在 WheelView 滚动开始的时候回调该接口 
  9.      *  
  10.      * @param wheel 
  11.      *            开始滚动的 WheelView 对象 
  12.      */  
  13.     void onScrollingStarted(WheelView wheel);  
  14.   
  15.     /** 
  16.      * 在 WheelView 滚动结束的时候回调该接口 
  17.      *  
  18.      * @param wheel 
  19.      *            结束滚动的 WheelView 对象 
  20.      */  
  21.     void onScrollingFinished(WheelView wheel);  
  22. }  



6. WheelView


[java]  view plain copy
  1. package cn.org.octopus.wheelview.widget;  
  2.   
  3. import java.util.LinkedList;  
  4. import java.util.List;  
  5.   
  6. import cn.org.octopus.wheelview.R;  
  7. import android.annotation.SuppressLint;  
  8. import android.content.Context;  
  9. import android.graphics.Canvas;  
  10. import android.graphics.Paint;  
  11. import android.graphics.Rect;  
  12. import android.graphics.drawable.Drawable;  
  13. import android.graphics.drawable.GradientDrawable;  
  14. import android.graphics.drawable.GradientDrawable.Orientation;  
  15. import android.os.Handler;  
  16. import android.os.Message;  
  17. import android.text.Layout;  
  18. import android.text.StaticLayout;  
  19. import android.text.TextPaint;  
  20. import android.util.AttributeSet;  
  21. import android.view.GestureDetector;  
  22. import android.view.GestureDetector.SimpleOnGestureListener;  
  23. import android.view.MotionEvent;  
  24. import android.view.View;  
  25. import android.view.animation.Interpolator;  
  26. import android.widget.Scroller;  
  27.   
  28. /** 
  29.  * WheelView 主对象 
  30.  */  
  31. public class WheelView extends View {  
  32.     /** 滚动花费时间 Scrolling duration */  
  33.     private static final int SCROLLING_DURATION = 400;  
  34.   
  35.     /** 最小的滚动值, 每次最少滚动一个单位 */  
  36.     private static final int MIN_DELTA_FOR_SCROLLING = 1;  
  37.   
  38.     /** 当前条目中的文字颜色 */  
  39.     private static final int VALUE_TEXT_COLOR = 0xF0FF6347;  
  40.   
  41.     /** 非当前条目的文字颜色 */  
  42.     private static final int ITEMS_TEXT_COLOR = 0xFF000000;  
  43.   
  44.     /** 顶部和底部的阴影颜色 */  
  45.     //private static final int[] SHADOWS_COLORS = new int[] { 0xFF5436EE, 0x0012CEAE, 0x0012CEAE };  
  46.     private static final int[] SHADOWS_COLORS = new int[] { 0xFF1111110x00AAAAAA0x00AAAAAA };  
  47.   
  48.     /** 额外的条目高度 Additional items height (is added to standard text item height) */  
  49.     private static final int ADDITIONAL_ITEM_HEIGHT = 15;  
  50.   
  51.     /** 字体大小 */  
  52.     private static final int TEXT_SIZE = 24;  
  53.   
  54.     /** 顶部 和 底部 条目的隐藏大小,  
  55.      * 如果是正数 会隐藏一部份,  
  56.      * 0 顶部 和 底部的字正好紧贴 边缘,  
  57.      * 负数时 顶部和底部 与 字有一定间距 */  
  58.     private static final int ITEM_OFFSET = TEXT_SIZE / 5;  
  59.   
  60.     /** Additional width for items layout */  
  61.     private static final int ADDITIONAL_ITEMS_SPACE = 10;  
  62.   
  63.     /** Label offset */  
  64.     private static final int LABEL_OFFSET = 8;  
  65.   
  66.     /** Left and right padding value */  
  67.     private static final int PADDING = 10;  
  68.   
  69.     /** 默认的可显示的条目数 */  
  70.     private static final int DEF_VISIBLE_ITEMS = 5;  
  71.   
  72.     /** WheelView 适配器 */  
  73.     private WheelAdapter adapter = null;  
  74.     /** 当前显示的条目索引 */  
  75.     private int currentItem = 0;  
  76.   
  77.     /** 条目宽度 */  
  78.     private int itemsWidth = 0;  
  79.     /** 标签宽度 */  
  80.     private int labelWidth = 0;  
  81.   
  82.     /** 可见的条目数 */  
  83.     private int visibleItems = DEF_VISIBLE_ITEMS;  
  84.   
  85.     /** 条目高度 */  
  86.     private int itemHeight = 0;  
  87.   
  88.     /** 绘制普通条目画笔 */  
  89.     private TextPaint itemsPaint;  
  90.     /** 绘制选中条目画笔 */  
  91.     private TextPaint valuePaint;  
  92.   
  93.     /** 普通条目布局 
  94.      * StaticLayout 布局用于控制 TextView 组件, 一般情况下不会直接使用该组件,  
  95.      * 除非你自定义一个组件 或者 想要直接调用  Canvas.drawText() 方法 
  96.      *  */  
  97.     private StaticLayout itemsLayout;  
  98.     private StaticLayout labelLayout;  
  99.     /** 选中条目布局 */  
  100.     private StaticLayout valueLayout;  
  101.   
  102.     /** 标签 在选中条目的右边出现 */  
  103.     private String label;  
  104.     /** 选中条目的背景图片 */  
  105.     private Drawable centerDrawable;  
  106.   
  107.     /** 顶部阴影图片 */  
  108.     private GradientDrawable topShadow;  
  109.     /** 底部阴影图片 */  
  110.     private GradientDrawable bottomShadow;  
  111.   
  112.     /** 是否在滚动 */  
  113.     private boolean isScrollingPerformed;  
  114.     /** 滚动的位置 */  
  115.     private int scrollingOffset;  
  116.   
  117.     /** 手势检测器 */  
  118.     private GestureDetector gestureDetector;  
  119.     /**  
  120.      * Scroll 类封装了滚动动作.  
  121.      * 开发者可以使用 Scroll 或者 Scroll 实现类 去收集产生一个滚动动画所需要的数据, 返回一个急冲滑动的手势. 
  122.      * 该对象可以追踪随着时间推移滚动的偏移量, 但是这些对象不会自动向 View 对象提供这些位置. 
  123.      * 如果想要使滚动动画看起来比较平滑, 开发者需要在适当的时机  获取 和 使用新的坐标;  
  124.      *  */  
  125.     private Scroller scroller;  
  126.     /** 之前所在的 y 轴位置 */  
  127.     private int lastScrollY;  
  128.   
  129.     /** 是否循环 */  
  130.     boolean isCyclic = false;  
  131.   
  132.     /** 条目改变监听器集合  封装了条目改变方法, 当条目改变时回调 */  
  133.     private List changingListeners = new LinkedList();  
  134.     /** 条目滚动监听器集合, 该监听器封装了 开始滚动方法, 结束滚动方法 */  
  135.     private List scrollingListeners = new LinkedList();  
  136.   
  137.     /** 
  138.      * 构造方法 
  139.      */  
  140.     public WheelView(Context context, AttributeSet attrs, int defStyle) {  
  141.         super(context, attrs, defStyle);  
  142.         initData(context);  
  143.     }  
  144.   
  145.     /** 
  146.      * 构造方法 
  147.      */  
  148.     public WheelView(Context context, AttributeSet attrs) {  
  149.         super(context, attrs);  
  150.         initData(context);  
  151.     }  
  152.   
  153.     /** 
  154.      * 构造方法 
  155.      */  
  156.     public WheelView(Context context) {  
  157.         super(context);  
  158.         initData(context);  
  159.     }  
  160.   
  161.     /** 
  162.      * 初始化数据 
  163.      *  
  164.      * @param context 
  165.      *            上下文对象 
  166.      */  
  167.     private void initData(Context context) {  
  168.         //创建一个手势处理  
  169.         gestureDetector = new GestureDetector(context, gestureListener);  
  170.         /* 
  171.          * 是否允许长按操作,  
  172.          * 如果设置为 true 用户按下不松开, 会返回一个长按事件,  
  173.          * 如果设置为 false, 按下不松开滑动的话 会收到滚动事件. 
  174.          */  
  175.         gestureDetector.setIsLongpressEnabled(false);  
  176.           
  177.         //使用默认的 时间 和 插入器 创建一个滚动器  
  178.         scroller = new Scroller(context);  
  179.     }  

你可能感兴趣的:(Android)