android仿iPhone滾輪控件實現及源碼分析

http://blog.csdn.net/aomandeshangxiao/article/details/7397697

    敬告:由於本文代碼較多,所以文章分爲了一二兩篇,如果不便,敬請諒解,可以先下載文章下方的代碼,打開參考本文查看,效果更好!        

 首先,先看下效果圖:


      這三張圖分別是使用滾動控件實現城市,隨機數和時間三個簡單的例子,當然,界面有點簡陋,下面我們就以時間這個爲例,開始解析一下。

     首先,先看下佈局文件:

[html] view plaincopy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2.   
  3. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  4.     android:layout_height="wrap_content"  
  5.     android:layout_width="fill_parent"  
  6.     android:layout_marginTop="12dp"  
  7.     android:orientation="vertical"  
  8.     android:background="@drawable/layout_bg">  
  9.       
  10.     <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  11.         android:layout_height="wrap_content"  
  12.         android:layout_width="fill_parent"  
  13.         android:layout_gravity="center_horizontal"  
  14.         android:paddingLeft="12dp"  
  15.         android:paddingRight="12dp"  
  16.         android:paddingTop="10dp">  
  17.         
  18.         <kankan.wheel.widget.WheelView android:id="@+id/hour"  
  19.             android:layout_height="wrap_content"  
  20.             android:layout_width="fill_parent"  
  21.             android:layout_weight="1"/>  
  22.         <kankan.wheel.widget.WheelView android:id="@+id/mins"  
  23.             android:layout_height="wrap_content"  
  24.             android:layout_width="fill_parent"  
  25.             android:layout_weight="1"/>  
  26.     </LinearLayout>  
  27.       
  28.     <TimePicker android:id="@+id/time"  
  29.         android:layout_marginTop="12dp"  
  30.         android:layout_height="wrap_content"  
  31.         android:layout_width="fill_parent"  
  32.         android:layout_weight="1"/>  
  33.           
  34. </LinearLayout>  

        裏面只有三個控件,兩個自定義的WheelView,還有一個TimePicker,然後進入代碼裏面看一下:

[java] view plaincopy
  1. public class TimeActivity extends Activity {  
  2.     // Time changed flag  
  3.     private boolean timeChanged = false;  
  4.       
  5.     //  
  6.     private boolean timeScrolled = false;  
  7.       
  8.     @Override  
  9.     public void onCreate(Bundle savedInstanceState) {  
  10.         super.onCreate(savedInstanceState);  
  11.   
  12.         setContentView(R.layout.time_layout);  
  13.       
  14.         final WheelView hours = (WheelView) findViewById(R.id.hour);  
  15.         hours.setAdapter(new NumericWheelAdapter(023));  
  16.         hours.setLabel("hours");  
  17.       
  18.         final WheelView mins = (WheelView) findViewById(R.id.mins);  
  19.         mins.setAdapter(new NumericWheelAdapter(059"%02d"));  
  20.         mins.setLabel("mins");  
  21.         mins.setCyclic(true);  
  22.       
  23.         final TimePicker picker = (TimePicker) findViewById(R.id.time);  
  24.         picker.setIs24HourView(true);  
  25.       
  26.         // set current time  
  27.         Calendar c = Calendar.getInstance();  
  28.         int curHours = c.get(Calendar.HOUR_OF_DAY);  
  29.         int curMinutes = c.get(Calendar.MINUTE);  
  30.       
  31.         hours.setCurrentItem(curHours);  
  32.         mins.setCurrentItem(curMinutes);  
  33.       
  34.         picker.setCurrentHour(curHours);  
  35.         picker.setCurrentMinute(curMinutes);  
  36.       
  37.         // add listeners  
  38.         addChangingListener(mins, "min");  
  39.         addChangingListener(hours, "hour");  
  40.       
  41.         OnWheelChangedListener wheelListener = new OnWheelChangedListener() {  
  42.             public void onChanged(WheelView wheel, int oldValue, int newValue) {  
  43.                 if (!timeScrolled) {  
  44.                     timeChanged = true;  
  45.                     picker.setCurrentHour(hours.getCurrentItem());  
  46.                     picker.setCurrentMinute(mins.getCurrentItem());  
  47.                     timeChanged = false;  
  48.                 }  
  49.             }  
  50.         };  
  51.   
  52.         hours.addChangingListener(wheelListener);  
  53.         mins.addChangingListener(wheelListener);  
  54.   
  55.         OnWheelScrollListener scrollListener = new OnWheelScrollListener() {  
  56.             public void onScrollingStarted(WheelView wheel) {  
  57.                 timeScrolled = true;  
  58.             }  
  59.             public void onScrollingFinished(WheelView wheel) {  
  60.                 timeScrolled = false;  
  61.                 timeChanged = true;  
  62.                 picker.setCurrentHour(hours.getCurrentItem());  
  63.                 picker.setCurrentMinute(mins.getCurrentItem());  
  64.                 timeChanged = false;  
  65.             }  
  66.         };  
  67.           
  68.         hours.addScrollingListener(scrollListener);  
  69.         mins.addScrollingListener(scrollListener);  
  70.           
  71.         picker.setOnTimeChangedListener(new TimePicker.OnTimeChangedListener() {  
  72.             public void onTimeChanged(TimePicker  view, int hourOfDay, int minute) {  
  73.                 if (!timeChanged) {  
  74.                     hours.setCurrentItem(hourOfDay, true);  
  75.                     mins.setCurrentItem(minute, true);  
  76.                 }  
  77.             }  
  78.         });  
  79.     }  
  80.   
  81.     /** 
  82.      * Adds changing listener for wheel that updates the wheel label 
  83.      * @param wheel the wheel 
  84.      * @param label the wheel label 
  85.      */  
  86.     private void addChangingListener(final WheelView wheel, final String label) {  
  87.         wheel.addChangingListener(new OnWheelChangedListener() {  
  88.             public void onChanged(WheelView wheel, int oldValue, int newValue) {  
  89.                 wheel.setLabel(newValue != 1 ? label + "s" : label);  
  90.             }  
  91.         });  
  92.     }  
  93. }  

     看一下,裏面調用WheelView的方法有setAdapter()、setLabel("mins")、setCyclic(true)、setCurrentItem()、getCurrentItem()、addChangingListener()、addScrollingListener()這些方法,其中setAapter設置數據適配器,setCyclic()設置是否是循環,setCurrentItem和getCurrentItem分別是設置現在選擇的item和返回現在選擇的item。後面兩個設置監聽的方法中,需要重寫兩個接口:

