使用Scroller制作滑块开关ToggleButton

Scroller这个类在自己定义view中使用的还算是非常频繁的,和它名字一样。我们通常是在控制滑动的时候使用Scroller,以便让view滑动起来不那么生硬。在官方的解释上,Scroller是一个滑动辅助类,也就是说Scroller本身并不參与滑动,而是让我们的代码在Scroller的辅助下轻松的实现平滑滑动的效果。

既然Scroller仅仅是一个辅助类,那能不能利用它来辅助一些其它的功能呢? 当然能够,今天带来额Toggle就是利用Scroller来实现的一个平滑的开关button。

一、实现思路

Toggle须要三张图片,一个是背景图片、一个状态为开的图片、一个状态为关的图片。

因为不会美工,只使用photozoom缩放了三张图片,并非那么完美,各位看官凑活着看吧。


 

第一张图片是我们的背景图片。当然也是通过android:background=”@drawable/xxx”来设置的。第二张是状态为开的时候的图片,当然,最后一张就是关了。

背景图片不须要我们去绘制在view的draw方法里就能够帮我们绘制完毕了,我们仅仅须要在合适的时间和合适的位置将开关两张图片画上就可以。

怎样实现从开到关一个过渡的效果呢?当然要使用前面我们提到过的Scroller了,实现的过程是:当我们点击Toggle的时候,调用scroller.start方法,从左边移动到右边,然后切换到关闭状态这个图片就ok了。

实现思路就这么简单,接下载我们来看一下代码。

二、实现代码

public class Toggle extends View {
	private int mNowX; // 当前滑块的x位置
	private int mSmoothDuration = 500;

	private boolean isOpen; // 是否为打开状态

	private Drawable mOpenDrawable; // 打开状态的图片
	private Drawable mCloseDrawable; // 关闭状态的图片

	private Scroller mScroller;

	public Toggle(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public Toggle(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);

		mScroller = new Scroller(context, new LinearInterpolator());

		// 获取两张图片
		TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.toggle, defStyleAttr, 0);
		mOpenDrawable = ta.getDrawable(R.styleable.toggle_drawable_open);
		mCloseDrawable = ta.getDrawable(R.styleable.toggle_drawable_close);
		ta.recycle();
	}

	/**
	 * 状态改变时,保存一下isOpen变量
	 * 比如 屏幕旋转,防止在旋转后恢复原样了
	 */
	@Override
	protected Parcelable onSaveInstanceState() {
		Bundle bundle = new Bundle();
		bundle.putBoolean("open", isOpen);
		bundle.putParcelable("state", super.onSaveInstanceState());
		return bundle;
	}

	/**
	 * 获取保存的isOpen
	 */
	@Override
	protected void onRestoreInstanceState(Parcelable state) {
		if(state instanceof Bundle) {
			Bundle bundle = (Bundle) state;
			isOpen = bundle.getBoolean("open");
			state = bundle.getParcelable("state");
		}

		super.onRestoreInstanceState(state);
	}

	/**
	 * 測量
	 */
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		int widthMode = MeasureSpec.getMode(widthMeasureSpec);
		int widthSize = MeasureSpec.getSize(widthMeasureSpec);

		/**
		 * 获取背景的宽高
		 */
		Drawable background = getBackground();
		// background.getBounds().width()是在背景绘制完毕后才返回值
		// 此时返回0
