HardBirch

android仿iPhone滚轮控件实现及源码分析(一)

时间:12-03-27 栏目:安卓源码解析与小应用 作者:张飞不张,文采横飞 评论:58 点击: 20,446 次

            敬告:由于本文代码较多,所以文章分为了一二两篇,如果不便,敬请谅解,可以先下载文章下方的代码,打开参考本文查看,效果更好!        

 首先,先看下效果图:


        这三张图分别是使用滚动控件实现城市,随机数和时间三个简单的例子,当然,界面有点简陋,下面我们就以时间这个为例,开始解析一下。

     首先,先看下布局文件:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_height="wrap_content"
	android:layout_width="fill_parent"
	android:layout_marginTop="12dp"
	android:orientation="vertical"
	android:background="@drawable/layout_bg">

	<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
		android:layout_height="wrap_content"
		android:layout_width="fill_parent"
		android:layout_gravity="center_horizontal"
		android:paddingLeft="12dp"
		android:paddingRight="12dp"
		android:paddingTop="10dp">

	  	<kankan.wheel.widget.WheelView android:id="@+id/hour"
			android:layout_height="wrap_content"
			android:layout_width="fill_parent"
			android:layout_weight="1"/>
		<kankan.wheel.widget.WheelView android:id="@+id/mins"
			android:layout_height="wrap_content"
			android:layout_width="fill_parent"
			android:layout_weight="1"/>
	</LinearLayout>

	<TimePicker android:id="@+id/time"
		android:layout_marginTop="12dp"
		android:layout_height="wrap_content"
		android:layout_width="fill_parent"
		android:layout_weight="1"/>

</LinearLayout>

        里面只有三个控件,两个自定义的WheelView,还有一个TimePicker,然后进入代码里面看一下:

public class TimeActivity extends Activity {
	// Time changed flag
	private boolean timeChanged = false;

	//
	private boolean timeScrolled = false;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		setContentView(R.layout.time_layout);

		final WheelView hours = (WheelView) findViewById(R.id.hour);
		hours.setAdapter(new NumericWheelAdapter(0, 23));
		hours.setLabel("hours");

		final WheelView mins = (WheelView) findViewById(R.id.mins);
		mins.setAdapter(new NumericWheelAdapter(0, 59, "%02d"));
		mins.setLabel("mins");
		mins.setCyclic(true);

		final TimePicker picker = (TimePicker) findViewById(R.id.time);
		picker.setIs24HourView(true);

		// set current time
		Calendar c = Calendar.getInstance();
		int curHours = c.get(Calendar.HOUR_OF_DAY);
		int curMinutes = c.get(Calendar.MINUTE);

		hours.setCurrentItem(curHours);
		mins.setCurrentItem(curMinutes);

		picker.setCurrentHour(curHours);
		picker.setCurrentMinute(curMinutes);

		// add listeners
		addChangingListener(mins, "min");
		addChangingListener(hours, "hour");

		OnWheelChangedListener wheelListener = new OnWheelChangedListener() {
			public void onChanged(WheelView wheel, int oldValue, int newValue) {
				if (!timeScrolled) {
					timeChanged = true;
					picker.setCurrentHour(hours.getCurrentItem());
					picker.setCurrentMinute(mins.getCurrentItem());
					timeChanged = false;
				}
			}
		};

		hours.addChangingListener(wheelListener);
		mins.addChangingListener(wheelListener);

		OnWheelScrollListener scrollListener = new OnWheelScrollListener() {
			public void onScrollingStarted(WheelView wheel) {
				timeScrolled = true;
			}
			public void onScrollingFinished(WheelView wheel) {
				timeScrolled = false;
				timeChanged = true;
				picker.setCurrentHour(hours.getCurrentItem());
				picker.setCurrentMinute(mins.getCurrentItem());
				timeChanged = false;
			}
		};

		hours.addScrollingListener(scrollListener);
		mins.addScrollingListener(scrollListener);