[java] view plaincopy
  1. /** 
  2.  * Wheel scrolled listener interface. 
  3.  */  
  4. public interface OnWheelScrollListener {  
  5.     /** 
  6.      * Callback method to be invoked when scrolling started. 
  7.      * @param wheel the wheel view whose state has changed. 
  8.      */  
  9.     void onScrollingStarted(WheelView wheel);  
  10.       
  11.     /** 
  12.      * Callback method to be invoked when scrolling ended. 
  13.      * @param wheel the wheel view whose state has changed. 
  14.      */  
  15.     void onScrollingFinished(WheelView wheel);  
  16. }  

[java] view plaincopy
  1. public interface OnWheelChangedListener {  
  2.     /** 
  3.      * Callback method to be invoked when current item changed 
  4.      * @param wheel the wheel view whose state has changed 
  5.      * @param oldValue the old value of current item 
  6.      * @param newValue the new value of current item 
  7.      */  
  8.     void onChanged(WheelView wheel, int oldValue, int newValue);  
  9. }  

在這裏使用的是典型的回調方法模式。

然後現在,我們進入WheelView類,看一下他是如何構建,首先,WheelView繼承了View類。代碼的22行到45行是導入的所需要的類。從54行到135行是聲明一些變量和類:

[java] view plaincopy
  1. /** Scrolling duration */  
  2.     private static final int SCROLLING_DURATION = 400;  
  3.   
  4.     /** Minimum delta for scrolling */  
  5.     private static final int MIN_DELTA_FOR_SCROLLING = 1;  
  6.   
  7.     /** Current value & label text color */  
  8.     private static final int VALUE_TEXT_COLOR = 0xF0000000;  
  9.   
  10.     /** Items text color */  
  11.     private static final int ITEMS_TEXT_COLOR = 0xFF000000;  
  12.   
  13.     /** Top and bottom shadows colors */  
  14.     private static final int[] SHADOWS_COLORS = new int[] { 0xFF111111,  
  15.             0x00AAAAAA0x00AAAAAA };  
  16.   
  17.     /** Additional items height (is added to standard text item height) */  
  18.     private static final int ADDITIONAL_ITEM_HEIGHT = 15;  
  19.   
  20.     /** Text size */  
  21.     private static final int TEXT_SIZE = 24;  
  22.   
  23.     /** Top and bottom items offset (to hide that) */  
  24.     private static final int ITEM_OFFSET = TEXT_SIZE / 5;  
  25.   
  26.     /** Additional width for items layout */  
  27.     private static final int ADDITIONAL_ITEMS_SPACE = 10;  
  28.   
  29.     /** Label offset */  
  30.     private static final int LABEL_OFFSET = 8;  
  31.   
  32.     /** Left and right padding value */  
  33.     private static final int PADDING = 10;  
  34.   
  35.     /** Default count of visible items */  
  36.     private static final int DEF_VISIBLE_ITEMS = 5;  
  37.   
  38.     // Wheel Values  
  39.     private WheelAdapter adapter = null;  
  40.     private int currentItem = 0;  
  41.       
  42.     // Widths  
  43.     private int itemsWidth = 0;  
  44.     private int labelWidth = 0;  
  45.   
  46.     // Count of visible items  
  47.     private int visibleItems = DEF_VISIBLE_ITEMS;  
  48.       
  49.     // Item height  
  50.     private int itemHeight = 0;  
  51.   
  52.     // Text paints  
  53.     private TextPaint itemsPaint;  
  54.     private TextPaint valuePaint;  
  55.   
  56.     // Layouts  
  57.     private StaticLayout itemsLayout;  
  58.     private StaticLayout labelLayout;  
  59.     private StaticLayout valueLayout;  
  60.   
  61.     // Label & background  
  62.     private String label;  
  63.     private Drawable centerDrawable;  
  64.   
  65.     // Shadows drawables  
  66.     private GradientDrawable topShadow;  
  67.     private GradientDrawable bottomShadow;  
  68.   
  69.     // Scrolling  
  70.     private boolean isScrollingPerformed;   
  71.     private int scrollingOffset;  
  72.   
  73.     // Scrolling animation  
  74.     private GestureDetector gestureDetector;  
  75.     private Scroller scroller;  
  76.     private int lastScrollY;  
  77.   
  78.     // Cyclic  
  79.     boolean isCyclic = false;  
  80.       
  81.     // Listeners  
  82.     private List<OnWheelChangedListener> changingListeners = new LinkedList<OnWheelChangedListener>();  
  83.     private List<OnWheelScrollListener> scrollingListeners = new LinkedList<OnWheelScrollListener>();  

          在這裏面,使用到了StaticLayout,在開發文檔中找一下這個類:

[plain] view plaincopy
  1. StaticLayout is a Layout for text that will not be edited after it is laid out. Use DynamicLayout for text that may change.  
  2.   
  3. This is used by widgets to control text layout. You should not need to use this class directly unless you are implementing your own widget or custom display object, or would be tempted to call Canvas.drawText() directly.  

staticLayout被創建以後就不能被修改了,通常被用於控制文本組件佈局。

    還使用到了Drawable、Text'Paint、GradientDrawable、GestureDetector、Scroller類,在開發文檔中,GradientDrawable的概述:

[plain] view plaincopy
  1. A Drawable with a color gradient for buttons, backgrounds, etc.  
  2.   
  3. It can be defined in an XML file with the <shape> element. For more information, see the guide to Drawable Resources.  

就是說這個類可以爲按鈕或者背景等提供漸變顏色的繪製。

TextPaint的概述:

[plain] view plaincopy
  1. TextPaint is an extension of Paint that leaves room for some extra data used during text measuring and drawing.  
  TextPaint是Paint類的一個擴展,主要是用於文本在繪製的過程中爲附件的數據留出空間。


