Android 實現 WheelView

我們都知道,在iOS裏面有一種控件------滾筒控件(Wheel View),這通常用於設置時間/日期,非常方便,但Android SDK並沒有提供類似的控件。這裏介紹一下如何Android實現WheelView。

先來看一看iOS中的WheelView的效果圖:


這個效果不錯吧,我們應該如何實現呢?

那在Android如果也要實現這樣一個效果,應該怎麼做呢?

1.Android WheelView效果圖


上圖是我實現的DEMO的運行效果圖。


2.網上的開源代碼

我們從網上找到了一個開源的代碼,它也實現了這樣的效果,而且效果也不錯,大家可以用SVN來checkout:

http://android-wheel.googlecode.com/svn/trunk

它這個Demo最本質是自己寫佈局,好像是利用一個LinearLayout來佈局child,然後調用LinearLayout.draw(canvas)方法,把child繪製在指定的canvas上面。它同時還提供了類似AdapterView的訪問方式,用戶可以設置Adapter來提供數據。我在這裏主要不是講解這個Demo的結構,如果大家感興趣,可以自己下載代碼研究。


3.實現思路

由於網上的Demo也是提供了類似於AdapterView的訪問方式,所以,我在想,我們能不能換一種方式來實現,試想,如果這個滾筒是橫着的,那麼我們就可以利用Gallery來實現,Gallery的特點跟WheelView有相似之處,比如:選中的項始終在View中間,只不過它是橫着佈局的。

由於我之前修改過Gallery的源代碼,可以使其循環滾動,並且第一個child可以排列在最左端,所以,我在想,如果我能把Gallery修改成豎的(垂直排列),那這個不就是OK了嗎?基於這樣的想法,我就準備修改代碼了。

我們這裏需要把Gallery的源碼複製到我們的工程中,然後修改,保證能編譯通過。

與Gallery相關的的幾個文件如下所示,它們都是放在widget文件夾和res/value文件夾下面。

  • AbsSpinner.java
  • AdapterView.java
  • Gallery.java
  • attr.xml

修改的過程比較麻煩,我這裏不詳細說明(要細說的話,內容太多了),在修改之後,我們的Gallery提供了一個方法:setOrientation(int),你可以讓這個Gallery水平滑動,也可以垂直滑動。

我們還應該提供以下幾個核心方法:

  • setOnEndFlingListener ------ 當Gallery停止滑動時的回調用,這樣調用者可以在停止滑動時來得到當前選中的項。
  • setOrientation(int) ------ 支持佈局方向:HORIZONTAL和VERTICAL。
  • setScrollCycle(boolean) ------ 是否支持循環滑動。
  • setSlotInCenter(boolean) ------ 是否讓Gallery選中的項居中。

4. 擴展Gallery