		picker.setOnTimeChangedListener(new TimePicker.OnTimeChangedListener() {
			public void onTimeChanged(TimePicker  view, int hourOfDay, int minute) {
				if (!timeChanged) {
					hours.setCurrentItem(hourOfDay, true);
					mins.setCurrentItem(minute, true);
				}
			}
		});
	}

	/**
	 * Adds changing listener for wheel that updates the wheel label
	 * @param wheel the wheel
	 * @param label the wheel label
	 */
	private void addChangingListener(final WheelView wheel, final String label) {
		wheel.addChangingListener(new OnWheelChangedListener() {
			public void onChanged(WheelView wheel, int oldValue, int newValue) {
				wheel.setLabel(newValue != 1 ? label + "s" : label);
			}
		});
	}
}

     看一下,里面调用WheelView的方法有setAdapter()、setLabel("mins")、setCyclic(true)、setCurrentItem()、getCurrentItem()、addChangingListener()、addScrollingListener()这些方法,其中setAapter设置数据适配器,setCyclic()设置是否是循环,setCurrentItem和getCurrentItem分别是设置现在选择的item和返回现在选择的item。后面两个设置监听的方法中,需要重写两个接口:

/**
 * Wheel scrolled listener interface.
 */
public interface OnWheelScrollListener {
	/**
	 * Callback method to be invoked when scrolling started.
	 * @param wheel the wheel view whose state has changed.
	 */
	void onScrollingStarted(WheelView wheel);

	/**
	 * Callback method to be invoked when scrolling ended.
	 * @param wheel the wheel view whose state has changed.
	 */
	void onScrollingFinished(WheelView wheel);
}

public interface OnWheelChangedListener {
	/**
	 * Callback method to be invoked when current item changed
	 * @param wheel the wheel view whose state has changed
	 * @param oldValue the old value of current item
	 * @param newValue the new value of current item
	 */
	void onChanged(WheelView wheel, int oldValue, int newValue);
}

在这里使用的是典型的回调方法模式。

然后现在,我们进入WheelView类,看一下他是如何构建,首先,WheelView继承了View类。代码的22行到45行是导入的所需要的类。从54行到135行是声明一些变量和类:

/** Scrolling duration */
	private static final int SCROLLING_DURATION = 400;

	/** Minimum delta for scrolling */
	private static final int MIN_DELTA_FOR_SCROLLING = 1;

	/** Current value & label text color */
	private static final int VALUE_TEXT_COLOR = 0xF0000000;

	/** Items text color */
	private static final int ITEMS_TEXT_COLOR = 0xFF000000;

	/** Top and bottom shadows colors */
	private static final int[] SHADOWS_COLORS = new int[] { 0xFF111111,
			0x00AAAAAA, 0x00AAAAAA };

	/** Additional items height (is added to standard text item height) */
	private static final int ADDITIONAL_ITEM_HEIGHT = 15;

	/** Text size */
	private static final int TEXT_SIZE = 24;

	/** Top and bottom items offset (to hide that) */
	private static final int ITEM_OFFSET = TEXT_SIZE / 5;

	/** Additional width for items layout */
	private static final int ADDITIONAL_ITEMS_SPACE = 10;

	/** Label offset */
	private static final int LABEL_OFFSET = 8;

	/** Left and right padding value */
	private static final int PADDING = 10;

	/** Default count of visible items */
	private static final int DEF_VISIBLE_ITEMS = 5;

	// Wheel Values
	private WheelAdapter adapter = null;
	private int currentItem = 0;

	// Widths
	private int itemsWidth = 0;
	private int labelWidth = 0;

	// Count of visible items
	private int visibleItems = DEF_VISIBLE_ITEMS;

	// Item height
	private int itemHeight = 0;

	// Text paints
	private TextPaint itemsPaint;
	private TextPaint valuePaint;

	// Layouts
	private StaticLayout itemsLayout;
	private StaticLayout labelLayout;
	private StaticLayout valueLayout;

	// Label & background
	private String label;
	private Drawable centerDrawable;

	// Shadows drawables
	private GradientDrawable topShadow;
	private GradientDrawable bottomShadow;

	// Scrolling
	private boolean isScrollingPerformed;
	private int scrollingOffset;

	// Scrolling animation
	private GestureDetector gestureDetector;
	private Scroller scroller;
	private int lastScrollY;

	// Cyclic
	boolean isCyclic = false;

	// Listeners
	private List<OnWheelChangedListener> changingListeners = new LinkedList<OnWheelChangedListener>();
	private List<OnWheelScrollListener> scrollingListeners = new LinkedList<OnWheelScrollListener>();

          在这里面,使用到了StaticLayout,在开发文档中找一下这个类:

StaticLayout is a Layout for text that will not be edited after it is laid out. Use DynamicLayout for text that may change.

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的概述:

A Drawable with a color gradient for buttons, backgrounds, etc.