GestureDetector:手勢檢測,看下開發文檔中關於該類的概述:

[plain] view plaincopy
  1. Detects various gestures and events using the supplied MotionEvents. The GestureDetector.OnGestureListener callback will notify users when a particular motion event has occurred. This class should only be used with MotionEvents reported via touch (don't use for trackball events).  

        爲各種手勢和事件提供MotionEvents。當一個具體的事件發生時會調用回調函數GestureDetector.OnGestureListener。這個類應該只適用於MotionEvents通過觸摸觸發的事件(不要使用追蹤事件)。


        140行到156行是構造方法,175到183行是set和getAdapter。在193行,setInterpolator()方法,設置interPolator這個動畫接口,我們看下這個接口的概述:

[plain] view plaincopy
  1. An interpolator defines the rate of change of an animation. This allows the basic animation effects (alpha, scale, translate, rotate) to be accelerated, decelerated, repeated, etc.  

定義了一種基於變率的一個動畫。這使得基本的動畫效果(alpha, scale, translate, rotate)是加速,減慢,重複等。這個方法在隨機數這個例子中被使用。

        203行到213行設置顯示的item條數。在setVisibleItems()方法裏面調用了View的invalidate()方法,看下文檔中對該方法的介紹:

[plain] view plaincopy
  1. Invalidate the whole view. If the view is visible, onDraw(android.graphics.Canvas) will be called at some point in the future. This must be called from a UI thread. To call from a non-UI thread, call postInvalidate().  

使全部視圖失效,如果View視圖是可見的,會在UI線程裏面從新調用onDraw()方法。

       223行到233行是設置Label,既後面圖片中的hours.

       245行到296行是設置監聽,在上面已經簡單的說了一下,這裏不在累述。

       307行到349行是設置正被選中item,就是在那個陰影條框下的那個部分,比較簡單。裏面主要調用了scroll這個方法:

[java] view plaincopy
  1. /** 
  2.      * Scroll the wheel 
  3.      * @param itemsToSkip items to scroll 
  4.      * @param time scrolling duration 
  5.      */  
  6.     public void scroll(int itemsToScroll, int time) {  
  7.         scroller.forceFinished(true);  
  8.         lastScrollY = scrollingOffset;  
  9.         int offset = itemsToScroll * getItemHeight();         
  10.         scroller.startScroll(0, lastScrollY, 0, offset - lastScrollY, time);  
  11.         setNextMessage(MESSAGE_SCROLL);       
  12.         startScrolling();  
  13.     }  


       357行到365行是設置item數據能否循環使用。

       384行的initResourcesIfNecessary()方法,從字面意思,如果需要的初始化資源。

[java] view plaincopy
  1. private void initResourcesIfNecessary() {  
  2.         if (itemsPaint == null) {  
  3.             itemsPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG  
  4.                     | Paint.FAKE_BOLD_TEXT_FLAG);  
  5.             //itemsPaint.density = getResources().getDisplayMetrics().density;  
  6.             itemsPaint.setTextSize(TEXT_SIZE);  
  7.         }  
  8.   
  9.         if (valuePaint == null) {  
  10.             valuePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG  
  11.                     | Paint.FAKE_BOLD_TEXT_FLAG | Paint.DITHER_FLAG);  
  12.             //valuePaint.density = getResources().getDisplayMetrics().density;  
  13.             valuePaint.setTextSize(TEXT_SIZE);  
  14.             valuePaint.setShadowLayer(0.1f, 00.1f, 0xFFC0C0C0);  
  15.         }  
  16.   
  17.         if (centerDrawable == null) {  
  18.             centerDrawable = getContext().getResources().getDrawable(R.drawable.wheel_val);  
  19.         }  
  20.   
  21.         if (topShadow == null) {  
  22.             topShadow = new GradientDrawable(Orientation.TOP_BOTTOM, SHADOWS_COLORS);  
  23.         }  
  24.   
  25.         if (bottomShadow == null) {  
  26.             bottomShadow = new GradientDrawable(Orientation.BOTTOM_TOP, SHADOWS_COLORS);  
  27.         }  
  28.   
  29.         setBackgroundResource(R.drawable.wheel_bg);  
  30.     }  


這個方法就是初始化在532行calculateLayoutWidth()方法中調用了這個方法,同時調用了487行的getMaxTextLength()這個方法。

      471行getTextItem(int index)通過一個索引獲取該item的文本。


      這是第一部分,沒有多少有太多意思的地方,重點的地方在以後532行到940行的內容,另起一篇,開始分析,這一篇先到這。

      最後是下載地址:

Android仿iPhone滾動控件源碼

http://download.csdn.net/detail/aomandeshangxiao/4175719


android仿iPhone滾輪控件實現及源碼分析(二)

分類: android小例子 429人閱讀 評論(8) 收藏 舉報

         在上一篇android仿iPhone滾輪控件實現及源碼分析(一)簡單的說了下架構還有效果圖,但是關於圖形的繪製各方面的代碼在532行到940行,如果寫在一篇文章裏面,可能會導致文章太長,效果不好,所以自作聰明的分成了兩篇大笑。閒言碎語不要講,下面開始正事。

       首先,先把代碼貼出來:

