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

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


        首先,先看下效果圖:


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

     首先,先看下佈局文件:

[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滾輪控件實現及源碼分析(二)





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

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