It can be defined in an XML file with the <shape> element. For more information, see the guide to Drawable Resources.

就是说这个类可以为按钮或者背景等提供渐变颜色的绘制。

TextPaint的概述:

TextPaint is an extension of Paint that leaves room for some extra data used during text measuring and drawing.

  TextPaint是Paint类的一个扩展,主要是用于文本在绘制的过程中为附件的数据留出空间。


GestureDetector:手势检测,看下开发文档中关于该类的概述:

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这个动画接口,我们看下这个接口的概述:

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()方法,看下文档中对该方法的介绍:

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这个方法:

/**
	 * Scroll the wheel
	 * @param itemsToSkip items to scroll
	 * @param time scrolling duration
	 */
	public void scroll(int itemsToScroll, int time) {
		scroller.forceFinished(true);
		lastScrollY = scrollingOffset;
		int offset = itemsToScroll * getItemHeight();
		scroller.startScroll(0, lastScrollY, 0, offset - lastScrollY, time);
		setNextMessage(MESSAGE_SCROLL);
		startScrolling();
	}

       357行到365行是设置item数据能否循环使用。

       384行的initResourcesIfNecessary()方法,从字面意思,如果需要的初始化资源。

private void initResourcesIfNecessary() {
		if (itemsPaint == null) {
			itemsPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG
					| Paint.FAKE_BOLD_TEXT_FLAG);
			//itemsPaint.density = getResources().getDisplayMetrics().density;
			itemsPaint.setTextSize(TEXT_SIZE);
		}

		if (valuePaint == null) {
			valuePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG
					| Paint.FAKE_BOLD_TEXT_FLAG | Paint.DITHER_FLAG);
			//valuePaint.density = getResources().getDisplayMetrics().density;
			valuePaint.setTextSize(TEXT_SIZE);
			valuePaint.setShadowLayer(0.1f, 0, 0.1f, 0xFFC0C0C0);
		}

		if (centerDrawable == null) {
			centerDrawable = getContext().getResources().getDrawable(R.drawable.wheel_val);
		}

		if (topShadow == null) {
			topShadow = new GradientDrawable(Orientation.TOP_BOTTOM, SHADOWS_COLORS);
		}

		if (bottomShadow == null) {
			bottomShadow = new GradientDrawable(Orientation.BOTTOM_TOP, SHADOWS_COLORS);
		}

		setBackgroundResource(R.drawable.wheel_bg);
	}

这个方法就是初始化在532行calculateLayoutWidth()方法中调用了这个方法,同时调用了487行的getMaxTextLength()这个方法。

      471行getTextItem(int index)通过一个索引获取该item的文本。


      这是第一部分,没有多少有太多意思的地方,重点的地方在以后532行到940行的内容,另起一篇,开始分析,这一篇先到这。

      最后是下载地址:

Android仿iPhone滚动控件源码

http://download.csdn.net/detail/aomandeshangxiao/4175719

未完待续!敬请下篇:android仿iPhone滚轮控件实现及源码分析(二)


声明: 本文由( 张飞不张,文采横飞 )原创编译,转载请保留链接: android仿iPhone滚轮控件实现及源码分析(一)