[java] view plaincopy
  1. /** 
  2.      * Calculates control width and creates text layouts 
  3.      * @param widthSize the input layout width 
  4.      * @param mode the layout mode 
  5.      * @return the calculated control width 
  6.      */  
  7.     private int calculateLayoutWidth(int widthSize, int mode) {  
  8.         initResourcesIfNecessary();  
  9.   
  10.         int width = widthSize;  
  11.         int maxLength = getMaxTextLength();  
  12.         if (maxLength > 0) {  
  13.             float textWidth = FloatMath.ceil(Layout.getDesiredWidth("0", itemsPaint));  
  14.             itemsWidth = (int) (maxLength * textWidth);  
  15.         } else {  
  16.             itemsWidth = 0;  
  17.         }  
  18.         itemsWidth += ADDITIONAL_ITEMS_SPACE; // make it some more  
  19.   
  20.         labelWidth = 0;  
  21.         if (label != null && label.length() > 0) {  
  22.             labelWidth = (int) FloatMath.ceil(Layout.getDesiredWidth(label, valuePaint));  
  23.         }  
  24.   
  25.         boolean recalculate = false;  
  26.         if (mode == MeasureSpec.EXACTLY) {  
  27.             width = widthSize;  
  28.             recalculate = true;  
  29.         } else {  
  30.             width = itemsWidth + labelWidth + 2 * PADDING;  
  31.             if (labelWidth > 0) {  
  32.                 width += LABEL_OFFSET;  
  33.             }  
  34.   
  35.             // Check against our minimum width  
  36.             width = Math.max(width, getSuggestedMinimumWidth());  
  37.   
  38.             if (mode == MeasureSpec.AT_MOST && widthSize < width) {  
  39.                 width = widthSize;  
  40.                 recalculate = true;  
  41.             }  
  42.         }  
  43.   
  44.         if (recalculate) {  
  45.             // recalculate width  
  46.             int pureWidth = width - LABEL_OFFSET - 2 * PADDING;  
  47.             if (pureWidth <= 0) {  
  48.                 itemsWidth = labelWidth = 0;  
  49.             }  
  50.             if (labelWidth > 0) {  
  51.                 double newWidthItems = (double) itemsWidth * pureWidth  
  52.                         / (itemsWidth + labelWidth);  
  53.                 itemsWidth = (int) newWidthItems;  
  54.                 labelWidth = pureWidth - itemsWidth;  
  55.             } else {  
  56.                 itemsWidth = pureWidth + LABEL_OFFSET; // no label  
  57.             }  
  58.         }  
  59.   
  60.         if (itemsWidth > 0) {  
  61.             createLayouts(itemsWidth, labelWidth);  
  62.         }  
  63.   
  64.         return width;  
  65.     }  
  66.   
  67.     /** 
  68.      * Creates layouts 
  69.      * @param widthItems width of items layout 
  70.      * @param widthLabel width of label layout 
  71.      */  
  72.     private void createLayouts(int widthItems, int widthLabel) {  
  73.         if (itemsLayout == null || itemsLayout.getWidth() > widthItems) {  
  74.             itemsLayout = new StaticLayout(buildText(isScrollingPerformed), itemsPaint, widthItems,  
  75.                     widthLabel > 0 ? Layout.Alignment.ALIGN_OPPOSITE : Layout.Alignment.ALIGN_CENTER,  
  76.                     1, ADDITIONAL_ITEM_HEIGHT, false);  
  77.         } else {  
  78.             itemsLayout.increaseWidthTo(widthItems);  
  79.         }  
  80.   
  81.         if (!isScrollingPerformed && (valueLayout == null || valueLayout.getWidth() > widthItems)) {  
  82.             String text = getAdapter() != null ? getAdapter().getItem(currentItem) : null;  
  83.             valueLayout = new StaticLayout(text != null ? text : "",  
  84.                     valuePaint, widthItems, widthLabel > 0 ?  
  85.                             Layout.Alignment.ALIGN_OPPOSITE : Layout.Alignment.ALIGN_CENTER,  
  86.                             1, ADDITIONAL_ITEM_HEIGHT, false);  
  87.         } else if (isScrollingPerformed) {  
  88.             valueLayout = null;  
  89.         } else {  
  90.             valueLayout.increaseWidthTo(widthItems);  
  91.         }  
  92.   
  93.         if (widthLabel > 0) {  
  94.             if (labelLayout == null || labelLayout.getWidth() > widthLabel) {  
  95.                 labelLayout = new StaticLayout(label, valuePaint,  
  96.                         widthLabel, Layout.Alignment.ALIGN_NORMAL, 1,  
  97.                         ADDITIONAL_ITEM_HEIGHT, false);  
  98.             } else {  
  99.                 labelLayout.increaseWidthTo(widthLabel);  
  100.             }  
  101.         }  
  102.     }  
  103.   
  104.     @Override  
  105.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  106.         int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
  107.         int heightMode = MeasureSpec.getMode(heightMeasureSpec);  
  108.         int widthSize = MeasureSpec.getSize(widthMeasureSpec);  
  109.         int heightSize = MeasureSpec.getSize(heightMeasureSpec);  
  110.   
  111.         int width = calculateLayoutWidth(widthSize, widthMode);  
  112.   
  113.         int height;  
  114.         if (heightMode == MeasureSpec.EXACTLY) {  
  115.             height = heightSize;  
  116.         } else {  
  117.             height = getDesiredHeight(itemsLayout);  
  118.   
  119.             if (heightMode == MeasureSpec.AT_MOST) {  
  120.                 height = Math.min(height, heightSize);  
  121.             }  
  122.         }  
  123.   
  124.         setMeasuredDimension(width, height);  
  125.     }  
  126.   
  127.     @Override  
  128.     protected void onDraw(Canvas canvas) {  
  129.         super.onDraw(canvas);  
  130.           
  131.         if (itemsLayout == null) {  
  132.             if (itemsWidth == 0) {  
  133.                 calculateLayoutWidth(getWidth(), MeasureSpec.EXACTLY);  
  134.             } else {  
  135.                 createLayouts(itemsWidth, labelWidth);  
  136.             }  
  137.         }  
  138.   
  139.         if (itemsWidth > 0) {  
  140.             canvas.save();  
  141.             // Skip padding space and hide a part of top and bottom items  
  142.             canvas.translate(PADDING, -ITEM_OFFSET);  
  143.             drawItems(canvas);  
  144.             drawValue(canvas);  
  145.             canvas.restore();  
  146.         }  
  147.   
  148.         drawCenterRect(canvas);  
  149.         drawShadows(canvas);  
  150.     }  
  151.   
  152.     /** 
  153.      * Draws shadows on top and bottom of control 
  154.      * @param canvas the canvas for drawing 
  155.      */  
  156.     private void drawShadows(Canvas canvas) {  
  157.         topShadow.setBounds(00, getWidth(), getHeight() / visibleItems);  
  158.         topShadow.draw(canvas);  
  159.   
  160.         bottomShadow.setBounds(0, getHeight() - getHeight() / visibleItems,  
  161.                 getWidth(), getHeight());  
  162.         bottomShadow.draw(canvas);  
  163.     }  
  164.   
  165.     /** 
  166.      * Draws value and label layout 
  167.      * @param canvas the canvas for drawing 
  168.      */  
  169.     private void drawValue(Canvas canvas) {  
  170.         valuePaint.setColor(VALUE_TEXT_COLOR);  
  171.         valuePaint.drawableState = getDrawableState();  
  172.   
  173.         Rect bounds = new Rect();  
  174.         itemsLayout.getLineBounds(visibleItems / 2, bounds);  
  175.   
  176.         // draw label  
  177.         if (labelLayout != null) {  
  178.             canvas.save();  
  179.             canvas.translate(itemsLayout.getWidth() + LABEL_OFFSET, bounds.top);  
  180.             labelLayout.draw(canvas);  
  181.             canvas.restore();  
  182.         }  
  183.   
  184.         // draw current value  
  185.         if (valueLayout != null) {  
  186.             canvas.save();  
  187.             canvas.translate(0, bounds.top + scrollingOffset);  
  188.             valueLayout.draw(canvas);  
  189.             canvas.restore();  
  190.         }  
  191.     }  
  192.   
  193.     /** 
  194.      * Draws items 
  195.      * @param canvas the canvas for drawing 
  196.      */  
  197.     private void drawItems(Canvas canvas) {  
  198.         canvas.save();  
  199.           
  200.         int top = itemsLayout.getLineTop(1);  
  201.         canvas.translate(0, - top + scrollingOffset);  
  202.           
  203.         itemsPaint.setColor(ITEMS_TEXT_COLOR);  
  204.         itemsPaint.drawableState = getDrawableState();  
  205.         itemsLayout.draw(canvas);  
  206.           
  207.         canvas.restore();  
  208.     }  
  209.   
  210.     /** 
  211.      * Draws rect for current value 
  212.      * @param canvas the canvas for drawing 
  213.      */  
  214.     private void drawCenterRect(Canvas canvas) {  
  215.         int center = getHeight() / 2;  
  216.         int offset = getItemHeight() / 2;  
  217.         centerDrawable.setBounds(0, center - offset, getWidth(), center + offset);  
  218.         centerDrawable.draw(canvas);  
  219.     }  
  220.   
  221.     @Override  
  222.     public boolean onTouchEvent(MotionEvent event) {  
  223.         WheelAdapter adapter = getAdapter();  
  224.         if (adapter == null) {  
  225.             return true;  
  226.         }  
  227.           
  228.             if (!gestureDetector.onTouchEvent(event) && event.getAction() == MotionEvent.ACTION_UP) {  
  229.             justify();  
  230.         }  
  231.         return true;  
  232.     }  
  233.       
  234.     /** 
  235.      * Scrolls the wheel 
  236.      * @param delta the scrolling value 
  237.      */  
  238.     private void doScroll(int delta) {  
  239.         scrollingOffset += delta;  
  240.           
  241.         int count = scrollingOffset / getItemHeight();  
  242.         int pos = currentItem - count;  
  243.         if (isCyclic && adapter.getItemsCount() > 0) {  
  244.             // fix position by rotating  
  245.             while (pos < 0) {  
  246.                 pos += adapter.getItemsCount();  
  247.             }  
  248.             pos %= adapter.getItemsCount();  
  249.         } else if (isScrollingPerformed) {  
  250.             //   
  251.             if (pos < 0) {  
  252.                 count = currentItem;  
  253.                 pos = 0;  
  254.             } else if (pos >= adapter.getItemsCount()) {  
  255.                 count = currentItem - adapter.getItemsCount() + 1;  
  256.                 pos = adapter.getItemsCount() - 1;  
  257.             }  
  258.         } else {  
  259.             // fix position  
  260.             pos = Math.max(pos, 0);  
  261.             pos = Math.min(pos, adapter.getItemsCount() - 1);  
  262.         }  
  263.           
  264.         int offset = scrollingOffset;  
  265.         if (pos != currentItem) {  
  266.             setCurrentItem(pos, false);  
  267.         } else {  
  268.             invalidate();  
  269.         }  
  270.           
  271.         // update offset  
  272.         scrollingOffset = offset - count * getItemHeight();  
  273.         if (scrollingOffset > getHeight()) {  
  274.             scrollingOffset = scrollingOffset % getHeight() + getHeight();  
  275.         }  
  276.     }  
  277.       
  278.     // gesture listener  
  279.     private SimpleOnGestureListener gestureListener = new SimpleOnGestureListener() {  
  280.         public boolean onDown(MotionEvent e) {  
  281.             if (isScrollingPerformed) {  
  282.                 scroller.forceFinished(true);  
  283.                 clearMessages();  
  284.                 return true;  
  285.             }  
  286.             return false;  
  287.         }  
  288.           
  289.         public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {  
  290.             startScrolling();  
  291.             doScroll((int)-distanceY);  
  292.             return true;  
  293.         }  
  294.           
  295.         public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {  
  296.             lastScrollY = currentItem * getItemHeight() + scrollingOffset;  
  297.             int maxY = isCyclic ? 0x7FFFFFFF : adapter.getItemsCount() * getItemHeight();  
  298.             int minY = isCyclic ? -maxY : 0;  
  299.             scroller.fling(0, lastScrollY, 0, (int) -velocityY / 200, minY, maxY);  
  300.             setNextMessage(MESSAGE_SCROLL);  
  301.             return true;  
  302.         }  
  303.     };  
  304.   
  305.     // Messages  
  306.     private final int MESSAGE_SCROLL = 0;  
  307.     private final int MESSAGE_JUSTIFY = 1;  
  308.       
  309.     /** 
  310.      * Set next message to queue. Clears queue before. 
  311.      *  
  312.      * @param message the message to set 
  313.      */  
  314.     private void setNextMessage(int message) {  
  315.         clearMessages();  
  316.         animationHandler.sendEmptyMessage(message);  
  317.     }  
  318.   
  319.     /** 
  320.      * Clears messages from queue 
  321.      */  
  322.     private void clearMessages() {  
  323.         animationHandler.removeMessages(MESSAGE_SCROLL);  
  324.         animationHandler.removeMessages(MESSAGE_JUSTIFY);  
  325.     }  
  326.       
  327.     // animation handler  
  328.     private Handler animationHandler = new Handler() {  
  329.         public void handleMessage(Message msg) {  
  330.             scroller.computeScrollOffset();  
  331.             int currY = scroller.getCurrY();  
  332.             int delta = lastScrollY - currY;  
  333.             lastScrollY = currY;  
  334.             if (delta != 0) {  
  335.                 doScroll(delta);  
  336.             }  
  337.               
  338.             // scrolling is not finished when it comes to final Y  
  339.             // so, finish it manually   
  340.             if (Math.abs(currY - scroller.getFinalY()) < MIN_DELTA_FOR_SCROLLING) {  
  341.                 currY = scroller.getFinalY();  
  342.                 scroller.forceFinished(true);  
  343.             }  
  344.             if (!scroller.isFinished()) {  
  345.                 animationHandler.sendEmptyMessage(msg.what);  
  346.             } else if (msg.what == MESSAGE_SCROLL) {  
  347.                 justify();  
  348.             } else {  
  349.                 finishScrolling();  
  350.             }  
  351.         }  
  352.     };  
  353.       
  354.     /** 
  355.      * Justifies wheel 
  356.      */  
  357.     private void justify() {  
  358.         if (adapter == null) {  
  359.             return;  
  360.         }  
  361.           
  362.         lastScrollY = 0;  
  363.         int offset = scrollingOffset;  
  364.         int itemHeight = getItemHeight();  
  365.         boolean needToIncrease = offset > 0 ? currentItem < adapter.getItemsCount() : currentItem > 0;   
  366.         if ((isCyclic || needToIncrease) && Math.abs((float) offset) > (float) itemHeight / 2) {  
  367.             if (offset < 0)  
  368.                 offset += itemHeight + MIN_DELTA_FOR_SCROLLING;  
  369.             else  
  370.                 offset -= itemHeight + MIN_DELTA_FOR_SCROLLING;  
  371.         }  
  372.         if (Math.abs(offset) > MIN_DELTA_FOR_SCROLLING) {  
  373.             scroller.startScroll(000, offset, SCROLLING_DURATION);  
  374.             setNextMessage(MESSAGE_JUSTIFY);  
  375.         } else {  
  376.             finishScrolling();  
  377.         }  
  378.     }  
  379.       
  380.     /** 
  381.      * Starts scrolling 
  382.      */  
  383.     private void startScrolling() {  
  384.         if (!isScrollingPerformed) {  
  385.             isScrollingPerformed = true;  
  386.             notifyScrollingListenersAboutStart();  
  387.         }  
  388.     }  
  389.   
  390.     /** 
  391.      * Finishes scrolling 
  392.      */  
  393.     void finishScrolling() {  
  394.         if (isScrollingPerformed) {  
  395.             notifyScrollingListenersAboutEnd();  
  396.             isScrollingPerformed = false;  
  397.         }  
  398.         invalidateLayouts();  
  399.         invalidate();  
  400.     }  
  401.           
  402.     /** 
  403.      * Scroll the wheel 
  404.      * @param itemsToSkip items to scroll 
  405.      * @param time scrolling duration 
  406.      */  
  407.     public void scroll(int itemsToScroll, int time) {  
  408.         scroller.forceFinished(true);  
  409.         lastScrollY = scrollingOffset;  
  410.         int offset = itemsToScroll * getItemHeight();         
  411.         scroller.startScroll(0, lastScrollY, 0, offset - lastScrollY, time);  
  412.         setNextMessage(MESSAGE_SCROLL);       
  413.         startScrolling();  
  414.     }  

        在629行到744行的代碼是繪製圖形,747行onTouchEvent()裏面主要是調用了882行的justify()方法,用於調整畫面,

