我们都知道,在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的完整实现代码如下:
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);    }}

上面代码没有什么特别的东西,只是有几点需要注意:
[1] 不要重写onDraw(),为什么呢?因为onDraw()是绘制自己,如果你在onDraw()中来绘制阴影的话,那么最后的效果可能是Child在上面,阴影在下面。因此,我们应该是在绘制完Child之后,再绘制阴影,怎么做呢?请看第二步。
[2] 重写dispatchDraw(),如果对这个方法不明白的话,请自己看文档,这里不解释,总之,这个方法是用来绘制Child的,因此,重写这个方法,先调用super.dispatchDraw()方法,然后再绘制阴影,OK,万事大吉。
[3] 你可以调用#setScrollCycle(boolean)来指定这个WheelView是否可以循环滑动。

5. 如何使用

关于如何使用,其实很简单,就跟使用GridView/ListView一样,通过Adapter来提供View。
// 设置listenermDateWheel.setOnEndFlingListener(mListener);// 设置滑动时的声音mDateWheel.setSoundEffectsEnabled(true);// 设置adaptermDateWheel.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;  }}


更多相关文章

  1. Android(安卓)OpenGL ES 分析与实践
  2. Android(安卓)四大组件
  3. Android中的Service 与 Thread 的区别
  4. android游戏编程的学习笔记(以植物大战僵尸等游戏源代码解析为例)
  5. Android(安卓)4.0 Service Framework
  6. Android(安卓)Jetpack系列——ViewModel源码分析
  7. Android(安卓)资源获取
  8. Android应用程序启动过程——Launcher源码分析
  9. Android系统上实现应用程序的静默安装

随机推荐

  1. (六)Android开发进阶(转)
  2. Android中Bitmap按比例放大
  3. android的 各种权限,有个印象就好
  4. Android(安卓)判断应用是否在运行以及判
  5. Android性能优化一些方法
  6. 看完《第一行代码》再看什么书可以进一步
  7. android — JNI注册方法说明
  8. Android(安卓)Input(一)-相关模块初始化
  9. Android(安卓)UI (include的使用)
  10. Android(安卓)- RecyclerView系列之小白