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的关键代码已经解说完了。以下我们来看看效果吧:


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

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

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name ...

  2. WPF 仿IPhone滑块开关 样式 - CheckBox

    原文:WPF 仿IPhone滑块开关 样式 - CheckBox <Style x:Key="CheckRadioFocusVisual"> <Setter Pr ...

  3. 自定义开关ToggleButton

    package com.example.test;import android.os.Bundle;import android.app.Activity;import android.view.Me ...

  4. 使用css3 制作switch开关

    使用css3来实现switch开关的效果: html代码: <!--switch开关--><div class="switch-btn"> <inpu ...

  5. 自定义开关ToggleButton的使用

    [代码]: toggleMe.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override p ...

  6. Arduino IDE搭建ESP8266开发环境,文件下载过慢解决方法 | ESP-01制作WiFi开关教程,改造宿舍灯

    1. Arduino IDE配置ESP8266环境 参考:https://www.jianshu.com/p/cb0274d612b5 首先从 Arduino 官网 下载最新版本的 Arduino I ...

  7. android开关控件Switch和ToggleButton

    序:今天项目中用到了开关按钮控件,查阅了一些资料特地写了这篇博客记录下. 1.Switch <Switch android:id="@+id/bt" android:layo ...

  8. 6.Android之switch和togglebutton按钮学习

    Switch和ToggleButton按钮在手机上也经常看到,比如手机设置里面wlan,蓝牙,gps开关等. 首先在工具上拖进一个switch和togglebutton开关按钮,如图 生成xml代码如 ...

  9. 纯css的滑块开关按钮

    之前在项目中使用滑块开关按钮,纯css写的,不考虑兼容低版本浏览器,先说下原理: 使用 checkbox 的 选中 checked 属性改变css 伪类样式, 一定要使用-webkit-appeara ...

随机推荐

  1. (转)淘淘商城系列——使用maven构建工程时出现的错误

    http://blog.csdn.net/yerenyuan_pku/article/details/72690846 我觉得maven就他妈是一个傻逼,反正我是对它无语了.昨天刚刚使用maven构建 ...

  2. TensorFlow低阶API(一)—— 简介

    简介 本文旨在知道您使用低级别TensorFlow API(TensorFlow Core)开始编程.您可以学习执行以下操作: 管理自己的TensorFlow程序(tf.Graph)和TensorFl ...

  3. convertquota - 把老的配额文件转换为新的格式

    总览 (SYNOPSIS) convertquota [ -ug ] filesystem 描述 (DESCRIPTION) convertquota 把老的配额文件 quota.user 和 quo ...

  4. Mac OS 使用asio库

    下载地址:http://sourceforge.net/projects/asio/files/asio/1.12.2%20%28Stable%29/ 本人下载的版本:asio-1.12.2 1,本人 ...

  5. WNDCLASS和WNDCLASSEX

    typedef struct { UINT cbSize; UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINST ...

  6. 扩展 IHttpModule

    上篇提到请求进入到System.Web后,创建完HttpApplication对象后会执行一堆的管道事件,然后可以通过HttpModule来对其进行扩展,那么这篇文章就来介绍下如何定义我们自己的mod ...

  7. redis简介以及安装

    redis作为开源的高性能的键值对数据库,本身是单线程的,性能虽然没有memcache高,但是也是性能跟memcache相差无几的,memcache是多线程的,但是redis本身功能更加强大,学习一下 ...

  8. HDOJ 1846 Brave Game - 博弈入门

    题目地址:http://acm.hdu.edu.cn/showproblem.php?pid=1846 经典基础博弈,首先面对(m+1)的人一定会输,依次往后推即可: #include<iost ...

  9. python3.x Day4 内置方法,装饰器,生成器,迭代器

    内置方法,就是python3提供的各种函数,可以认为是关键字,帮助进行一些列的牛x运算. abs()#取绝对值 all([])#可迭代对象中的所有元素都为True 则为True,只要至少一个为Fals ...

  10. rbac组件之菜单操作(三)

    菜单包括菜单列表,菜单列表不仅将菜单列出来,而且将每个菜单下的权限也列出来.菜单的添加.删除.修改. urls.py ... re_path(r'^menus/list/$', MenuView.as ...