[java] view plaincopy
  1. @Override  
  2.     public boolean onTouchEvent(MotionEvent event) {  
  3.         WheelAdapter adapter = getAdapter();  
  4.         if (adapter == null) {  
  5.             return true;  
  6.         }  
  7.           
  8.             if (!gestureDetector.onTouchEvent(event) && event.getAction() == MotionEvent.ACTION_UP) {  
  9.             justify();  
  10.         }  
  11.         return true;  
  12.     }  


[java] view plaincopy
  1. /** 
  2.      * Justifies wheel 
  3.      */  
  4.     private void justify() {  
  5.         if (adapter == null) {  
  6.             return;  
  7.         }  
  8.           
  9.         lastScrollY = 0;  
  10.         int offset = scrollingOffset;  
  11.         int itemHeight = getItemHeight();  
  12.         boolean needToIncrease = offset > 0 ? currentItem < adapter.getItemsCount() : currentItem > 0;   
  13.         if ((isCyclic || needToIncrease) && Math.abs((float) offset) > (float) itemHeight / 2) {  
  14.             if (offset < 0)  
  15.                 offset += itemHeight + MIN_DELTA_FOR_SCROLLING;  
  16.             else  
  17.                 offset -= itemHeight + MIN_DELTA_FOR_SCROLLING;  
  18.         }  
  19.         if (Math.abs(offset) > MIN_DELTA_FOR_SCROLLING) {  
  20.             scroller.startScroll(000, offset, SCROLLING_DURATION);  
  21.             setNextMessage(MESSAGE_JUSTIFY);  
  22.         } else {  
  23.             finishScrolling();  
  24.         }  
  25.     }  

        我們看下重寫的系統回調函數onMeasure()(用於測量各個控件距離,父子控件空間大小等):