在修改完Gallery後,我們就可以來使用它了,還得做一些事情,就是先要擴展Gallery,實現一個WheelView,在這個類裏面,我們要去繪製中間選擇的矩形、背景圖片、上下陰影等。
這個WheelView擴展了Gallery,同時還應該提供設置背景圖片,選擇矩形的圖片和上下陰影的圖片等功能。
WheelView的完整實現代碼如下:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.nj1s.lib.widget;  
  2.   
  3. import android.content.Context;  
  4. import android.graphics.Canvas;  
  5. import android.graphics.Rect;  
  6. import android.graphics.drawable.Drawable;  
  7. import android.graphics.drawable.GradientDrawable;  
  8. import android.graphics.drawable.GradientDrawable.Orientation;  
  9. import android.util.AttributeSet;  
  10. import android.view.Gravity;  
  11. import android.view.View;  
  12.   
  13. import com.nj1s.lib.R;  
  14.   
  15. public class WheelView extends TosGallery  
  16. {  
  17.     private Drawable mSelectorDrawable      = null;  
  18.     private Rect mSelectorBound             = new Rect();  
  19.     private GradientDrawable mTopShadow     = null;  
  20.     private GradientDrawable mBottomShadow  = null;  
  21.     private static final int[] SHADOWS_COLORS =  
  22.     {  
  23.         0xFF111111,  
  24.         0x00AAAAAA,  
  25.         0x00AAAAAA  
  26.     };  
  27.   
  28.     public WheelView(Context context)  
  29.     {  
  30.         super(context);  
  31.   
  32.         initialize(context);  
  33.     }  
  34.   
  35.     public WheelView(Context context, AttributeSet attrs)  
  36.     {  
  37.         super(context, attrs);  
  38.   
  39.         initialize(context);  
  40.     }  
  41.   
  42.     public WheelView(Context context, AttributeSet attrs, int defStyle)  
  43.     {  
  44.         super(context, attrs, defStyle);  
  45.   
  46.         initialize(context);  
  47.     }  
  48.   
  49.     private void initialize(Context context)  
  50.     {  
  51.         this.setVerticalScrollBarEnabled(false);  
  52.         this.setSlotInCenter(true);  
  53.         this.setOrientation(TosGallery.VERTICAL);  
  54.         this.setGravity(Gravity.CENTER_HORIZONTAL);  
  55.         this.setUnselectedAlpha(1.0f);  
  56.   
  57.         // This lead the onDraw() will be called.  
  58.         this.setWillNotDraw(false);  
  59.   
  60.         // The selector rectangle drawable.  
  61.         this.mSelectorDrawable =   
  62.             getContext().getResources().getDrawable(R.drawable.wheel_val);  
  63.         this.mTopShadow    =   
  64.             new GradientDrawable(Orientation.TOP_BOTTOM, SHADOWS_COLORS);  
  65.         this.mBottomShadow =   
  66.             new GradientDrawable(Orientation.BOTTOM_TOP, SHADOWS_COLORS);  
  67.   
  68.         // The default background.  
  69.         this.setBackgroundResource(R.drawable.wheel_bg);  
  70.     }  
  71.   
  72.     @Override  
  73.     protected void dispatchDraw(Canvas canvas)  
  74.     {  
  75.         super.dispatchDraw(canvas);  
  76.   
  77.         // After draw child, we do the following things:  
  78.         // +1, Draw the center rectangle.  
  79.         // +2, Draw the shadows on the top and bottom.  
  80.   
  81.         drawCenterRect(canvas);  
  82.   
  83.         drawShadows(canvas);  
  84.     }  
  85.   
  86.     /** 
  87.      * setOrientation 
  88.      */  
  89.     @Override  
  90.     public void setOrientation(int orientation)  
  91.     {  
  92.         if (TosGallery.HORIZONTAL == orientation)  
  93.         {  
  94.             throw new IllegalArgumentException("The orientation must be VERTICAL");  
  95.         }  
  96.   
  97.         super.setOrientation(orientation);  
  98.     }  
  99.   
  100.     @Override  
  101.     protected void onLayout(boolean changed, int l, int t, int r, int b)  
  102.     {  
  103.         super.onLayout(changed, l, t, r, b);  
  104.   
  105.         int galleryCenter = getCenterOfGallery();  
  106.         View v = this.getChildAt(0);  
  107.   
  108.         int height = (null != v) ? v.getMeasuredHeight() : 50;  
  109.         int top = galleryCenter - height / 2;  
  110.         int bottom = top + height;  
  111.   
  112.         mSelectorBound.set(  
  113.                 getPaddingLeft(),  
  114.                 top,  
  115.                 getWidth() - getPaddingRight(),  
  116.                 bottom);  
  117.     }  
  118.   
  119.     private void drawCenterRect(Canvas canvas)  
  120.     {  
  121.         if (null != mSelectorDrawable)  
  122.         {  
  123.             mSelectorDrawable.setBounds(mSelectorBound);  
  124.             mSelectorDrawable.draw(canvas);  
  125.         }  
  126.     }  
  127.   
  128.     private void drawShadows(Canvas canvas)  
  129.     {  
  130.         int height = (int)(2.0 * mSelectorBound.height());  
  131.         mTopShadow.setBounds(00, getWidth(), height);  
  132.         mTopShadow.draw(canvas);  
  133.   
  134.         mBottomShadow.setBounds(0, getHeight() - height, getWidth(), getHeight());  
  135.         mBottomShadow.draw(canvas);  
  136.     }  
  137. }  

