Android仿iPhone的时间轮的工具Demo

移动开发 Android
相信大家一定都见过iPhone上面的时间滚动轮的效果,类似轮盘一样的滚动来选择数据,非常有意思,动画效果也很生动,相比较安卓自带的spinner,TimePicker等控件,用户体验要好很多。在android上面去实现这样的效果,需要自定义view来实现,下面一个demo,进行了详细的注释,希望对您有所帮助。

相信大家一定都见过iPhone上面的时间滚动轮的效果,类似轮盘一样的滚动来选择数据,非常有意思,动画效果也很生动,相比较安卓自带的spinner,TimePicker等控件,用户体验要好很多。在android上面去实现这样的效果,需要自定义view来实现,下面一个demo,进行了详细的注释,希望对您有所帮助,下面先放上改动之后的效果图片:

由于代码比较多,只贴上了自定义view的相关代码:

  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 changingListeners = new LinkedList(); 
  129.  
  130.     /** 实例化OnWheelScrollListener */ 
  131.     private List scrollingListeners = new LinkedList(); 
  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.  

 

责任编辑:徐川 来源: eoeAndroid
相关推荐

2011-06-03 09:34:14

Android iphone tab

2011-06-03 09:05:18

Android iphone tab

2013-08-02 10:20:03

android时间轴

2021-08-01 09:55:57

Netty时间轮中间件

2015-02-02 16:42:49

特效密码锁

2022-03-24 10:23:51

时间轮方法任务

2020-10-15 17:38:00

Time Wheel

2011-08-09 11:36:41

iPhoneUIPickerVieDEMO

2023-04-07 08:55:40

AndroidFlutter

2018-06-06 10:14:32

Kafka时间轮任务

2011-07-06 17:53:40

iPhone SDK Xcode

2010-11-24 09:27:39

调试器部署争论

2016-09-08 14:50:59

AndroidiPhoneiOS

2013-01-15 14:13:05

UbuntuAndroid

2011-05-16 17:19:29

游戏开发iPhone

2014-08-18 14:18:07

Android桌面悬浮

2015-10-23 13:36:22

2012-12-26 11:35:02

iPhone苹果三星

2011-08-17 13:27:08

iPhone游戏开发objective-c

2017-07-23 10:30:38

CTO职责Node.JS
点赞
收藏

51CTO技术栈公众号