[java] view plaincopy
  1. @Override  
  2.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  3.         int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
  4.         int heightMode = MeasureSpec.getMode(heightMeasureSpec);  
  5.         int widthSize = MeasureSpec.getSize(widthMeasureSpec);  
  6.         int heightSize = MeasureSpec.getSize(heightMeasureSpec);  
  7.   
  8.         int width = calculateLayoutWidth(widthSize, widthMode);  
  9.   
  10.         int height;  
  11.         if (heightMode == MeasureSpec.EXACTLY) {  
  12.             height = heightSize;  
  13.         } else {  
  14.             height = getDesiredHeight(itemsLayout);  
  15.   
  16.             if (heightMode == MeasureSpec.AT_MOST) {  
  17.                 height = Math.min(height, heightSize);  
  18.             }  
  19.         }  
  20.   
  21.         setMeasuredDimension(width, height);  
  22.     }  

裏面用到了532行calculateLayoutWidth()的方法,就是計算Layout的寬度,在calculateLayoutWidth()這個方法裏面調用了

[java] view plaincopy
  1. /** 
  2.      * Creates layouts 
  3.      * @param widthItems width of items layout 
  4.      * @param widthLabel width of label layout 
  5.      */  
  6.     private void createLayouts(int widthItems, int widthLabel) {  
  7.         if (itemsLayout == null || itemsLayout.getWidth() > widthItems) {  
  8.             itemsLayout = new StaticLayout(buildText(isScrollingPerformed), itemsPaint, widthItems,  
  9.                     widthLabel > 0 ? Layout.Alignment.ALIGN_OPPOSITE : Layout.Alignment.ALIGN_CENTER,  
  10.                     1, ADDITIONAL_ITEM_HEIGHT, false);  
  11.         } else {  
  12.             itemsLayout.increaseWidthTo(widthItems);  
  13.         }  
  14.   
  15.         if (!isScrollingPerformed && (valueLayout == null || valueLayout.getWidth() > widthItems)) {  
  16.             String text = getAdapter() != null ? getAdapter().getItem(currentItem) : null;  
  17.             valueLayout = new StaticLayout(text != null ? text : "",  
  18.                     valuePaint, widthItems, widthLabel > 0 ?  
  19.                             Layout.Alignment.ALIGN_OPPOSITE : Layout.Alignment.ALIGN_CENTER,  
  20.                             1, ADDITIONAL_ITEM_HEIGHT, false);  
  21.         } else if (isScrollingPerformed) {  
  22.             valueLayout = null;  
  23.         } else {  
  24.             valueLayout.increaseWidthTo(widthItems);  
  25.         }  
  26.   
  27.         if (widthLabel > 0) {  
  28.             if (labelLayout == null || labelLayout.getWidth() > widthLabel) {  
  29.                 labelLayout = new StaticLayout(label, valuePaint,  
  30.                         widthLabel, Layout.Alignment.ALIGN_NORMAL, 1,  
  31.                         ADDITIONAL_ITEM_HEIGHT, false);  
  32.             } else {  
  33.                 labelLayout.increaseWidthTo(widthLabel);  
  34.             }  
  35.         }  
  36.     }  