//		background.getBounds().width();
		// 获取宽度
		int width = Math.max(background.getIntrinsicWidth(), 50);
		// 获取高度
		int height = Math.max(background.getIntrinsicHeight(), 20);

		// 父布局给指定了大小
		if(widthMode == MeasureSpec.EXACTLY) {
			width = widthSize;
		}else if(widthMode == MeasureSpec.AT_MOST) { // 父布局给指定了最大限度
			width = Math.min(width, widthSize);
		}

		setMeasuredDimension(width, height);
		// 假设“关闭” 则滑块的位置为当前view宽度-关闭图片宽度
		if(!isOpen) mNowX = getMeasuredWidth() - mCloseDrawable.getIntrinsicWidth();
	}

	@Override
	protected void onDraw(Canvas canvas) {
		// 依据isOpen获取当前要绘制的drawable
		Drawable drawing = isOpen ? mOpenDrawable : mCloseDrawable;
		// clip bounds
		drawing.setBounds(mNowX, 0, mNowX + mOpenDrawable.getIntrinsicWidth(), getMeasuredHeight());
		// draw on the canvas
		drawing.draw(canvas);
	}

	@Override
	public void computeScroll() {
		if(mScroller.computeScrollOffset()) {
			mNowX = mScroller.getCurrX();
			if(mScroller.isFinished()) {
				isOpen = !isOpen;
				if(isOpen) mNowX = 0;
				else mNowX = getMeasuredWidth() - mCloseDrawable.getIntrinsicWidth();
			}

			postInvalidate();
		}
	}

	public void toggle() {
		mScroller.abortAnimation();
		// open -> close
		if(isOpen) {
			mScroller.startScroll(0, 0, getMeasuredWidth() - mOpenDrawable.getIntrinsicWidth() ,
					0, mSmoothDuration);
		}else {
			mScroller.startScroll(getMeasuredWidth() - mCloseDrawable.getIntrinsicWidth(), 0,
					mOpenDrawable.getIntrinsicWidth()-getMeasuredWidth(), 0, mSmoothDuration);
		}

		postInvalidate();
	}

	/**
	 * 设置Scroller的Interpolator
	 * @param interpolator
	 */
	public void setInterpolator(Interpolator interpolator) {
		mScroller = new Scroller(getContext(), interpolator);
	}

	/**
	 * 设置动画完毕的时间间隔
	 * @param duration
	 */
	public void setSmoothDuration(int duration) {
		mSmoothDuration = duration;
	}

	public boolean isOpen() {
		return isOpen;
	}

	public void open() {
		isOpen = true;
		mNowX = 0;
		postInvalidate();
	}

	public void close() {
		isOpen = false;
		mNowX = getMeasuredWidth() - mCloseDrawable.getIntrinsicWidth();
		postInvalidate();
	}
}

代码量不是非常多,并且非常清晰。以下我们就来分析分析几个方法。

三、代码分析

首先我们在第二个构造方法中。获取了两个Drawable,分别相应了开和关时的图片。

TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.toggle, defStyleAttr, 0);
mOpenDrawable = ta.getDrawable(R.styleable.toggle_drawable_open);
mCloseDrawable = ta.getDrawable(R.styleable.toggle_drawable_close);
ta.recycle();

继续走代码,onSaveInstanceState和onRestoreInstanceState这两个方法中做的工作就是将isopen保存和恢复了。

onMeasure方法中。首先获取背景的高度和宽度,注意这里不能使用Drawable.getBounds().width()。由于这种方法仅仅有在Drawable绘制完成后才会返回值,此前都是返回的0。所以我们使用Drawable.getIntrinsicWidth()来获取Drawable的真实宽度。继续代码,以下的就是一个简单的測量流程了。再来看看最后一行代码

if(!isOpen) mNowX = getMeasuredWidth() - mCloseDrawable.getIntrinsicWidth();

由于关闭状态下,我们的滑动要位于view的右边,所以,在这里推断一下,假设是关闭状态,则初始化mNowX为视图的宽度减去Drawable的宽度,也就是Drawable正好位于视图的右边。

onDraw里主要就是依据状态和位置来绘制滑块了:

// 依据isOpen获取当前要绘制的drawable
Drawable drawing = isOpen ? mOpenDrawable : mCloseDrawable;
// clip bounds
drawing.setBounds(mNowX, 0, mNowX + mOpenDrawable.getIntrinsicWidth(), getMeasuredHeight());
// draw on the canvas
drawing.draw(canvas);

代码非常easy, so, 跳过。

接下来再来看看toggle方法。toggle方法是提供给外部调用的,该方法依据如今的状态来调用Scroller.startScroll()方法。

public void toggle() {
<span style="white-space:pre">	</span>mScroller.abortAnimation();
	// open -> close
	if(isOpen) {
		mScroller.startScroll(0, 0, getMeasuredWidth() - mOpenDrawable.getIntrinsicWidth() ,
				0, mSmoothDuration);
	}else {
		mScroller.startScroll(getMeasuredWidth() - mCloseDrawable.getIntrinsicWidth(), 0,
				mOpenDrawable.getIntrinsicWidth()-getMeasuredWidth(), 0, mSmoothDuration);
	}

	postInvalidate();
}

