相信大家一定都见过iPhone上面的时间滚动轮的效果,类似轮盘一样的滚动来选择数据,非常有意思,动画效果也很生动,相比较安卓自带的spinner,TimePicker等控件,用户体验要好很多,但是不知道怎么在android上面去实现这样的效果,后来在网上搜索到了这一效果需要自定义view来实现,下载一个相关的的demo,由于不知道原作者的出处,没有放相关的连接,下下来后自己讲里面详细的进行了注释,整理成一篇blog,记录一下学习的过程,下面先放上我自己改动之后的效果图片:

                         

                     


由于代码比较多,只贴上了自定义view的相关代码,想看实际效果的话请下载整个demo研究

 

 

  1. package com.cogent.iPhonewheel.widget;  
  2.   
  3. import java.util.LinkedList;  
  4. import java.util.List;  
  5.   
  6. import android.content.Context;  
  7. import android.graphics.Canvas;  
  8. import android.graphics.Paint;  
  9. import android.graphics.Rect;  
  10. import android.graphics.drawable.Drawable;  
  11. import android.graphics.drawable.GradientDrawable;  
  12. import android.graphics.drawable.GradientDrawable.Orientation;  
  13. import android.os.Handler;  
  14. import android.os.Message;  
  15. import android.text.Layout;  
  16. import android.text.StaticLayout;  
  17. import android.text.TextPaint;  
  18. import android.util.AttributeSet;  
  19. import android.util.FloatMath;  
  20. import android.view.GestureDetector;  
  21. import android.view.MotionEvent;  
  22. import android.view.View;  
  23. import android.view.GestureDetector.SimpleOnGestureListener;  
  24. import android.view.animation.Interpolator;  
  25. import android.widget.Scroller;  
  26.   
  27. import com.cogent.iPhonewheel.Interface.OnWheelChangedListener;  
  28. import com.cogent.iPhonewheel.Interface.OnWheelScrollListener;  
  29. import com.cogent.iPhonewheel.Interface.WheelAdapter;  
  30. import com.cogent.iPhonewheel.UI.R;  
  31.   
  32. /** 
  33.  * 自定义的滚轮view 
  34.  *  
  35.  * @author Administrator 
  36.  *  
  37.  */  
  38. public class WheelView extends View {  
  39.     /** 滚动持续的时间 */  
  40.     private static final int SCROLLING_DURATION = 400;  
  41.   
  42.     /** 最少滚动的位置 */  
  43.     private static final int MIN_DELTA_FOR_SCROLLING = 1;  
  44.   
  45.     /** 当前值和标签的颜色 */  
  46.     private static final int VALUE_TEXT_COLOR = 0xF0FF6347;  
  47.   
  48.     /** item文字的颜色 */  
  49.     private static final int ITEMS_TEXT_COLOR = 0xFF000000;  
  50.   
  51.     /** 顶部和底部阴影的颜色 */  
  52.     private static final int[] SHADOWS_COLORS = new int[] { 0xFF111111,  
  53.             0x00AAAAAA0x00AAAAAA };  
  54.   
  55.     /** 附加的item的高度 */  
  56.     private static final int ADDITIONAL_ITEM_HEIGHT = 15;  
  57.   
  58.     /** 字体大小 */  
  59.     private static final int TEXT_SIZE = 30;  
  60.   
  61.     /** 顶部和底部item的偏移值 */  
  62.     private static final int ITEM_OFFSET = TEXT_SIZE / 5;  
  63.   
  64.     /** item布局的附加宽度 */  
  65.     private static final int ADDITIONAL_ITEMS_SPACE = 10;  
  66.   
  67.     /** 标签偏移值 */  
  68.     private static final int LABEL_OFFSET = 8;  
  69.   
  70.     /** 左右padding值 */  
  71.     private static final int PADDING = 10;  
  72.   
  73.     /** 默认可见的item数目 */  
  74.     private static final int DEF_VISIABLE_ITEMS = 5;  
  75.   
  76.     /** 初始化wheeladpater */  
  77.     private WheelAdapter adapter = null;  
  78.   
  79.     /** 当前item位置 */  
  80.     private int currentItem = 0;  
  81.   
  82.     /** item宽度 */  
  83.     private int itemsWidth = 0;  
  84.   
  85.     /** 标签宽度 */  
  86.     private int labelWidth = 0;  
  87.   
  88.     /** 可见item数目 */  
  89.     private int visibleItems = DEF_VISIABLE_ITEMS;  
  90.   
  91.     /** item高度 */  
  92.     private int itemHeight = 0;  
  93.   
  94.     /** item的字符串属性对象 */  
  95.     private TextPaint itemsPaint;  
  96.   
  97.     /** value的字符串属性对象 */  
  98.     private TextPaint valuePaint;  
  99.   
  100.     // Layouts  
  101.     private StaticLayout itemsLayout, labelLayout, valueLayout;  
  102.   
  103.     private String label;  
  104.     private Drawable centerDrawable;  
  105.   
  106.     /** 顶部渐变drawable对象 */  
  107.     private GradientDrawable topShadow;  
  108.   
  109.     /** 顶部渐变drawable对象 */  
  110.     private GradientDrawable bottomShadow;  
  111.   
  112.     /** 滚动动作是否执行 */  
  113.     private boolean isScrollingPerformed;  
  114.   
  115.     /** 滚动偏移量 */  
  116.     private int scrollingOffset;  
  117.   
  118.     /** 手势侦测对象 */  
  119.     private GestureDetector gestureDetector;  
  120.   
  121.     private Scroller scroller;  
  122.     private int lastScrollY;  
  123.   
  124.     /** 是否可循环 */  
  125.     private boolean isCyclic = false;  
  126.   
  127.     /** 实例化OnWheelChangedListener */  
  128.     private List<OnWheelChangedListener> changingListeners = new LinkedList<OnWheelChangedListener>();  
  129.   
  130.     /** 实例化OnWheelScrollListener */  
  131.     private List<OnWheelScrollListener> scrollingListeners = new LinkedList<OnWheelScrollListener>();  
  132.   
  133.     /** 
  134.      * 3个参数构造函数 
  135.      */  
  136.     public WheelView(Context context, AttributeSet attrs, int defStyle) {  
  137.         super(context, attrs, defStyle);  
  138.         initData(context);  
  139.     }  
  140.   
  141.     /** 
  142.      * 2个参数构造函数 
  143.      */  
  144.     public WheelView(Context context, AttributeSet attrs) {  
  145.         super(context, attrs);  
  146.         initData(context);  
  147.     }  
  148.   
  149.     /** 
  150.      * 1个参数构造函数 
  151.      */  
  152.     public WheelView(Context context) {  
  153.         super(context);  
  154.         initData(context);  
  155.     }  
  156.   
  157.     private void initData(Context context) {  
  158.         gestureDetector = new GestureDetector(context, gestureListener);  
  159.         gestureDetector.setIsLongpressEnabled(false);// 设置手势长按不起作用  
  160.   
  161.         scroller = new Scroller(context);  
  162.     }  
  163.   
  164.     /** 
  165.      * 获取滚轮适配器 
  166.      *  
  167.      * @return 
  168.      */  
  169.     public WheelAdapter getAdapter() {  
  170.         return adapter;  
  171.     }  
  172.   
  173.     /** 
  174.      * 设置滚轮适配器 
  175.      *  
  176.      * @param adapter 
  177.      */  
  178.     public void setAdapter(WheelAdapter adapter) {  
  179.         this.adapter = adapter;  
  180.         invalidateLayouts();  
  181.         invalidate();// 是视图无效  
  182.   
  183.     }  
  184.   
  185.     /** 
  186.      * 设置指定的滚轮动画变化率 
  187.      *  
  188.      * @param interpolator 
  189.      */  
  190.     public void setInterpolator(Interpolator interpolator) {  
  191.         scroller.forceFinished(true);  
  192.         scroller = new Scroller(getContext(), interpolator);  
  193.     }  
  194.   
  195.     /** 
  196.      * 得到可见item的数目 
  197.      *  
  198.      * @return the count of visible items 
  199.      */  
  200.     public int getVisibleItems() {  
  201.         return visibleItems;  
  202.     }  
  203.   
  204.     /** 
  205.      * 设置可见item的数目 
  206.      *  
  207.      * @param count 
  208.      *            the new count 
  209.      */  
  210.     public void setVisibleItems(int count) {  
  211.         visibleItems = count;  
  212.         invalidate();  
  213.     }  
  214.   
  215.     /** 
  216.      * 得到标签 
  217.      *  
  218.      * @return 
  219.      */  
  220.     public String getLabel() {  
  221.         return label;  
  222.     }  
  223.   
  224.     /** 
  225.      * 设置标签 
  226.      *  
  227.      * @param newLabel 
  228.      */  
  229.     public void setLabel(String newLabel) {  
  230.         if (label == null || !label.equals(newLabel)) {  
  231.             label = newLabel;  
  232.             labelLayout = null;  
  233.             invalidate();  
  234.         }  
  235.     }  
  236.   
  237.     /** 
  238.      * 增加滚轮变化监听器 
  239.      *  
  240.      * @param listener 
  241.      */  
  242.     public void addChangingListener(OnWheelChangedListener listener) {  
  243.         changingListeners.add(listener);  
  244.     }  
  245.   
  246.     /** 
  247.      * 移除滚轮变化监听器 
  248.      *  
  249.      * @param listener 
  250.      */  
  251.     public void removeChangingListener(OnWheelChangedListener listener) {  
  252.         changingListeners.remove(listener);  
  253.     }  
  254.   
  255.     /** 
  256.      * 通知改变的监听器 
  257.      *  
  258.      * @param oldValue 
  259.      * @param newValue 
  260.      */  
  261.     protected void notifyChangingListeners(int oldValue, int newValue) {  
  262.         for (OnWheelChangedListener listener : changingListeners) {  
  263.             listener.onChanged(this, oldValue, newValue);  
  264.         }  
  265.     }  
  266.   
  267.     /** 
  268.      * 增加滚轮监听器 
  269.      *  
  270.      * @param listener 
  271.      *            the listener 
  272.      */  
  273.     public void addScrollingListener(OnWheelScrollListener listener) {  
  274.         scrollingListeners.add(listener);  
  275.     }  
  276.   
  277.     /** 
  278.      * 移除滚轮监听器 
  279.      *  
  280.      * @param listener 
  281.      *            the listener 
  282.      */  
  283.     public void removeScrollingListener(OnWheelScrollListener listener) {  
  284.         scrollingListeners.remove(listener);  
  285.     }  
  286.   
  287.     /** 
  288.      * 通知监听器开始滚动 
  289.      */  
  290.     protected void notifyScrollingListenersAboutStart() {  
  291.         for (OnWheelScrollListener listener : scrollingListeners) {  
  292.             listener.onScrollingStarted(this);  
  293.         }  
  294.     }  
  295.   
  296.     /** 
  297.      * 通知监听器结束滚动 
  298.      */  
  299.     protected void notifyScrollingListenersAboutEnd() {  
  300.         for (OnWheelScrollListener listener : scrollingListeners) {  
  301.             listener.onScrollingFinished(this);  
  302.         }  
  303.     }  
  304.   
  305.     /** 
  306.      * 取得当前item 
  307.      *  
  308.      * @return 
  309.      */  
  310.     public int getCurrentItem() {  
  311.         return currentItem;  
  312.     }  
  313.   
  314.     /** 
  315.      * 设置当前item 
  316.      * @param index 
  317.      * @param animated 
  318.      */  
  319.     public void setCurrentItem(int index, boolean animated) {  
  320.         if (adapter == null || adapter.getItemsCount() == 0) {  
  321.             return;  
  322.         }  
  323.         if (index < 0 || index >= adapter.getItemsCount()) {  
  324.             if (isCyclic) {  
  325.                 while (index < 0) {  
  326.                     index += adapter.getItemsCount();  
  327.                 }  
  328.                 index %= adapter.getItemsCount();  
  329.             } else {  
  330.                 return;  
  331.             }  
  332.         }  
  333.         if (index != currentItem) {  
  334.             if (animated) {  
  335.                 scroll(index - currentItem, SCROLLING_DURATION);  
  336.             } else {  
  337.                 invalidateLayouts();  
  338.   
  339.                 int old = currentItem;  
  340.                 currentItem = index;  
  341.   
  342.                 notifyChangingListeners(old, currentItem);  
  343.                 invalidate();  
  344.             }  
  345.         }  
  346.     }  
  347.   
  348.     /** 
  349.      * 设置当前item w/o 动画. 当index有误是不做任何响应. 
  350.      *  
  351.      * @param index 
  352.      *            the item index 
  353.      */  
  354.     public void setCurrentItem(int index) {  
  355.         setCurrentItem(index, false);  
  356.     }  
  357.   
  358.     /** 
  359.      * 测试滚轮是否可循环. 
  360.      *  
  361.      * @return true if wheel is cyclic 
  362.      */  
  363.     public boolean isCyclic() {  
  364.         return isCyclic;  
  365.     }  
  366.   
  367.     /** 
  368.      * 设置滚轮循环标志 
  369.      *  
  370.      * @param isCyclic 
  371.      *            the flag to set 
  372.      */  
  373.     public void setCyclic(boolean isCyclic) {  
  374.         this.isCyclic = isCyclic;  
  375.   
  376.         invalidate();  
  377.         invalidateLayouts();  
  378.     }  
  379.   
  380.     /** 
  381.      * 使布局无效 
  382.      */  
  383.     private void invalidateLayouts() {  
  384.         itemsLayout = null;  
  385.         valueLayout = null;  
  386.         scrollingOffset = 0;  
  387.     }  
  388.   
  389.     /** 
  390.      * 初始化资源信息 
  391.      */  
  392.     private void initResourceIfNecessary() {  
  393.         if (itemsPaint == null) {  
  394.             itemsPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG  
  395.                     | Paint.FAKE_BOLD_TEXT_FLAG);  
  396.             itemsPaint.setTextSize(TEXT_SIZE);  
  397.         }  
  398.   
  399.         if (valuePaint == null) {  
  400.             valuePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG  
  401.                     | Paint.FAKE_BOLD_TEXT_FLAG | Paint.DITHER_FLAG);  
  402.             valuePaint.setTextSize(TEXT_SIZE);  
  403.             valuePaint.setShadowLayer(0.1f, 00.1f, 0xFFC0C0C0);  
  404.         }  
  405.   
  406.         if (centerDrawable == null) {  
  407.             centerDrawable = getContext().getResources().getDrawable(  
  408.                     R.drawable.wheel_val);  
  409.         }  
  410.   
  411.         if (topShadow == null) {  
  412.             topShadow = new GradientDrawable(Orientation.TOP_BOTTOM,  
  413.                     SHADOWS_COLORS);  
  414.         }  
  415.   
  416.         if (bottomShadow == null) {  
  417.             bottomShadow = new GradientDrawable(Orientation.BOTTOM_TOP,  
  418.                     SHADOWS_COLORS);  
  419.         }  
  420.   
  421.         setBackgroundResource(R.drawable.wheel_bg);  
  422.     }  
  423.   
  424.       
  425.     /** 
  426.      * 计算layout所需的高度 
  427.      * @param layout 
  428.      * @return 
  429.      */  
  430.     private int getDesiredHeight(Layout layout) {  
  431.         if (layout == null) {  
  432.             return 0;  
  433.         }  
  434.   
  435.         int desired = getItemHeight() * visibleItems - ITEM_OFFSET * 2  
  436.                 - ADDITIONAL_ITEM_HEIGHT;  
  437.           
  438.         desired = Math.max(desired,getSuggestedMinimumHeight());  
  439.         return desired;  
  440.     }  
  441.       
  442.       
  443.     /** 
  444.      * 通过index得到text 
  445.      * @param index 
  446.      * @return 
  447.      */  
  448.     private String getTextItem(int index){  
  449.         if(adapter == null || adapter.getItemsCount() == 0){  
  450.             return null;  
  451.         }  
  452.         int count = adapter.getItemsCount();  
  453.         if((index < 0 || index >= count) && !isCyclic){  
  454.             return null;  
  455.         }else{  
  456.             while(index < 0){  
  457.                 index += count;  
  458.             }  
  459.         }  
  460.         index %= count;  
  461.         return adapter.getItem(index);  
  462.     }  
  463.       
  464.     /** 
  465.      * 根据当前值构建text 
  466.      *  
  467.      * @param useCurrentValue 
  468.      * @return the text 
  469.      */  
  470.     private String buildText(boolean useCurrentValue) {  
  471.         StringBuilder itemsText = new StringBuilder();  
  472.         int addItems = visibleItems / 2 + 1;  
  473.   
  474.         for (int i = currentItem - addItems; i <= currentItem + addItems; i++) {  
  475.             if (useCurrentValue || i != currentItem) {  
  476.                 String text = getTextItem(i);  
  477.                 if (text != null) {  
  478.                     itemsText.append(text);  
  479.                 }  
  480.             }  
  481.             if (i < currentItem + addItems) {  
  482.                 itemsText.append("\n");  
  483.             }  
  484.         }  
  485.           
  486.         return itemsText.toString();  
  487.     }  
  488.   
  489.     /** 
  490.      * 返回可以表示的item的最大长度 
  491.      * @return the max length 
  492.      */  
  493.     private int getMaxTextLength() {  
  494.         WheelAdapter adapter = getAdapter();  
  495.         if (adapter == null) {  
  496.             return 0;  
  497.         }  
  498.           
  499.         int adapterLength = adapter.getMaximumLength();  
  500.         if (adapterLength > 0) {  
  501.             return adapterLength;  
  502.         }  
  503.   
  504.         String maxText = null;  
  505.         int addItems = visibleItems / 2;  
  506.         for (int i = Math.max(currentItem - addItems, 0);  
  507.                 i < Math.min(currentItem + visibleItems, adapter.getItemsCount()); i++) {  
  508.             String text = adapter.getItem(i);  
  509.             if (text != null && (maxText == null || maxText.length() < text.length())) {  
  510.                 maxText = text;  
  511.             }  
  512.         }  
  513.   
  514.         return maxText != null ? maxText.length() : 0;  
  515.     }  
  516.   
  517.     /** 
  518.      * 返回滚轮item的高度 
  519.      * @return the item height 
  520.      */  
  521.     private int getItemHeight() {  
  522.         if (itemHeight != 0) {  
  523.             return itemHeight;  
  524.         } else if (itemsLayout != null && itemsLayout.getLineCount() > 2) {  
  525.             itemHeight = itemsLayout.getLineTop(2) - itemsLayout.getLineTop(1);  
  526.             return itemHeight;  
  527.         }  
  528.           
  529.         return getHeight() / visibleItems;  
  530.     }  
  531.   
  532.     /** 
  533.      * 计算控制宽度和创建text布局 
  534.      * @param widthSize the input layout width 
  535.      * @param mode the layout mode 
  536.      * @return the calculated control width 
  537.      */  
  538.     private int calculateLayoutWidth(int widthSize, int mode) {  
  539.         initResourceIfNecessary();  
  540.   
  541.         int width = widthSize;  
  542.   
  543.         int maxLength = getMaxTextLength();  
  544.         if (maxLength > 0) {  
  545.             float textWidth = FloatMath.ceil(Layout.getDesiredWidth("0", itemsPaint));  
  546.             itemsWidth = (int) (maxLength * textWidth);  
  547.         } else {  
  548.             itemsWidth = 0;  
  549.         }  
  550.         itemsWidth += ADDITIONAL_ITEMS_SPACE; // make it some more  
  551.   
  552.         labelWidth = 0;  
  553.         if (label != null && label.length() > 0) {  
  554.             labelWidth = (int) FloatMath.ceil(Layout.getDesiredWidth(label, valuePaint));  
  555.         }  
  556.   
  557.         boolean recalculate = false;  
  558.         if (mode == MeasureSpec.EXACTLY) {  
  559.             width = widthSize;  
  560.             recalculate = true;  
  561.         } else {  
  562.             width = itemsWidth + labelWidth + 2 * PADDING;  
  563.             if (labelWidth > 0) {  
  564.                 width += LABEL_OFFSET;  
  565.             }  
  566.   
  567.             // Check against our minimum width  
  568.             width = Math.max(width, getSuggestedMinimumWidth());  
  569.   
  570.             if (mode == MeasureSpec.AT_MOST && widthSize < width) {  
  571.                 width = widthSize;  
  572.                 recalculate = true;  
  573.             }  
  574.         }  
  575.   
  576.         if (recalculate) {  
  577.             // recalculate width  
  578.             int pureWidth = width - LABEL_OFFSET - 2 * PADDING;  
  579.             if (pureWidth <= 0) {  
  580.                 itemsWidth = labelWidth = 0;  
  581.             }  
  582.             if (labelWidth > 0) {  
  583.                 double newWidthItems = (double) itemsWidth * pureWidth  
  584.                         / (itemsWidth + labelWidth);  
  585.                 itemsWidth = (int) newWidthItems;  
  586.                 labelWidth = pureWidth - itemsWidth;  
  587.             } else {  
  588.                 itemsWidth = pureWidth + LABEL_OFFSET; // no label  
  589.             }  
  590.         }  
  591.   
  592.         if (itemsWidth > 0) {  
  593.             createLayouts(itemsWidth, labelWidth);  
  594.         }  
  595.   
  596.         return width;  
  597.     }  
  598.   
  599.     /** 
  600.      * 创建布局 
  601.      * @param widthItems width of items layout 
  602.      * @param widthLabel width of label layout 
  603.      */  
  604.     private void createLayouts(int widthItems, int widthLabel) {  
  605.         if (itemsLayout == null || itemsLayout.getWidth() > widthItems) {  
  606.             itemsLayout = new StaticLayout(buildText(isScrollingPerformed), itemsPaint, widthItems,  
  607.                     widthLabel > 0 ? Layout.Alignment.ALIGN_OPPOSITE : Layout.Alignment.ALIGN_CENTER,  
  608.                     1, ADDITIONAL_ITEM_HEIGHT, false);  
  609.         } else {  
  610.             itemsLayout.increaseWidthTo(widthItems);  
  611.         }  
  612.   
  613.         if (!isScrollingPerformed && (valueLayout == null || valueLayout.getWidth() > widthItems)) {  
  614.             String text = getAdapter() != null ? getAdapter().getItem(currentItem) : null;  
  615.             valueLayout = new StaticLayout(text != null ? text : "",  
  616.                     valuePaint, widthItems, widthLabel > 0 ?  
  617.                             Layout.Alignment.ALIGN_OPPOSITE : Layout.Alignment.ALIGN_CENTER,  
  618.                             1, ADDITIONAL_ITEM_HEIGHT, false);  
  619.         } else if (isScrollingPerformed) {  
  620.             valueLayout = null;  
  621.         } else {  
  622.             valueLayout.increaseWidthTo(widthItems);  
  623.         }  
  624.   
  625.         if (widthLabel > 0) {  
  626.             if (labelLayout == null || labelLayout.getWidth() > widthLabel) {  
  627.                 labelLayout = new StaticLayout(label, valuePaint,  
  628.                         widthLabel, Layout.Alignment.ALIGN_NORMAL, 1,  
  629.                         ADDITIONAL_ITEM_HEIGHT, false);  
  630.             } else {  
  631.                 labelLayout.increaseWidthTo(widthLabel);  
  632.             }  
  633.         }  
  634.     }  
  635.   
  636.     @Override  
  637.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  638.         int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
  639.         int heightMode = MeasureSpec.getMode(heightMeasureSpec);  
  640.         int widthSize = MeasureSpec.getSize(widthMeasureSpec);  
  641.         int heightSize = MeasureSpec.getSize(heightMeasureSpec);  
  642.   
  643.         int width = calculateLayoutWidth(widthSize, widthMode);  
  644.   
  645.         int height;  
  646.         if (heightMode == MeasureSpec.EXACTLY) {  
  647.             height = heightSize;  
  648.         } else {  
  649.             height = getDesiredHeight(itemsLayout);  
  650.   
  651.             if (heightMode == MeasureSpec.AT_MOST) {  
  652.                 height = Math.min(height, heightSize);  
  653.             }  
  654.         }  
  655.   
  656.         setMeasuredDimension(width, height);  
  657.     }  
  658.   
  659.     @Override  
  660.     protected void onDraw(Canvas canvas) {  
  661.         super.onDraw(canvas);  
  662.           
  663.         if (itemsLayout == null) {  
  664.             if (itemsWidth == 0) {  
  665.                 calculateLayoutWidth(getWidth(), MeasureSpec.EXACTLY);  
  666.             } else {  
  667.                 createLayouts(itemsWidth, labelWidth);  
  668.             }  
  669.         }  
  670.   
  671.         if (itemsWidth > 0) {  
  672.             canvas.save();  
  673.             // Skip padding space and hide a part of top and bottom items  
  674.             canvas.translate(PADDING, -ITEM_OFFSET);  
  675.             drawItems(canvas);  
  676.             drawValue(canvas);  
  677.             canvas.restore();  
  678.         }  
  679.   
  680.         drawCenterRect(canvas);  
  681.         drawShadows(canvas);  
  682.     }  
  683.   
  684.     /** 
  685.      * 在顶部和底部画阴影的控制 
  686.      * @param canvas the canvas for drawing 
  687.      */  
  688.     private void drawShadows(Canvas canvas) {  
  689.         topShadow.setBounds(00, getWidth(), getHeight() / visibleItems);  
  690.         topShadow.draw(canvas);  
  691.   
  692.         bottomShadow.setBounds(0, getHeight() - getHeight() / visibleItems,  
  693.                 getWidth(), getHeight());  
  694.         bottomShadow.draw(canvas);  
  695.     }  
  696.   
  697.     /** 
  698.      * 画value和标签的布局 
  699.      * @param canvas the canvas for drawing 
  700.      */  
  701.     private void drawValue(Canvas canvas) {  
  702.         valuePaint.setColor(VALUE_TEXT_COLOR);  
  703.         valuePaint.drawableState = getDrawableState();  
  704.   
  705.         Rect bounds = new Rect();  
  706.         itemsLayout.getLineBounds(visibleItems / 2, bounds);  
  707.   
  708.         // draw label  
  709.         if (labelLayout != null) {  
  710.             canvas.save();  
  711.             canvas.translate(itemsLayout.getWidth() + LABEL_OFFSET, bounds.top);  
  712.             labelLayout.draw(canvas);  
  713.             canvas.restore();  
  714.         }  
  715.   
  716.         // draw current value  
  717.         if (valueLayout != null) {  
  718.             canvas.save();  
  719.             canvas.translate(0, bounds.top + scrollingOffset);  
  720.             valueLayout.draw(canvas);  
  721.             canvas.restore();  
  722.         }  
  723.     }  
  724.   
  725.     /** 
  726.      * 画items 
  727.      * @param canvas the canvas for drawing 
  728.      */  
  729.     private void drawItems(Canvas canvas) {  
  730.         canvas.save();  
  731.           
  732.         int top = itemsLayout.getLineTop(1);  
  733.         canvas.translate(0, - top + scrollingOffset);  
  734.           
  735.         itemsPaint.setColor(ITEMS_TEXT_COLOR);  
  736.         itemsPaint.drawableState = getDrawableState();  
  737.         itemsLayout.draw(canvas);  
  738.           
  739.         canvas.restore();  
  740.     }  
  741.   
  742.     /** 
  743.      * 画当前值的矩形 
  744.      * @param canvas the canvas for drawing 
  745.      */  
  746.     private void drawCenterRect(Canvas canvas) {  
  747.         int center = getHeight() / 2;  
  748.         int offset = getItemHeight() / 2;  
  749.         centerDrawable.setBounds(0, center - offset, getWidth(), center + offset);  
  750.         centerDrawable.draw(canvas);  
  751.     }  
  752.   
  753.     @Override  
  754.     public boolean onTouchEvent(MotionEvent event) {  
  755.         WheelAdapter adapter = getAdapter();  
  756.         if (adapter == null) {  
  757.             return true;  
  758.         }  
  759.           
  760.             if (!gestureDetector.onTouchEvent(event) && event.getAction() == MotionEvent.ACTION_UP) {  
  761.             justify();  
  762.         }  
  763.         return true;  
  764.     }  
  765.       
  766.     /** 
  767.      * 滚动滚轮 
  768.      * @param delta the scrolling value 
  769.      */  
  770.     private void doScroll(int delta) {  
  771.         scrollingOffset += delta;  
  772.           
  773.         int count = scrollingOffset / getItemHeight();  
  774.         int pos = currentItem - count;  
  775.         if (isCyclic && adapter.getItemsCount() > 0) {  
  776.             // fix position by rotating  
  777.             while (pos < 0) {  
  778.                 pos += adapter.getItemsCount();  
  779.             }  
  780.             pos %= adapter.getItemsCount();  
  781.         } else if (isScrollingPerformed) {  
  782.             //   
  783.             if (pos < 0) {  
  784.                 count = currentItem;  
  785.                 pos = 0;  
  786.             } else if (pos >= adapter.getItemsCount()) {  
  787.                 count = currentItem - adapter.getItemsCount() + 1;  
  788.                 pos = adapter.getItemsCount() - 1;  
  789.             }  
  790.         } else {  
  791.             // fix position  
  792.             pos = Math.max(pos, 0);  
  793.             pos = Math.min(pos, adapter.getItemsCount() - 1);  
  794.         }  
  795.           
  796.         int offset = scrollingOffset;  
  797.         if (pos != currentItem) {  
  798.             setCurrentItem(pos, false);  
  799.         } else {  
  800.             invalidate();  
  801.         }  
  802.           
  803.         // update offset  
  804.         scrollingOffset = offset - count * getItemHeight();  
  805.         if (scrollingOffset > getHeight()) {  
  806.             scrollingOffset = scrollingOffset % getHeight() + getHeight();  
  807.         }  
  808.     }  
  809.       
  810.     // gesture listener  
  811.     private SimpleOnGestureListener gestureListener = new SimpleOnGestureListener() {  
  812.         public boolean onDown(MotionEvent e) {  
  813.             if (isScrollingPerformed) {  
  814.                 scroller.forceFinished(true);  
  815.                 clearMessages();  
  816.                 return true;  
  817.             }  
  818.             return false;  
  819.         }  
  820.           
  821.         public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {  
  822.             startScrolling();  
  823.             doScroll((int)-distanceY);  
  824.             return true;  
  825.         }  
  826.           
  827.         public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {  
  828.             lastScrollY = currentItem * getItemHeight() + scrollingOffset;  
  829.             int maxY = isCyclic ? 0x7FFFFFFF : adapter.getItemsCount() * getItemHeight();  
  830.             int minY = isCyclic ? -maxY : 0;  
  831.             scroller.fling(0, lastScrollY, 0, (int) -velocityY / 200, minY, maxY);  
  832.             setNextMessage(MESSAGE_SCROLL);  
  833.             return true;  
  834.         }  
  835.     };  
  836.   
  837.   
  838.     // Messages  
  839.     private final int MESSAGE_SCROLL = 0;  
  840.     private final int MESSAGE_JUSTIFY = 1;  
  841.       
  842.     /** 
  843.      * Set next message to queue. Clears queue before. 
  844.      *  
  845.      * @param message the message to set 
  846.      */  
  847.     private void setNextMessage(int message) {  
  848.         clearMessages();  
  849.         animationHandler.sendEmptyMessage(message);  
  850.     }  
  851.   
  852.     /** 
  853.      * Clears messages from queue 
  854.      */  
  855.     private void clearMessages() {  
  856.         animationHandler.removeMessages(MESSAGE_SCROLL);  
  857.         animationHandler.removeMessages(MESSAGE_JUSTIFY);  
  858.     }  
  859.       
  860.     // animation handler  
  861.     private Handler animationHandler = new Handler() {  
  862.         public void handleMessage(Message msg) {  
  863.             scroller.computeScrollOffset();  
  864.             int currY = scroller.getCurrY();  
  865.             int delta = lastScrollY - currY;  
  866.             lastScrollY = currY;  
  867.             if (delta != 0) {  
  868.                 doScroll(delta);  
  869.             }  
  870.               
  871.             // scrolling is not finished when it comes to final Y  
  872.             // so, finish it manually   
  873.             if (Math.abs(currY - scroller.getFinalY()) < MIN_DELTA_FOR_SCROLLING) {  
  874.                 currY = scroller.getFinalY();  
  875.                 scroller.forceFinished(true);  
  876.             }  
  877.             if (!scroller.isFinished()) {  
  878.                 animationHandler.sendEmptyMessage(msg.what);  
  879.             } else if (msg.what == MESSAGE_SCROLL) {  
  880.                 justify();  
  881.             } else {  
  882.                 finishScrolling();  
  883.             }  
  884.         }  
  885.     };  
  886.       
  887.     /** 
  888.      * Justifies wheel 
  889.      */  
  890.     private void justify() {  
  891.         if (adapter == null) {  
  892.             return;  
  893.         }  
  894.           
  895.         lastScrollY = 0;  
  896.         int offset = scrollingOffset;  
  897.         int itemHeight = getItemHeight();  
  898.         boolean needToIncrease = offset > 0 ? currentItem < adapter.getItemsCount() : currentItem > 0;   
  899.         if ((isCyclic || needToIncrease) && Math.abs((float) offset) > (float) itemHeight / 2) {  
  900.             if (offset < 0)  
  901.                 offset += itemHeight + MIN_DELTA_FOR_SCROLLING;  
  902.             else  
  903.                 offset -= itemHeight + MIN_DELTA_FOR_SCROLLING;  
  904.         }  
  905.         if (Math.abs(offset) > MIN_DELTA_FOR_SCROLLING) {  
  906.             scroller.startScroll(000, offset, SCROLLING_DURATION);  
  907.             setNextMessage(MESSAGE_JUSTIFY);  
  908.         } else {  
  909.             finishScrolling();  
  910.         }  
  911.     }  
  912.       
  913.     /** 
  914.      * 开始滚动 
  915.      */  
  916.     private void startScrolling() {  
  917.         if (!isScrollingPerformed) {  
  918.             isScrollingPerformed = true;  
  919.             notifyScrollingListenersAboutStart();  
  920.         }  
  921.     }  
  922.   
  923.     /** 
  924.      * 停止滚动 
  925.      */  
  926.     void finishScrolling() {  
  927.         if (isScrollingPerformed) {  
  928.             notifyScrollingListenersAboutEnd();  
  929.             isScrollingPerformed = false;  
  930.         }  
  931.         invalidateLayouts();  
  932.         invalidate();  
  933.     }  
  934.   
  935.     public void scroll(int itemsToScroll, int time) {  
  936.         scroller.forceFinished(true);  
  937.   
  938.         lastScrollY = scrollingOffset;  
  939.   
  940.         int offset = itemsToScroll * getItemHeight();  
  941.   
  942.         scroller.startScroll(0, lastScrollY, 0, offset - lastScrollY, time);  
  943.         setNextMessage(MESSAGE_SCROLL);  
  944.   
  945.         startScrolling();  
  946.     }  
  947.   
  948. }

  这个demo还是比较实用的,可以自行将自定义的view按照自己需要进行修改扩展来实现自己想要的功能,放在今后自己开发的项目中去,当做一个控件直接使用,很方便 

 

 点击下载本demo的源代码

posted on 2012-08-27 10:12  Mr梵谷  阅读(2578)  评论(2编辑  收藏  举报