然後我們接着看onDraw()方法:

[java] view plaincopy
  1. @Override  
  2.     protected void onDraw(Canvas canvas) {  
  3.         super.onDraw(canvas);  
  4.           
  5.         if (itemsLayout == null) {  
  6.             if (itemsWidth == 0) {  
  7.                 calculateLayoutWidth(getWidth(), MeasureSpec.EXACTLY);  
  8.             } else {  
  9.                 createLayouts(itemsWidth, labelWidth);  
  10.             }  
  11.         }  
  12.   
  13.         if (itemsWidth > 0) {  
  14.             canvas.save();  
  15.             // Skip padding space and hide a part of top and bottom items  
  16.             canvas.translate(PADDING, -ITEM_OFFSET);  
  17.             drawItems(canvas);  
  18.             drawValue(canvas);  
  19.             canvas.restore();  
  20.         }  
  21.   
  22.         drawCenterRect(canvas);  
  23.         drawShadows(canvas);  
  24.     }  

在onDraw方法中,也調用了CreateLayout()方法,然後在後面調用drawCenterRect()、drawItems()、drawValue()、繪製陰影drawShadows()兩個方法:

[java] view plaincopy
  1. /** 
  2.      * Draws shadows on top and bottom of control 
  3.      * @param canvas the canvas for drawing 
  4.      */  
  5.     private void drawShadows(Canvas canvas) {  
  6.         topShadow.setBounds(00, getWidth(), getHeight() / visibleItems);  
  7.         topShadow.draw(canvas);  
  8.   
  9.   
  10.         bottomShadow.setBounds(0, getHeight() - getHeight() / visibleItems,  
  11.                 getWidth(), getHeight());  
  12.         bottomShadow.draw(canvas);  
  13.     }  
  14.   
  15.   
  16.     /** 
  17.      * Draws value and label layout 
  18.      * @param canvas the canvas for drawing 
  19.      */  
  20.     private void drawValue(Canvas canvas) {  
  21.         valuePaint.setColor(VALUE_TEXT_COLOR);  
  22.         valuePaint.drawableState = getDrawableState();  
  23.   
  24.   
  25.         Rect bounds = new Rect();  
  26.         itemsLayout.getLineBounds(visibleItems / 2, bounds);  
  27.   
  28.   
  29.         // draw label  
  30.         if (labelLayout != null) {  
  31.             canvas.save();  
  32.             canvas.translate(itemsLayout.getWidth() + LABEL_OFFSET, bounds.top);  
  33.             labelLayout.draw(canvas);  
  34.             canvas.restore();  
  35.         }  
  36.   
  37.   
  38.         // draw current value  
  39.         if (valueLayout != null) {  
  40.             canvas.save();  
  41.             canvas.translate(0, bounds.top + scrollingOffset);  
  42.             valueLayout.draw(canvas);  
  43.             canvas.restore();  
  44.         }  
  45.     }  
  46.   
  47.   
  48.     /** 
  49.      * Draws items 
  50.      * @param canvas the canvas for drawing 
  51.      */  
  52.     private void drawItems(Canvas canvas) {  
  53.         canvas.save();  
  54.           
  55.         int top = itemsLayout.getLineTop(1);  
  56.         canvas.translate(0, - top + scrollingOffset);  
  57.           
  58.         itemsPaint.setColor(ITEMS_TEXT_COLOR);  
  59.         itemsPaint.drawableState = getDrawableState();  
  60.         itemsLayout.draw(canvas);  
  61.           
  62.         canvas.restore();  
  63.     }  
  64.   
  65.   
  66.     /** 
  67.      * Draws rect for current value 
  68.      * @param canvas the canvas for drawing 
  69.      */  
  70.     private void drawCenterRect(Canvas canvas) {  
  71.         int center = getHeight() / 2;  
  72.         int offset = getItemHeight() / 2;  
  73.         centerDrawable.setBounds(0, center - offset, getWidth(), center + offset);  
  74.         centerDrawable.draw(canvas);  
  75.     }  