上面代碼沒有什麼特別的東西,只是有幾點需要注意:

[1] 不要重寫onDraw(),爲什麼呢?因爲onDraw()是繪製自己,如果你在onDraw()中來繪製陰影的話,那麼最後的效果可能是Child在上面,陰影在下面。因此,我們應該是在繪製完Child之後,再繪製陰影,怎麼做呢?請看第二步。

[2] 重寫dispatchDraw(),如果對這個方法不明白的話,請自己看文檔,這裏不解釋,總之,這個方法是用來繪製Child的,因此,重寫這個方法,先調用super.dispatchDraw()方法,然後再繪製陰影,OK,萬事大吉。

[3] 你可以調用#setScrollCycle(boolean)來指定這個WheelView是否可以循環滑動。


5. 如何使用

關於如何使用,其實很簡單,就跟使用GridView/ListView一樣,通過Adapter來提供View。

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. // 設置listener  
  2. mDateWheel.setOnEndFlingListener(mListener);  
  3. // 設置滑動時的聲音  
  4. mDateWheel.setSoundEffectsEnabled(true);  
  5. // 設置adapter  
  6. mDateWheel.setAdapter(new WheelTextAdapter(this));  
  7.   
  8.   
  9. // Adapter的實現  
  10. protected class WheelTextAdapter extends BaseAdapter  
  11. {  
  12.     ArrayList<TextInfo> mData = null;  
  13.     int mWidth  = ViewGroup.LayoutParams.MATCH_PARENT;  
  14.     int mHeight = 50;  
  15.     Context mContext = null;  
  16.       
  17.     public WheelTextAdapter(Context context)  
  18.     {  
  19.         mContext = context;  
  20.     }  
  21.       
  22.     public void setData(ArrayList<TextInfo> data)  
  23.     {  
  24.         mData = data;  
  25.         this.notifyDataSetChanged();  
  26.     }  
  27.       
  28.     public void setItemSize(int width, int height)  
  29.     {  
  30.         mWidth  = width;  
  31.         mHeight = height;  
  32.     }  
  33.       
  34.     @Override  
  35.     public int getCount()  
  36.     {  
  37.         return (null != mData) ? mData.size() : 0;  
  38.     }  
  39.   
  40.   
  41.     @Override  
  42.     public Object getItem(int position)  
  43.     {  
  44.         return null;  
  45.     }  
  46.   
  47.   
  48.     @Override  
  49.     public long getItemId(int position)  
  50.     {  
  51.         return 0;  
  52.     }  
  53.   
  54.   
  55.     @Override  
  56.     public View getView(int position, View convertView, ViewGroup parent)  
  57.     {  
  58.         TextView textView = null;  
  59.           
  60.         if (null == convertView)  
  61.         {  
  62.             convertView = new TextView(mContext);  
  63.             convertView.setLayoutParams(new TosGallery.LayoutParams(mWidth, mHeight));  
  64.             textView = (TextView)convertView;  
  65.             textView.setGravity(Gravity.CENTER);  
  66.             textView.setTextSize(26);  
  67.             textView.setTextColor(Color.BLACK);  
  68.         }  
  69.           
  70.         if (null == textView)  
  71.         {  
  72.             textView = (TextView)convertView;  
  73.         }  
  74.           
  75.         TextInfo info = mData.get(position);  
  76.         textView.setText(info.mText);  
  77.         textView.setTextColor(info.mColor);  
  78.           
  79.         return convertView;  
  80.     }  
  81. }  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章