我们都知道,在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
plain
copy

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

上面代码没有什么特别的东西,只是有几点需要注意:


[1] 不要重写onDraw(),为什么呢?因为onDraw()是绘制自己,如果你在onDraw()中来绘制阴影的话,那么最后的效果可能是Child在上面,阴影在下面。因此,我们应该是在绘制完Child之后,再绘制阴影,怎么做呢?请看第二步。

[2] 重写dispatchDraw(),如果对这个方法不明白的话,请自己看文档,这里不解释,总之,这个方法是用来绘制Child的,因此,重写这个方法,先调用super.dispatchDraw()方法,然后再绘制阴影,OK,万事大吉。

[3] 你可以调用#setScrollCycle(boolean)来指定这个WheelView是否可以循环滑动。


5. 如何使用

关于如何使用,其实很简单,就跟使用GridView/ListView一样,通过Adapter来提供View。

[java] view
plain
copy

  1. // 设置listener
  2. mDateWheel.setOnEndFlingListener(mListener);
  3. // 设置滑动时的声音
  4. mDateWheel.setSoundEffectsEnabled(true);
  5. // 设置adapter
  6. mDateWheel.setAdapter(new WheelTextAdapter(this));
  7. // Adapter的实现
  8. protected class WheelTextAdapter extends BaseAdapter
  9. {
  10. ArrayList<TextInfo> mData = null;
  11. int mWidth  = ViewGroup.LayoutParams.MATCH_PARENT;
  12. int mHeight = 50;
  13. Context mContext = null;
  14. public WheelTextAdapter(Context context)
  15. {
  16. mContext = context;
  17. }
  18. public void setData(ArrayList<TextInfo> data)
  19. {
  20. mData = data;
  21. this.notifyDataSetChanged();
  22. }
  23. public void setItemSize(int width, int height)
  24. {
  25. mWidth  = width;
  26. mHeight = height;
  27. }
  28. @Override
  29. public int getCount()
  30. {
  31. return (null != mData) ? mData.size() : 0;
  32. }
  33. @Override
  34. public Object getItem(int position)
  35. {
  36. return null;
  37. }
  38. @Override
  39. public long getItemId(int position)
  40. {
  41. return 0;
  42. }
  43. @Override
  44. public View getView(int position, View convertView, ViewGroup parent)
  45. {
  46. TextView textView = null;
  47. if (null == convertView)
  48. {
  49. convertView = new TextView(mContext);
  50. convertView.setLayoutParams(new TosGallery.LayoutParams(mWidth, mHeight));
  51. textView = (TextView)convertView;
  52. textView.setGravity(Gravity.CENTER);
  53. textView.setTextSize(26);
  54. textView.setTextColor(Color.BLACK);
  55. }
  56. if (null == textView)
  57. {
  58. textView = (TextView)convertView;
  59. }
  60. TextInfo info = mData.get(position);
  61. textView.setText(info.mText);
  62. textView.setTextColor(info.mColor);
  63. return convertView;
  64. }
  65. }
如果大家感兴趣,可以给我发邮件要源代码,我的邮箱是:leehong2005@163.com

这个是《UEFA.com新闻客户端》,大家关注一下,谢谢

原文地址:http://blog.csdn.net/leehong2005/article/details/8623694