主要就是通過canvas類進行圖形的繪製。


       最後,我們看下840行定義的手勢監聽:

[java] view plaincopy
  1. // gesture listener  
  2.     private SimpleOnGestureListener gestureListener = new SimpleOnGestureListener() {  
  3.         public boolean onDown(MotionEvent e) {  
  4.             if (isScrollingPerformed) {  
  5.                 scroller.forceFinished(true);  
  6.                 clearMessages();  
  7.                 return true;  
  8.             }  
  9.             return false;  
  10.         }  
  11.           
  12.         public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {  
  13.             startScrolling();  
  14.             doScroll((int)-distanceY);  
  15.             return true;  
  16.         }  
  17.           
  18.         public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {  
  19.             lastScrollY = currentItem * getItemHeight() + scrollingOffset;  
  20.             int maxY = isCyclic ? 0x7FFFFFFF : adapter.getItemsCount() * getItemHeight();  
  21.             int minY = isCyclic ? -maxY : 0;  
  22.             scroller.fling(0, lastScrollY, 0, (int) -velocityY / 200, minY, maxY);  
  23.             setNextMessage(MESSAGE_SCROLL);  
  24.             return true;  
  25.         }  
  26.     };  

裏面主要調用的方法:clearMessages()、startScrolling()、doScroll()、setNextMessage(),先看下中間的兩個方法開始滑動和滑動

[java] view plaincopy
  1. /** 
  2.      * Scrolls the wheel 
  3.      * @param delta the scrolling value 
  4.      */  
  5.     private void doScroll(int delta) {  
  6.         scrollingOffset += delta;         
  7.         int count = scrollingOffset / getItemHeight();  
  8.         int pos = currentItem - count;  
  9.         if (isCyclic && adapter.getItemsCount() > 0) {  
  10.             // fix position by rotating  
  11.             while (pos < 0) {  
  12.                 pos += adapter.getItemsCount();  
  13.             }  
  14.             pos %= adapter.getItemsCount();  
  15.         } else if (isScrollingPerformed) {  
  16.             //   
  17.             if (pos < 0) {  
  18.                 count = currentItem;  
  19.                 pos = 0;  
  20.             } else if (pos >= adapter.getItemsCount()) {  
  21.                 count = currentItem - adapter.getItemsCount() + 1;  
  22.                 pos = adapter.getItemsCount() - 1;  
  23.             }  
  24.         } else {  
  25.             // fix position  
  26.             pos = Math.max(pos, 0);  
  27.             pos = Math.min(pos, adapter.getItemsCount() - 1);  
  28.         }  
  29.           
  30.         int offset = scrollingOffset;  
  31.         if (pos != currentItem) {  
  32.             setCurrentItem(pos, false);  
  33.         } else {  
  34.             invalidate();  
  35.         }  
  36.           
  37.         // update offset  
  38.         scrollingOffset = offset - count * getItemHeight();  
  39.         if (scrollingOffset > getHeight()) {  
  40.             scrollingOffset = scrollingOffset % getHeight() + getHeight();  
  41.         }  
  42.     }  

[java] view plaincopy
  1. /** 
  2.      * Starts scrolling 
  3.      */  
  4.     private void startScrolling() {  
  5.         if (!isScrollingPerformed) {  
  6.             isScrollingPerformed = true;  
  7.             notifyScrollingListenersAboutStart();  
  8.         }  
  9.     }  

在startScrolling方法裏面有287行的notifyScrollingListenersAboutStart函數。

再看clearMessages()、setMessageNext()

[java] view plaincopy
  1. private void setNextMessage(int message) {  
  2.         clearMessages();  
  3.         animationHandler.sendEmptyMessage(message);  
  4.     }  
  5.   
  6.   
  7.     /** 
  8.      * Clears messages from queue 
  9.      */  
  10.     private void clearMessages() {  
  11.         animationHandler.removeMessages(MESSAGE_SCROLL);  
  12.         animationHandler.removeMessages(MESSAGE_JUSTIFY);  
  13.     }  

裏面使用到了animationHandler,用來傳遞動畫有段的操作:

[java] view plaincopy
  1. // animation handler  
  2.     private Handler animationHandler = new Handler() {  
  3.         public void handleMessage(Message msg) {  
  4.             scroller.computeScrollOffset();  
  5.             int currY = scroller.getCurrY();  
  6.             int delta = lastScrollY - currY;  
  7.             lastScrollY = currY;  
  8.             if (delta != 0) {  
  9.                 doScroll(delta);  
  10.             }  
  11.               
  12.             // scrolling is not finished when it comes to final Y  
  13.             // so, finish it manually   
  14.             if (Math.abs(currY - scroller.getFinalY()) < MIN_DELTA_FOR_SCROLLING) {  
  15.                 currY = scroller.getFinalY();  
  16.                 scroller.forceFinished(true);  
  17.             }  
  18.             if (!scroller.isFinished()) {  
  19.                 animationHandler.sendEmptyMessage(msg.what);  
  20.             } else if (msg.what == MESSAGE_SCROLL) {  
  21.                 justify();  
  22.             } else {  
  23.                 finishScrolling();  
  24.             }  
  25.         }  
  26.     };  

裏面調用了finishScrolling()

[java] view plaincopy
  1. /** 
  2.      * Finishes scrolling 
  3.      */  
  4.     void finishScrolling() {  
  5.         if (isScrollingPerformed) {  
  6.             notifyScrollingListenersAboutEnd();  
  7.             isScrollingPerformed = false;  
  8.         }  
  9.         invalidateLayouts();  
  10.         invalidate();  
  11.     }  

                                                                                            



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章