android仿iPhone滚轮控件实现及源码分析(一):目前有58 条留言

  1. [reply]rockjz_zhang[/reply]
    哈哈,不是我写的,国外一个大牛的作品。

    2012-03-27 21:18 [回复]
  2. 0楼
    a4150902:

    牛逼的小东西

    2012-03-28 13:30 [回复]
  3. 支持 呀!

    2012-03-28 13:33 [回复]
  4. [reply]a4150902[/reply]
    呵呵,国外一个大牛的作品,可以学到不少东西,好的话,麻烦帮忙顶一下吧。谢谢。

    2012-03-28 14:30 [回复]
  5. [reply]wanli_smile[/reply]
    支持的话,就帮忙顶一下吧,谢谢。

    2012-03-28 14:31 [回复]
  6. 0楼
    i348018533:

    受教了!

    2012-03-28 14:46 [回复]
  7. vv

    2012-03-28 22:43 [回复]
  8. [reply]i348018533[/reply]
    呵呵,共同学习吧,支持的话,就帮忙顶一下吧!

    2012-03-29 09:31 [回复]
  9. [reply]hzp119065521[/reply]
    这个是什么意思呢??

    2012-03-29 09:32 [回复]
  10. Wooooow

    2012-03-29 10:05 [回复]
  11. [reply]meichen8050753[/reply]
    难道这是传说中的密码???

    2012-03-29 11:21 [回复]
  12. 0楼
    learnJSee:

    该滚轮控件,在我开发应用中有使用过,本来想有空写个调用技术文档,没想到楼主先上了,
    android-wheel挺不错的.
    http://code.google.com/p/android-wheel/downloads/list

    2012-03-29 13:51 [回复]
  13. [reply]learnJSee[/reply]
    呵呵 ,我想问下,我下载下来,只有一个apk啊,你有那个源码吗?有的话,麻烦给我一份,谢谢!

    2012-03-29 14:16 [回复]
  14. 0楼
    haoruifly:

    好东西 帮顶一下

    2012-03-29 14:30 [回复]
  15. [reply]haoruifly[/reply]
    谢谢。。。。

    2012-03-29 14:49 [回复]
  16. 0楼
    cs6480012:

    看不懂的 路过~~~

    2012-03-29 15:21 [回复]
  17. 0楼
    learnJSee:

    [reply]aomandeshangxiao[/reply]
    若要下载google code内的代码,需要你电脑有装SVN软件,
    输入http://android-wheel.googlecode.com/svn/trunk/
    checkout下来就行
    附上csdn下载地址(赚点下载分)
    http://download.csdn.net/detail/learnjsee/4183543

    2012-03-29 15:41 [回复]
  18. 0楼
    WDYDXF:

    [reply]WDYDXF[/reply]
    rtuyrtuyrtuyrtuyrtuy

    2012-03-29 16:04 [回复]
  19. 0楼
    WDYDXF:

    [reply]WDYDXF[/reply]
    hgjfgjhfgjh

    2012-03-29 16:04 [回复]
  20. 0楼
    WDYDXF:

    [reply]learnJSee[/reply]
    sdfgsdfgsdfgdfg

    2012-03-29 16:04 [回复]
  21. [reply]cs6480012[/reply]
    呵呵,看不懂不要紧,可以直接哪去用。

    2012-03-29 16:56 [回复]
  22. 谢lz分享 不过看不懂 呵呵 先下载再说

    2012-03-29 17:33 [回复]
  23. [reply]yutong346395[/reply]
    嗯,看不懂,直接拿去用,我也没有看太懂。。。

    2012-03-29 17:51 [回复]
  24. 0楼
    tuise_yang:

    新人表示压力很大。

    2012-03-29 18:57 [回复]
  25. [reply]tuise_yang[/reply]
    大牛作品,慢慢的看吧,一步一步来。

    2012-03-29 21:22 [回复]
  26. [reply]learnJSee[/reply]
    呵呵 谢谢。

    2012-03-29 21:29 [回复]
  27. 6666666666

    2012-03-30 09:25 [回复]
  28. 55555555

    2012-03-30 09:25 [回复]
  29. [reply]chenqi19900130[/reply]
    这是什么意思,刷数据???

    2012-03-30 09:30 [回复]
  30. LZ V5

    2012-03-30 10:45 [回复]
  31. 不过还是顶

    2012-03-30 15:48 [回复]
  32. 你这控件怎么跟我们公司的前辈们开发用过的控件很像

    2012-03-30 15:48 [回复]
  33. [reply]xiangyong2008[/reply]
    谢谢。。。。

    2012-03-30 15:54 [回复]
  34. [reply]xinyuetonghua[/reply]
    你没有看我说吗,这是一个国外大牛的一个作品,不知道你公司前辈有没有参考。

    2012-03-30 15:55 [回复]
  35. 0楼
    mlg1978:

    是呀,很牛的东西。

    2012-03-31 06:20 [回复]
  36. [reply]mlg1978[/reply]
    好好学习下,人家的设计思路吧

    2012-03-31 16:14 [回复]
  37. 真好

    2012-04-01 14:21 [回复]
  38. [reply]fedfgrgt[/reply]
    我觉得主要是代码写的好。

    2012-04-01 17:44 [回复]
  39. [reply]wang_xiao_bao[/reply]
    好好学习。。。

    2012-04-01 17:44 [回复]
  40. [reply]Hongxinglucia[/reply]
    加不起啊。。。油价太贵。。。

    2012-04-01 17:44 [回复]
  41. 0楼
    DongStone:

    ding

    2012-04-08 00:23 [回复]
  42. 请教个问题,我把城市换成中文,初次加载的时候字全是码在一起的,重叠了,点击一下后不再重叠,换成英文一切正常.求教解决方法!万分感谢

    2012-04-20 14:49 [回复]
  43. [reply]luofengyeer[/reply]
    你只是改了中文吗?其他的没有修改?我修改以后怎么没有问题呢??是不是修改其他地方呢?

    2012-04-20 16:24 [回复]
  44. 确实是只改了中文就出现这种情况了[reply]aomandeshangxiao[/reply]

    2012-04-20 16:46 [回复]
  45. 我找到了,在定义滚轮时宽度是fill_parent的,下回来的代码是wrap_content,所以会出现错误
    [reply]luofengyeer[/reply]

    2012-04-20 17:24 [回复]
  46. [reply]luofengyeer[/reply]
    哦,那就改过来吧,哈哈。。。

    2012-04-20 17:34 [回复]
  47. 0楼
    tomven1990:

    请问一下楼主,我你的kankan.wheel.widget这个包导至我的包里,但是在实例化的时候却出现问题,请问楼主可否给指点指点“小的菜鸟级

    2012-05-07 15:33 [回复]
  48. 0楼
    tomven1990:

    country.setVisibleItems(3);运行这句时出现
    05-07 07:45:59.346: E/AndroidRuntime(895): at com.twt.tomven.Study.wheelSecect(Study.java:312)

    2012-05-07 15:54 [回复]
  49. [reply]tomven1990[/reply]
    报什么错,能说具体点吗?country.setVisibleItems(3),会不会是你的ite,个数设置过少,多设置几个,看看是这个原因吗?建议还是把log说的详细点。

    2012-05-07 16:41 [回复]
  50. 0楼
    tomven1990:

    [reply]aomandeshangxiao[/reply]

    05-08 12:02:29.404: E/AndroidRuntime(383): FATAL EXCEPTION: main
    05-08 12:02:29.404: E/AndroidRuntime(383): java.lang.NullPointerException
    05-08 12:02:29.404: E/AndroidRuntime(383): at com.twt.tomven.Study.wheelSecect(Study.java:312)
    05-08 12:02:29.404: E/AndroidRuntime(383): at com.twt.tomven.Study$1.onClick(Study.java:135)
    05-08 12:02:29.404: E/AndroidRuntime(383): at android.view.View.performClick(View.java:2485)
    05-08 12:02:29.404: E/AndroidRuntime(383): at android.view.View$PerformClick.run(View.java:9080)
    05-08 12:02:29.404: E/AndroidRuntime(383): at android.os.Handler.handleCallback(Handler.java:587)
    05-08 12:02:29.404: E/AndroidRuntime(383): at android.os.Handler.dispatchMessage(Handler.java:92)

    2012-05-08 20:02 [回复]
  51. [reply]tomven1990[/reply]
    你看你的错误,是空指针异常,我猜想是你的country没有初始化,你看看对不对,看看你的country代码有错吗?final WheelView country = (WheelView) findViewById(R.id.xxx);是不是有错。

    2012-05-08 22:57 [回复]
  52. 0楼
    llzz1985:

    果然是好东西!!

    2012-05-23 12:30 [回复]
  53. [reply]llzz1985[/reply]
    因为好东西,所以要多分享。。。

    2012-05-23 14:15 [回复]
  54. 0楼
    douzi_cs:

    不知道为什么 我的里面少了个class类 , 看到可以联系我吗
    2310221668

    2012-08-03 17:36 [回复]
  55. 很好的东西,多谢分享

    2012-08-10 10:31 [回复]
  56. [reply]douzi_cs[/reply]
    重新下载下,应该没问题吧,很多人都下载了,都没问题。。。

    2012-08-10 10:50 [回复]
  57. 0楼
    wongruo:

    呵呵,你这个完全是复制过来的东东,我觉得贴过来的话倒可以把一些关键的函数详细的介绍下

    2012-11-13 15:05 [回复]
  58. [reply]wongruo[/reply]
    恩,具体内容在二。。。

    2012-11-13 19:50 [回复]

发表评论


QQ群互动

Linux系统与内核学习群:194051772

WP建站技术学习交流群:194062106

魔豆之路QR

魔豆的Linux内核之路

魔豆的Linux内核之路

优秀工程师当看优秀书籍

优秀程序员,要看优秀书!

赞助商广告

友荐云推荐