使用Scroller制作滑块开关ToggleButton
2015-04-02 09:26
218 查看
Scroller这个类在自定义view中使用的还算是很频繁的,和它名字一样,我们一般是在控制滑动的时候使用Scroller,以便让view滑动起来不那么生硬。在官方的解释上,Scroller是一个滑动辅助类,也就是说Scroller本身并不参与滑动,而是让我们的代码在Scroller的辅助下轻松的实现平滑滑动的效果。
既然Scroller只是一个辅助类,那能不能利用它来辅助一些其他的功能呢? 当然可以,今天带来额Toggle就是利用Scroller来实现的一个平滑的开关按钮。
由于不会美工,仅仅使用photozoom缩放了三张图片,并不是那么完美,各位看官凑活着看吧。
第一张图片是我们的背景图片,当然也是通过android:background=”@drawable/xxx”来设置的,第二张是状态为开的时候的图片,当然,最后一张就是关了。
背景图片不需要我们去绘制在view的draw方法里就可以帮我们绘制完成了,我们只需要在合适的时间和合适的位置将开关两张图片画上即可。
如何实现从开到关一个过渡的效果呢?当然要使用前面我们提到过的Scroller了,实现的过程是:当我们点击Toggle的时候,调用scroller.start方法,从左边移动到右边,然后切换到关闭状态这个图片就ok了。
实现思路就这么简单,接下载我们来看一下代码。
代码量不是很多,而且很清晰,下面我们就来分析分析几个方法。
继续走代码,onSaveInstanceState和onRestoreInstanceState这两个方法中做的工作就是将isopen保存和恢复了。
onMeasure方法中,首先获取背景的高度和宽度,注意这里不能使用Drawable.getBounds().width(),因为这个方法只有在Drawable绘制完毕后才会返回值,此前都是返回的0。所以我们使用Drawable.getIntrinsicWidth()来获取Drawable的真实宽度。继续代码,下面的就是一个简单的测量流程了。再来看看最后一行代码
onDraw里主要就是根据状态和位置来绘制滑块了:
代码很简单, so, 跳过。
接下来再来看看toggle方法,toggle方法是提供给外部调用的,该方法根据现在的状态来调用Scroller.startScroll()方法。
可以看到,在该方法中并没有去改变isOpen的值,那isOpen是在什么时候改变的呢?答案是在Scroller停止的时候,来看看重载的computeScroll()的代码.
可以看到,在if(mScroller.isFinished())中我们改变了isOpen的值,当然,最后别忘了postInvalidate一下,以刷新当前的视图。
到现在为止,Toggle的关键代码已经讲解完了,下面我们来看看效果吧:
代码下载:http://git.oschina.net/qibin/ToggleButton
既然Scroller只是一个辅助类,那能不能利用它来辅助一些其他的功能呢? 当然可以,今天带来额Toggle就是利用Scroller来实现的一个平滑的开关按钮。
一、实现思路
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);
代码很简单, 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
相关文章推荐
- 使用Scroller制作滑块开关ToggleButton
- 使用Scroller制作滑块开关ToggleButton
- 开关按钮控件 ToggleButton 的使用
- togglebutton的使用(开关按钮)改变布局的方式
- ToggleButton 开关按钮使用代码
- 使用ToggleButton按钮实现的灯泡开关效果
- 黑马程序员IOS开关,滑块和分段控件的基本使用
- iOS 9应用开发教程之使用开关滑块控件以及滚动部署视图
- iOS 9应用开发教程之使用开关滑块控件以及滚动部署视图
- android 使用ToggleButton实现开关效果
- ToggleButton开关按钮的使用
- 关于分别使用CSS3和JQ制作IOS7开关按钮的方法
- 安卓控件使用系列11:ToggleButton开关控件的使用
- 使用ToggleButton按钮实现开关效果
- IOS开关控件,滑块控件的使用
- 【Android 开发】:UI控件之开关状态按钮 ToggleButton 的使用方法
- Android成长日记-使用ToggleButton实现灯的开关
- ToggleButton状态开关控件的使用
- iOS 9应用开发教程之使用开关滑块控件以及滚动部署视图
- 使用C#制作《邮件特快专递》