博客地址 : 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 对象 :
-
- final WheelView wheelLeft = new WheelView(context);
b. 设置 WheelView 显示条目数 :
-
- wheelLeft.setVisibleItems(5);
c. 设置 WheelView 是否滚动循环 :
-
- wheelLeft.setCyclic(false);
d. 设置 WheelView 适配器 :
-
- wheelLeft.setAdapter(new ArrayWheelAdapter(left));
e. 设置条目改变监听器 :
-
- wheelLeft.addChangingListener(new OnWheelChangedListener() {
- @Override
- public void onChanged(WheelView wheel, int oldValue, int newValue) {
-
- wheelRight.setAdapter(new ArrayWheelAdapter(right[newValue]));
- wheelRight.setCurrentItem(right[newValue].length / 2);
- }
- });
f. 设置滚动监听器 :
- wheelLeft.addScrollingListener(new OnWheelScrollListener() {
-
- @Override
- public void onScrollingStarted(WheelView wheel) {
-
-
- }
-
- @Override
- public void onScrollingFinished(WheelView wheel) {
-
-
- }
- );
二. WheelView 适配器 监听器 相关接口分析
1. 适配器 分析
这里定义了一个适配器接口, 以及两个适配器类, 一个用于任意类型的数据集适配, 一个用于数字适配;
适配器操作 : 在 WheelView.java 中通过 setAdapter(WheelAdapter adapter) 和 getAdapter() 方法设置 获取 适配器;
-- 适配器常用操作 : 在 WheelView 中定义了 getItem(), getItemsCount(), getMaxmiumLength() 方法获取 适配器的相关信息;
-
-
-
-
-
-
- public WheelAdapter getAdapter() {
- return adapter;
- }
-
-
-
-
-
-
-
- public void setAdapter(WheelAdapter adapter) {
- this.adapter = adapter;
- invalidateLayouts();
- invalidate();
- }
(1) 适配器接口 ( interface WheelAdapter )
适配器接口 : WheelAdapter;
-- 接口作用 : 该接口是所有适配器的接口, 适配器类都需要实现该接口;
接口抽象方法介绍 :
-- getItemsCount() : 获取适配器数据集合中元素个数;
-
-
-
-
-
-
- public int getItemsCount();
-- getItem(int index) : 获取适配器集合的中指定索引元素;
-
-
-
-
-
-
-
-
- public String getItem(int index);
-- getMaximumLength() : 获取 WheelView 在界面上的显示宽度;
-
-
-
-
-
-
- public int getMaximumLength();
(2) 数组适配器 ( class ArrayWheelAdapter implements WheelAdapter )
适配器作用 : 该适配器可以传入任何数据类型的数组, 可以是 字符串数组, 也可以是任何对象的数组, 传入的数组作为适配器的数据源;
成员变量分析 :
-- 数据源 :
-- WheelView 最大宽度 :
构造方法分析 :
-- ArrayWheelAdapter(T items[], int length) : 传入 T 类型 对象数组, 以及 WheelView 的宽度;
-
-
-
-
-
-
-
-
- public ArrayWheelAdapter(T items[], int length) {
- this.items = items;
- this.length = length;
- }
-- ArrayWheelAdapter(T items[]) : 传入 T 类型对象数组, 宽度使用默认的宽度;
-
-
-
-
-
-
- public ArrayWheelAdapter(T items[]) {
- this(items, DEFAULT_LENGTH);
- }
实现的父类方法分析 :
-- getItem(int index) : 根据索引获取数组中对应位置的对象的字符串类型;
- @Override
- public String getItem(int index) {
-
- if (index >= 0 && index < items.length) {
- return items[index].toString();
- }
- return null;
- }
-- getItemsCount() : 获取数据集广大小, 直接返回数组大小;
- @Override
- public int getItemsCount() {
-
- return items.length;
- }
-- getMaximumLength() : 获取 WheelView 的最大宽度;
- @Override
- public int getMaximumLength() {
-
- return length;
- }
(3) 数字适配器 ( class NumericWheelAdapter implements WheelAdapter )
NumericWheelAdapter 适配器作用 : 数字作为 WheelView 适配器的显示值;
成员变量分析 :
-- 最小值 : WheelView 数值显示的最小值;
-- 最大值 : WheelView 数值显示的最大值;
--
格式化字符串 : 用于字符串的格式化;
构造方法分析 :
-- NumericWheelAdapter() : 默认的构造方法, 使用默认的最大最小值;
-
-
-
- public NumericWheelAdapter() {
- this(DEFAULT_MIN_VALUE, DEFAULT_MAX_VALUE);
- }
-- NumericWheelAdapter(int minValue, int maxValue) : 传入一个最大最小值;
-
-
-
-
-
-
-
-
- public NumericWheelAdapter(int minValue, int maxValue) {
- this(minValue, maxValue, null);
- }
-- NumericWheelAdapter(int minValue, int maxValue, String 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;
-
- 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 组件对象, 以及 旧的 和 新的 条目值索引;
-
-
-
-
-
-
-
-
-
-
- void onChanged(WheelView wheel, int oldValue, int newValue);
(2) 滚动监听器 ( interface OnWheelScrollListener )
滚动监听器作用 : 在 WheelView 滚动动作 开始 和 结束的时候回调对应的方法, 在对应方法中进行相应的操作;
接口方法介绍 :
-- 开始滚动方法 : 在滚动开始的时候回调该方法;
-
-
-
-
-
-
- void onScrollingStarted(WheelView wheel);
-- 停止滚动方法 : 在滚动结束的时候回调该方法;
-
-
-
-
-
-
- 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 轴的速度;
-- 代码示例 :
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
-
- startScrolling();
- doScroll((int) -distanceY);
- return true;
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
-
- lastScrollY = currentItem * getItemHeight() + scrollingOffset;
-
- int maxY = isCyclic ? 0x7FFFFFFF : adapter.getItemsCount() * getItemHeight();
- int minY = isCyclic ? -maxY : 0;
-
-
-
-
-
-
-
-
-
-
-
-
- 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);
-
-
-
-
-
- gestureDetector.setIsLongpressEnabled(false);
(3) 将手势探测器 与 组件结合
关联手势探测器 与 组件 : 在组件的 onTouchEvent(MotionEvent event) 方法中, 调用手势探测器的 gestureDetector.onTouchEvent(event) 方法即可;
-
-
-
-
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
-
- WheelAdapter adapter = getAdapter();
- if (adapter == null) {
- return true;
- }
-
-
-
-
-
-
- if (!gestureDetector.onTouchEvent(event) && event.getAction() == MotionEvent.ACTION_UP) {
- justify();
- }
- return true;
- }
2. Scroller 简介
(1) Scroller 简介
Scroller 通用作用 : Scroller 组件并不是一个布局组件, 该组件是运行在后台的, 通过一些方法设定 Scroller 对象 的操作 或者 动画, 然后让 Scroller 运行在后台中 用于模拟滚动操作, 在适当的时机 获取该对象的坐标信息, 这些信息是在后台运算出来的;
Scroller 在本 View 中作用 : Android 的这个自定义的 WheelView 组件, 可以平滑的滚动, 当我们做一个加速滑动时, 会根据速度计算出滑动的距离, 这些数据都是在 Scroller 中计算出来的;
(2) 设定 Scroller 对象的动作参数
终止滚动 :
-- 终止滚动 跳转到目标位置 : 终止平缓的动画, 直接跳转到最终的 x y 轴的坐标位置;
- public void abortAnimation()
-- 终止滚动 停止在当前位置 : 强行结束 Scroll 的滚动;
- public final void forceFinished(boolean finished)
设置滚动参数 :
-- 设置最终 x 轴坐标 :
- public void setFinalX(int newX)
-- 设置最终 y 轴坐标 :
- public void setFinalY(int newY)
-- 设置滚动摩擦力 :
- public final void setFriction(float friction)
设置动作 :
-- 开始滚动 : 传入参数 开始 x 位置, 开始 y 位置, x 轴滚动距离, y 轴滚动距离;
- public void startScroll(int startX, int startY, int dx, int dy)
--
开始滚动 设定时间 : 最后一个参数是时间, 单位是 ms;
- public void startScroll(int startX, int startY, int dx, int dy, int duration)
--
急冲滚动 : 根据一个 急冲 手势进行滚动, 传入参数 : x轴开始位置, y轴开始位置, x 轴速度, y 轴速度, x 轴最小速度, x 轴最大速度, y 轴最小速度, y 轴最大速度;
- public void fling(int startX, int startY, int velocityX, int velocityY,
- int minX, int maxX, int minY, int maxY)
延长滚动时间 : 延长滚动的时间, 让滚动滚的更远一些;
- public void extendDuration(int extend)
(3) 获取 Scroll 后台运行参数
获取当前数据 :
-- 获取当前 x 轴坐标 :
- public final int getCurrX()
-- 获取当前 y 轴坐标 :
- public final int getCurrY()
-- 获取当前速度 :
- public float getCurrVelocity()
获取开始结束时的数据 :
-- 获取开始 x 轴坐标 :
- public final int getStartX()
-- 获取开始 y 轴坐标 :
- public final int getStartY()
-- 获取最终 x 轴坐标 : 该参数只在急冲滚动时有效;
- public final int getFinalX()
-- 获取最终 y 轴坐标 : 该参数只在急冲滚动时有效;
- public final int getFinalY()
查看是否滚动完毕 :
- public final boolean isFinished()
获取从开始滚动到现在的时间 :
获取新位置 : 调用该方法可以获取新位置, 如果返回 true 说明动画还没执行完毕;
- public boolean computeScrollOffset()
(4) Scroll 在 WheelView 中的运用
Scroller 创建 :
-
- scroller = new Scroller(context);
手势监听器 SimpleOnGestureListener 对象中的 onDown() 方法 : 如果滚动还在执行, 那么强行停止 Scroller 滚动;
-
- public boolean onDown(MotionEvent e) {
-
- if (isScrollingPerformed) {
-
- scroller.forceFinished(true);
-
- clearMessages();
- return true;
- }
- return false;
- }
当手势监听器 SimpleOnGestureListener 对象中有急冲动作时 onFling() 方法中 : 手势监听器监听到了 急冲动作, 那么 Scroller 也进行对应操作;
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
-
- lastScrollY = currentItem * getItemHeight() + scrollingOffset;
-
- int maxY = isCyclic ? 0x7FFFFFFF : adapter.getItemsCount() * getItemHeight();
- int minY = isCyclic ? -maxY : 0;
-
-
-
-
-
-
-
-
-
-
-
-
- scroller.fling(0, lastScrollY, 0, (int) -velocityY / 2, 0, 0, minY, maxY);
- setNextMessage(MESSAGE_SCROLL);
- return true;
- }
动画控制 Handler 中 :
-- 滚动 : 获取当前 Scroller 的 y 轴位置, 与上一次的 y 轴位置对比, 如果 间距 delta 不为0, 就滚动;
-- 查看是否停止 : 如果现在距离 到 最终距离 小于最小滚动距离, 强制停止;
-- 执行 msg.what 指令 : 如果需要停止, 强制停止, 否则调整坐标;
-
-
-
-
-
-
-
-
-
-
- @SuppressLint("HandlerLeak")
- vate Handler animationHandler = new Handler() {
- public void handleMessage(Message msg) {
-
- scroller.computeScrollOffset();
-
- int currY = scroller.getCurrY();
-
- int delta = lastScrollY - currY;
- lastScrollY = currY;
- if (delta != 0) {
-
- doScroll(delta);
- }
-
-
-
-
-
- if (Math.abs(currY - scroller.getFinalY()) < MIN_DELTA_FOR_SCROLLING) {
- currY = scroller.getFinalY();
- scroller.forceFinished(true);
- }
-
-
-
-
-
- if (!scroller.isFinished()) {
- animationHandler.sendEmptyMessage(msg.what);
- } else if (msg.what == MESSAGE_SCROLL) {
- justify();
- } else {
- finishScrolling();
- }
- }
- };
3. StaticLayout 布局容器
(1) StaticLayout 解析
StaticLayout 解析 : 该组件用于显示文本, 一旦该文本被显示后, 就不能再编辑, 如果想要修改文本, 使用 DynamicLayout 布局即可;
-- 使用场景 : 一般情况下不会使用该组件, 当想要自定义组件 或者 想要使用 Canvas 绘制文本时 才使用该布局;
常用方法解析 :
-- 获取底部 Padding : 获取底部 到最后一行文字的 间隔, 单位是 px;
- public int getBottomPadding()
-- 获取顶部 Padding :
- public int getTopPadding()
--
获取省略个数 : 获取某一行需要省略的字符个数;
- public int getEllipsisCount(int line)
--
获取省略开始位置 : 获取某一行要省略的字符串的第一个位置索引;
- public int getEllipsisStart(int line)
--
获取省略的宽度 : 获取某一行省略字符串的宽度, 单位 px;
- public int getEllipsisStart(int line)
--
获取是否处理特殊符号 :
- public boolean getLineContainsTab(int line)
--
获取文字的行数 :
- public int getLineCount()
--
获取顶部位置 : 获取某一行顶部的位置;
- public int getLineTop(int line)
--
获取某一行底部位置 :
- public int getLineDescent(int line)
--
获取行的方向 : 字符串从左至右 还是从右至左;
- public final Directions getLineDirections(int line)
--
获取某行第一个字符索引 : 获取的是 某一行 第一个字符 在整个字符串的索引;
- public int getLineStart(int line)
--
获取该行段落方向 : 获取该行文字方向, 左至右 或者 右至左;
- public int getParagraphDirection(int line)
--
获取某个垂直位置显示的行数 :
- public int getLineForVertical(int vertical)
(2) 布局显示
布局创建 :
-- 三种布局 : WheelView 中涉及到了三种 StaticLayout 布局, 普通条目布局 itemLayout, 选中条目布局 valueLayout, 标签布局 labelLayout;
-- 创建时机 : 在 View 组件 每次 onMeasure() 和 onDraw() 方法中都要重新创建对应布局;
-- 创建布局源码 :
-
-
-
-
-
-
-
-
- private void createLayouts(int widthItems, int widthLabel) {
-
-
-
-
-
- if (itemsLayout == null || itemsLayout.getWidth() > widthItems) {
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- itemsLayout = new StaticLayout(buildText(isScrollingPerformed), itemsPaint, widthItems,
- widthLabel > 0 ? Layout.Alignment.ALIGN_OPPOSITE : Layout.Alignment.ALIGN_CENTER, 1,
- ADDITIONAL_ITEM_HEIGHT, false);
- } else {
-
- itemsLayout.increaseWidthTo(widthItems);
- }
-
-
-
-
- if (!isScrollingPerformed && (valueLayout == null || valueLayout.getWidth() > widthItems)) {
- String text = getAdapter() != null ? getAdapter().getItem(currentItem) : null;
- valueLayout = new StaticLayout(text != null ? text : "", valuePaint, widthItems,
- widthLabel > 0 ? Layout.Alignment.ALIGN_OPPOSITE : Layout.Alignment.ALIGN_CENTER, 1,
- ADDITIONAL_ITEM_HEIGHT, false);
- } else if (isScrollingPerformed) {
- valueLayout = null;
- } else {
- valueLayout.increaseWidthTo(widthItems);
- }
-
-
-
-
- if (widthLabel > 0) {
- if (labelLayout == null || labelLayout.getWidth() > widthLabel) {
- labelLayout = new StaticLayout(label, valuePaint, widthLabel, Layout.Alignment.ALIGN_NORMAL, 1,
- ADDITIONAL_ITEM_HEIGHT, false);
- } else {
- labelLayout.increaseWidthTo(widthLabel);
- }
- }
- }
4. 监听器管理
监听器集合维护 :
-- 定义监听器集合 : 在 View 组件中 定义一个 List 集合, 集合中存放 监听器元素;
-
- private List changingListeners = new LinkedList();
-
- private List scrollingListeners = new LinkedList();
-- 提供对监听器集合的添加删除接口 : 提供 对集合 进行 添加 和 删除的接口;
-
-
-
-
-
-
- public void addChangingListener(OnWheelChangedListener listener) {
- changingListeners.add(listener);
- }
-
-
-
-
-
-
-
- public void removeChangingListener(OnWheelChangedListener listener) {
- changingListeners.remove(listener);
- }
--
调用监听器接口 :
-
-
-
-
-
-
-
-
- protected void notifyChangingListeners(int oldValue, int newValue) {
- for (OnWheelChangedListener listener : changingListeners) {
- listener.onChanged(this, oldValue, newValue);
- }
- }
5. 自定义 View 对象的宽高
(1) onMeasure 方法 MeasureSpec 模式解析
常规处理方法 : 组件的宽高有三种情况, widthMeasureSpec 有三种模式 最大模式, 精准模式, 未定义模式;
-- 最大模式 : 在 组件的宽或高 warp_content 属性时, 会使用最大模式;
-- 精准模式 : 当给组件宽 或者高 定义一个值 或者 使用 match_parent 时, 会使用精准模式;
处理宽高的常规代码 :
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
-
- int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- int heightMode = MeasureSpec.getMode(heightMeasureSpec);
- int widthSize = MeasureSpec.getSize(widthMeasureSpec);
- int heightSize = MeasureSpec.getSize(heightMeasureSpec);
-
- Log.i(TAG, "宽度 : widthMode : " + getMode(widthMode) + " , widthSize : " + widthSize + "\n"
- + "高度 : heightMode : " + getMode(heightMode) + " , heightSize : " + heightSize);
-
- int width = 0;
- int height = 0;
-
-
-
-
- if (heightMode == MeasureSpec.EXACTLY) {
- height = heightSize;
-
- } else {
-
- height = 100;
-
-
- if (heightMode == MeasureSpec.AT_MOST) {
- height = Math.min(height, heightSize);
- }
- }
-
- if (widthMode == MeasureSpec.EXACTLY) {
- width = widthSize;
- } else {
- width = 100;
- if (heightMode == MeasureSpec.AT_MOST) {
- width = Math.min(width, widthSize);
- }
- }
-
- Log.i(TAG, "最终结果 : 宽度 : " + width + " , 高度 : " + height);
-
- setMeasuredDimension(width, height);
-
- }
-
-
- public String getMode(int mode) {
- String modeName = "";
- if(mode == MeasureSpec.EXACTLY){
- modeName = "精准模式";
- }else if(mode == MeasureSpec.AT_MOST){
- modeName = "最大模式";
- }else if(mode == MeasureSpec.UNSPECIFIED){
- modeName = "未定义模式";
- }
-
- return modeName;
- }
(2) 测试上述代码
使用下面的自定义组件测试 :
- package cn.org.octopus.wheelview;
-
- import android.content.Context;
- import android.graphics.Canvas;
- import android.graphics.Color;
- import android.util.AttributeSet;
- import android.util.Log;
- import android.view.View;
-
- public class MyView extends View {
-
- public static final String TAG = "octopus.my.view";
-
- public MyView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public MyView(Context context) {
- super(context);
- }
-
- public MyView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
-
-
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
-
- int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- int heightMode = MeasureSpec.getMode(heightMeasureSpec);
- int widthSize = MeasureSpec.getSize(widthMeasureSpec);
- int heightSize = MeasureSpec.getSize(heightMeasureSpec);
-
- Log.i(TAG, "宽度 : widthMode : " + getMode(widthMode) + " , widthSize : " + widthSize + "\n"
- + "高度 : heightMode : " + getMode(heightMode) + " , heightSize : " + heightSize);
-
- int width = 0;
- int height = 0;
-
-
-
-
- if (heightMode == MeasureSpec.EXACTLY) {
- height = heightSize;
-
- } else {
-
- height = 100;
-
-
- if (heightMode == MeasureSpec.AT_MOST) {
- height = Math.min(height, heightSize);
- }
- }
-
- if (widthMode == MeasureSpec.EXACTLY) {
- width = widthSize;
- } else {
- width = 100;
- if (heightMode == MeasureSpec.AT_MOST) {
- width = Math.min(width, widthSize);
- }
- }
-
- Log.i(TAG, "最终结果 : 宽度 : " + width + " , 高度 : " + height);
-
- setMeasuredDimension(width, height);
-
- }
-
-
- public String getMode(int mode) {
- String modeName = "";
- if(mode == MeasureSpec.EXACTLY){
- modeName = "精准模式";
- }else if(mode == MeasureSpec.AT_MOST){
- modeName = "最大模式";
- }else if(mode == MeasureSpec.UNSPECIFIED){
- modeName = "未定义模式";
- }
-
- return modeName;
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
- canvas.drawColor(Color.BLUE);
- }
-
- }
给定具体值情况 :
-- 组件信息 :
- <cn.org.octopus.wheelview.MyView
- android:layout_width="300dip"
- android:layout_height="300dip"/>
--
日志信息 :
- 11-30 01:40:24.304: I/octopus.my.view(2609): 宽度 : widthMode : 精准模式 , widthSize : 450
- 11-30 01:40:24.304: I/octopus.my.view(2609): 高度 : heightMode : 最大模式 , heightSize : 850
- 11-30 01:40:24.304: I/octopus.my.view(2609): 最终结果 : 宽度 : 450 , 高度 : 100
- 11-30 01:40:24.304: I/octopus.my.view(2609): 宽度 : widthMode : 精准模式 , widthSize : 450
- 11-30 01:40:24.304: I/octopus.my.view(2609): 高度 : heightMode : 精准模式 , heightSize : 450
- 11-30 01:40:24.304: I/octopus.my.view(2609): 最终结果 : 宽度 : 450 , 高度 : 450
- 11-30 01:40:24.335: I/octopus.my.view(2609): 宽度 : widthMode : 精准模式 , widthSize : 450
- 11-30 01:40:24.335: I/octopus.my.view(2609): 高度 : heightMode : 最大模式 , heightSize : 850
- 11-30 01:40:24.335: I/octopus.my.view(2609): 最终结果 : 宽度 : 450 , 高度 : 100
- 11-30 01:40:24.335: I/octopus.my.view(2609): 宽度 : widthMode : 精准模式 , widthSize : 450
- 11-30 01:40:24.335: I/octopus.my.view(2609): 高度 : heightMode : 精准模式 , heightSize : 450
- 11-30 01:40:24.335: I/octopus.my.view(2609): 最终结果 : 宽度 : 450 , 高度 : 450
warp_content 情况 :
-- 组件信息 :
- <cn.org.octopus.wheelview.MyView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
--
日志信息 :
- 11-30 01:37:47.351: I/octopus.my.view(1803): 宽度 : widthMode : 最大模式 , widthSize : 492
- 11-30 01:37:47.351: I/octopus.my.view(1803): 高度 : heightMode : 最大模式 , heightSize : 850
- 11-30 01:37:47.351: I/octopus.my.view(1803): 最终结果 : 宽度 : 100 , 高度 : 100
- 11-30 01:37:47.351: I/octopus.my.view(1803): 宽度 : widthMode : 精准模式 , widthSize : 100
- 11-30 01:37:47.351: I/octopus.my.view(1803): 高度 : heightMode : 最大模式 , heightSize : 802
- 11-30 01:37:47.351: I/octopus.my.view(1803): 最终结果 : 宽度 : 100 , 高度 : 100
- 11-30 01:37:47.390: I/octopus.my.view(1803): 宽度 : widthMode : 最大模式 , widthSize : 492
- 11-30 01:37:47.390: I/octopus.my.view(1803): 高度 : heightMode : 最大模式 , heightSize : 850
- 11-30 01:37:47.390: I/octopus.my.view(1803): 最终结果 : 宽度 : 100 , 高度 : 100
- 11-30 01:37:47.390: I/octopus.my.view(1803): 宽度 : widthMode : 精准模式 , widthSize : 100
- 11-30 01:37:47.390: I/octopus.my.view(1803): 高度 : heightMode : 最大模式 , heightSize : 802
- 11-30 01:37:47.390: I/octopus.my.view(1803): 最终结果 : 宽度 : 100 , 高度 : 100
match_parent 情况 :
-- 组件信息 :
- <cn.org.octopus.wheelview.MyView
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
-- 日志信息 :
- 11-30 01:39:08.296: I/octopus.my.view(2249): 宽度 : widthMode : 精准模式 , widthSize : 492
- 11-30 01:39:08.296: I/octopus.my.view(2249): 高度 : heightMode : 精准模式 , heightSize : 850
- 11-30 01:39:08.296: I/octopus.my.view(2249): 最终结果 : 宽度 : 492 , 高度 : 850
- 11-30 01:39:08.296: I/octopus.my.view(2249): 宽度 : widthMode : 精准模式 , widthSize : 492
- 11-30 01:39:08.296: I/octopus.my.view(2249): 高度 : heightMode : 精准模式 , heightSize : 802
- 11-30 01:39:08.296: I/octopus.my.view(2249): 最终结果 : 宽度 : 492 , 高度 : 802
- 11-30 01:39:08.328: I/octopus.my.view(2249): 宽度 : widthMode : 精准模式 , widthSize : 492
- 11-30 01:39:08.328: I/octopus.my.view(2249): 高度 : heightMode : 精准模式 , heightSize : 850
- 11-30 01:39:08.328: I/octopus.my.view(2249): 最终结果 : 宽度 : 492 , 高度 : 850
- 11-30 01:39:08.328: I/octopus.my.view(2249): 宽度 : widthMode : 精准模式 , widthSize : 492
- 11-30 01:39:08.328: I/octopus.my.view(2249): 高度 : heightMode : 精准模式 , heightSize : 802
- 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
- package cn.org.octopus.wheelview.widget;
-
-
-
-
-
-
- public interface WheelAdapter {
-
-
-
-
-
-
- public int getItemsCount();
-
-
-
-
-
-
-
-
-
- public String getItem(int index);
-
-
-
-
-
-
-
- public int getMaximumLength();
- }
2. ArrayWheelAdapter
- package cn.org.octopus.wheelview.widget;
-
-
-
-
-
-
-
- public class ArrayWheelAdapter implements WheelAdapter {
-
-
- public static final int DEFAULT_LENGTH = -1;
-
-
- private T items[];
-
- private int length;
-
-
-
-
-
-
-
-
-
- public ArrayWheelAdapter(T items[], int length) {
- this.items = items;
- this.length = length;
- }
-
-
-
-
-
-
-
- public ArrayWheelAdapter(T items[]) {
- this(items, DEFAULT_LENGTH);
- }
-
-
- @Override
- public String getItem(int index) {
-
- if (index >= 0 && index < items.length) {
- return items[index].toString();
- }
- return null;
- }
-
- @Override
- public int getItemsCount() {
-
- return items.length;
- }
-
- @Override
- public int getMaximumLength() {
-
- return length;
- }
-
- }
3. NumericWheelAdapter
- package cn.org.octopus.wheelview.widget;
-
-
-
-
- public class NumericWheelAdapter implements WheelAdapter {
-
-
- public static final int DEFAULT_MAX_VALUE = 9;
-
-
- private static final int DEFAULT_MIN_VALUE = 0;
-
-
- private int minValue;
-
- private int maxValue;
-
-
- private String format;
-
-
-
-
- public NumericWheelAdapter() {
- this(DEFAULT_MIN_VALUE, DEFAULT_MAX_VALUE);
- }
-
-
-
-
-
-
-
-
-
- public NumericWheelAdapter(int minValue, int maxValue) {
- this(minValue, maxValue, null);
- }
-
-
-
-
-
-
-
-
-
-
-
- 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;
-
- 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;
- }
-
- @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;
- }
- }
4. OnWheelChangedListener
- package cn.org.octopus.wheelview.widget;
-
-
-
-
- public interface OnWheelChangedListener {
-
-
-
-
-
-
-
-
-
-
- void onChanged(WheelView wheel, int oldValue, int newValue);
- }
5. OnWheelScrollListener
- package cn.org.octopus.wheelview.widget;
-
-
-
-
- public interface OnWheelScrollListener {
-
-
-
-
-
-
- void onScrollingStarted(WheelView wheel);
-
-
-
-
-
-
-
- void onScrollingFinished(WheelView wheel);
- }
6. WheelView