能够看到。在该方法中并没有去改变isOpen的值,那isOpen是在什么时候改变的呢?答案是在Scroller停止的时候,来看看重载的computeScroll()的代码.

@Override
public void computeScroll() {
	if(mScroller.computeScrollOffset()) {
		mNowX = mScroller.getCurrX();
		if(mScroller.isFinished()) {
			isOpen = !isOpen;
			if(isOpen) mNowX = 0;
			else mNowX = getMeasuredWidth() - mCloseDrawable.getIntrinsicWidth();
		}

		postInvalidate();
	}
}

能够看到,在if(mScroller.isFinished())中我们改变了isOpen的值,当然,最后别忘了postInvalidate一下,以刷新当前的视图。

到如今为止。Toggle的关键代码已经解说完了。以下我们来看看效果吧:

代码下载:http://git.oschina.net/qibin/ToggleButton

时间: 2024-10-06 00:43:19

使用Scroller制作滑块开关ToggleButton的相关文章

安卓学习第8课——开关ToggleButton、Switch

今天学的是对开关的监听,两种开关ToggleButton和switch <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="ma

WPF 滑块开关 转换器 对XML的操作

今天大清早起床打开微信朋友圈看到大学同学院一哥们发的结婚的照片,在此遥祝一对新人:新婚快乐,百年好合!这哥们大学时时班长,结婚也来了好多同学,不由得觉得吾等屌丝大学确实留下了很多遗憾~哦,对了,这哥们还跟我现在在一个公司的不同部门里,听说他也混的如鱼得水,祝福! 转到正题,今天想记录的知识点有三个: 1.wpf样式实现安卓和苹果移动终端上的滑块式开关: 2.wpf转换器的使用: 3.c#对xml文件的操作: 说说需求,项目上要手动打开配置文件修改里面的值,确切来说是在true和false中间切换

mui 滑块开关 进度条 以及如何获取值

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> <title></title

状态开关(ToggleButton)

用一个开关实现两种状态,一个true执行一个命令,一个false执行一个命令. 先学习自慕课网. 先进行activity_main改写. <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_pa

使用css3 制作switch开关

本片文章将简单的采用input[type=checkbox]跟css3来实现switch开关的效果,效果图如下: html代码: <div class="bg_con"> <input id="checkSwitch" type="checkbox" checked="true" class="switch" /> <label for="checkSwitch&qu

纯css的滑块开关按钮

之前在项目中使用滑块开关按钮,纯css写的,不考虑兼容低版本浏览器,先说下原理: 使用 checkbox 的 选中 checked 属性改变css 伪类样式, 一定要使用-webkit-appearance: none; 先清除checkbox的默认样式 ,否则写其他的样式不起作用: 好,不多说,直接上代码: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&qu

Unity3D脚本中文系列教程(十六)

Unity3D脚本中文系列教程(十五) ◆ function OnPostprocessAudio (clip:AudioClip):void 描述:◆  function OnPostprocessGameObjectWithUserProperties (root : GameObject, propNames : string[], values : object[]) : void 描述:在导入文件时,为每个至少附加了一个用户属性的游戏物体调用propNames是一个string[ ],

Android 实现自动接听和挂断电话功能

添加权限 <uses-permission android:name="android.permission.CALL_PHONE"/> <uses-permission android:name="android.permission.MODIFY_PHONE_STATE"/> main.xml <?xml version="1.0" encoding="utf-8"?> <Line

常见算法和例题

第3章  算法与程序设计模块 3.1  算    法 算法是对特定问题求解步骤的一种描述,它是指令的有限序列,其中每一条指令表示一个或多个操作. 常用的算法:列举了穷举搜索.递归.回溯.递推.模拟.分治.贪心.深度优先搜索.广度优先搜索等几种较为常用的算法,没有做过多的描述,一旦给出具体描述,容易使内容加深,产生严重学科取向的引导,符合教育部普通高中课程方案的特点,对于这些必需的方法和思想,关键不在于学生能不能,而在于教师是否想到,是否有过关注,引发学生对系统方法和思想的思考,重视建立编程思想,