Android 实现 WheelView的更多相关文章

  1. android: WheelView组件(滑轮组件)的应用!

    android前段组件中, 填表单,选择条目 的样式有很多, WheelView滚动组件为其中一种,如下图所示:                                          前两 ...

  2. 揭破android中的闹钟app 二

    · 这节,我们通过wheelview来模仿一个简易的正点闹钟. 我这里不说wheelview来龙去脉,只阐述几个简单的方法,如果,想看一看具体wheelview的内容,请看下面两篇文章: androi ...

  3. android 时间控件概述

    android的自带时间选择控件,是一个让用户既能输入的又能选择的样子.这本来没有太大的问题了. 但是,坑爹的android是开源的.自带的时间控件在某些机型上,早已经是面目全非了,在用以一个普通用户 ...

  4. VLC For Android Ubuntu14.04编译环境搭建

    VLC多媒体播放器(英语:VLC media player,最初为VideoLAN Client.是VideoLAN计划的开放源码多媒体播放器.)支持众多音频与视频解码器及文件格式,并支持DVD影音光 ...

  5. 7.Android开源项目WheelView的时间和地址联动选择对话框

    类似WheelView的时间和地址联动选择对话框在现在App经常看到,今天小结下. 主布局界面: <LinearLayout xmlns:android="http://schemas ...

  6. 【Android 应用开发】 自定义组件 宽高适配方法, 手势监听器操作组件, 回调接口维护策略, 绘制方法分析 -- 基于 WheelView 组件分析自定义组件

    博客地址 : http://blog.csdn.net/shulianghan/article/details/41520569 代码下载 : -- GitHub : https://github.c ...

  7. Android自己定义实现循环滚轮控件WheelView

    首先呈上效果图 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/d ...

  8. [Android]竖直滑动选择器WheelView的实现

    以下内容为原创,转载请注明: 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/3819304.html 公司项目中有这么一个需求,所以需要自己实现下.效 ...

  9. android wheelview实现三级城市选择

    很早之前看淘宝就有了ios那种的城市选择控件,当时也看到网友有分享,不过那个写的很烂,后来(大概是去年吧),我们公司有这么一个项目,当时用的还是网上比较流行的那个黑框的那个,感觉特别的丑,然后我在那个 ...

随机推荐

  1. 如何在 Azure 门户中将托管数据磁盘附加到 Windows VM

    本文介绍了如何通过 Azure 门户将新的托管数据磁盘附加到 Windows 虚拟机. 在开始之前,请查看以下提示: 虚拟机的大小决定了可以附加多少个磁盘. 有关详细信息,请参阅虚拟机大小. 对于新磁 ...

  2. mysql中InnoDB表为什么要建议用自增列做主键

    InnoDB引擎表的特点 1.InnoDB引擎表是基于B+树的索引组织表(IOT) 关于B+树 (图片来源于网上) B+ 树的特点: (1)所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关 ...

  3. Python实例---模拟微信网页登录(day1)

    第一步:创建Django项目 创建Django项目,添加App 创建静态文件夹static 修改settings.py文件 1. 取消csrf注释 2. 添加静态文件路径 # 添加静态文件路径 STA ...

  4. MySQL基础之 逻辑运算符

    mysql的逻辑运算符有四个:与.或.非.异或 我们平时在谈论的时候不考虑带有NULL的情况,今天我们就来考虑一下带有NULL值,他们的结果是怎么样的 AND(&&)运算符 mysql ...

  5. 读高性能JavaScript编程 第三章

    第三章  DOM Scripting  最小化 DOM 访问,在 JavaScript 端做尽可能多的事情. 在反复访问的地方使用局部变量存放 DOM 引用. 小心地处理 HTML 集合,因为他们表现 ...

  6. dns服务器测试工具

    下载地址:https://www.eatm.app/wp-content/uploads/2018/08/eDnsTest.20180810.zip

  7. spark-机器学习实践-K近邻应用实践一

    K近邻应用-异常检测应用 原理: 根据数据样本进行KMeans机器学习模型的建立,获取簇心点,以簇为单位,离簇心最远的第五个点的距离为阈值,大于这个值的为异常点,即获得数据异常. 如图:

  8. BZOJ1941:[SDOI2010]Hide and Seek(K-D Tree)

    Description 小猪iPig在PKU刚上完了无聊的猪性代数课,天资聪慧的iPig被这门对他来说无比简单的课弄得非常寂寞,为了消除寂寞感,他决定和他的好朋友giPi(鸡皮)玩一个更加寂寞的游戏- ...

  9. 不要以为字段以transient修饰的话就一定不会被序列化

    1: 先阅读这边文章:http://www.importnew.com/21517.html 2:被transient修饰真的会被序列化吗? 反例:java.util.ArrayList中底层存储数组 ...

  10. leetcode 200. Number of Islands 、694 Number of Distinct Islands 、695. Max Area of Island 、130. Surrounded Regions

    两种方式处理已经访问过的节点:一种是用visited存储已经访问过的1:另一种是通过改变原始数值的值,比如将1改成-1,这样小于等于0的都会停止. Number of Islands 用了第一种方式, ...