我們都知道,在iOS里面有一種控件------滾筒控件(Wheel View),這通常用於設置時間/日期,非常方便,但Android SDK並沒有提供類似的控件。這里介紹一下如何Android實現WheelView。
先來看一看iOS中的WheelView的效果圖:
這個效果不錯吧,我們應該如何實現呢?
那在Android如果也要實現這樣一個效果,應該怎么做呢?
1.Android WheelView效果圖

2.網上的開源代碼
我們從網上找到了一個開源的代碼,它也實現了這樣的效果,而且效果也不錯,大家可以用SVN來checkout:
http://android-wheel.googlecode.com/svn/trunk
它這個Demo最本質是自己寫布局,好像是利用一個LinearLayout來布局child,然后調用LinearLayout.draw(canvas)方法,把child繪制在指定的canvas上面。它同時還提供了類似AdapterView的訪問方式,用戶可以設置Adapter來提供數據。我在這里主要不是講解這個Demo的結構,如果大家感興趣,可以自己下載代碼研究。
3.實現思路
由於我之前修改過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
- package com.nj1s.lib.widget;
- import android.content.Context;
- import android.graphics.Canvas;
- import android.graphics.Rect;
- import android.graphics.drawable.Drawable;
- import android.graphics.drawable.GradientDrawable;
- import android.graphics.drawable.GradientDrawable.Orientation;
- import android.util.AttributeSet;
- import android.view.Gravity;
- import android.view.View;
- import com.nj1s.lib.R;
- public class WheelView extends TosGallery
- {
- private Drawable mSelectorDrawable = null;
- private Rect mSelectorBound = new Rect();
- private GradientDrawable mTopShadow = null;
- private GradientDrawable mBottomShadow = null;
- private static final int[] SHADOWS_COLORS =
- {
- 0xFF111111,
- 0x00AAAAAA,
- 0x00AAAAAA
- };
- public WheelView(Context context)
- {
- super(context);
- initialize(context);
- }
- public WheelView(Context context, AttributeSet attrs)
- {
- super(context, attrs);
- initialize(context);
- }
- public WheelView(Context context, AttributeSet attrs, int defStyle)
- {
- super(context, attrs, defStyle);
- initialize(context);
- }
- private void initialize(Context context)
- {
- this.setVerticalScrollBarEnabled(false);
- this.setSlotInCenter(true);
- this.setOrientation(TosGallery.VERTICAL);
- this.setGravity(Gravity.CENTER_HORIZONTAL);
- this.setUnselectedAlpha(1.0f);
- // This lead the onDraw() will be called.
- this.setWillNotDraw(false);
- // The selector rectangle drawable.
- this.mSelectorDrawable =
- getContext().getResources().getDrawable(R.drawable.wheel_val);
- this.mTopShadow =
- new GradientDrawable(Orientation.TOP_BOTTOM, SHADOWS_COLORS);
- this.mBottomShadow =
- new GradientDrawable(Orientation.BOTTOM_TOP, SHADOWS_COLORS);
- // The default background.
- this.setBackgroundResource(R.drawable.wheel_bg);
- }
- @Override
- protected void dispatchDraw(Canvas canvas)
- {
- super.dispatchDraw(canvas);
- // After draw child, we do the following things:
- // +1, Draw the center rectangle.
- // +2, Draw the shadows on the top and bottom.
- drawCenterRect(canvas);
- drawShadows(canvas);
- }
- /**
- * setOrientation
- */
- @Override
- public void setOrientation(int orientation)
- {
- if (TosGallery.HORIZONTAL == orientation)
- {
- throw new IllegalArgumentException("The orientation must be VERTICAL");
- }
- super.setOrientation(orientation);
- }
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b)
- {
- super.onLayout(changed, l, t, r, b);
- int galleryCenter = getCenterOfGallery();
- View v = this.getChildAt(0);
- int height = (null != v) ? v.getMeasuredHeight() : 50;
- int top = galleryCenter - height / 2;
- int bottom = top + height;
- mSelectorBound.set(
- getPaddingLeft(),
- top,
- getWidth() - getPaddingRight(),
- bottom);
- }
- private void drawCenterRect(Canvas canvas)
- {
- if (null != mSelectorDrawable)
- {
- mSelectorDrawable.setBounds(mSelectorBound);
- mSelectorDrawable.draw(canvas);
- }
- }
- private void drawShadows(Canvas canvas)
- {
- int height = (int)(2.0 * mSelectorBound.height());
- mTopShadow.setBounds(0, 0, getWidth(), height);
- mTopShadow.draw(canvas);
- mBottomShadow.setBounds(0, getHeight() - height, getWidth(), getHeight());
- mBottomShadow.draw(canvas);
- }
- }
上面代碼沒有什么特別的東西,只是有幾點需要注意:
5. 如何使用
- // 設置listener
- mDateWheel.setOnEndFlingListener(mListener);
- // 設置滑動時的聲音
- mDateWheel.setSoundEffectsEnabled(true);
- // 設置adapter
- mDateWheel.setAdapter(new WheelTextAdapter(this));
- // Adapter的實現
- protected class WheelTextAdapter extends BaseAdapter
- {
- ArrayList<TextInfo> mData = null;
- int mWidth = ViewGroup.LayoutParams.MATCH_PARENT;
- int mHeight = 50;
- Context mContext = null;
- public WheelTextAdapter(Context context)
- {
- mContext = context;
- }
- public void setData(ArrayList<TextInfo> data)
- {
- mData = data;
- this.notifyDataSetChanged();
- }
- public void setItemSize(int width, int height)
- {
- mWidth = width;
- mHeight = height;
- }
- @Override
- public int getCount()
- {
- return (null != mData) ? mData.size() : 0;
- }
- @Override
- public Object getItem(int position)
- {
- return null;
- }
- @Override
- public long getItemId(int position)
- {
- return 0;
- }
- @Override
- public View getView(int position, View convertView, ViewGroup parent)
- {
- TextView textView = null;
- if (null == convertView)
- {
- convertView = new TextView(mContext);
- convertView.setLayoutParams(new TosGallery.LayoutParams(mWidth, mHeight));
- textView = (TextView)convertView;
- textView.setGravity(Gravity.CENTER);
- textView.setTextSize(26);
- textView.setTextColor(Color.BLACK);
- }
- if (null == textView)
- {
- textView = (TextView)convertView;
- }
- TextInfo info = mData.get(position);
- textView.setText(info.mText);
- textView.setTextColor(info.mColor);
- return convertView;
- }
- }