使用Scroller制作滑块开关ToggleButton

来源:互联网 发布:淘宝卖充值卡进货渠道 编辑:程序博客网 时间:2024/04/26 18:16

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

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

 

一、实现思路

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

由于不会美工,仅仅使用photozoom缩放了三张图片,并不是那么完美,各位看官凑活着看吧。

 


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

背景图片不需要我们去绘制在viewdraw方法里就可以帮我们绘制完成了,我们只需要在合适的时间和合适的位置将开关两张图片画上即可。

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

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


二、实现代码

[java] view plaincopy
  1. public class Toggle extends View {  
  2.     private int mNowX; // 当前滑块的x位置  
  3.     private int mSmoothDuration = 500;  
  4.       
  5.     private boolean isOpen; // 是否为打开状态  
  6.       
  7.     private Drawable mOpenDrawable; // 打开状态的图片  
  8.     private Drawable mCloseDrawable; // 关闭状态的图片  
  9.   
  10.     private Scroller mScroller;  
  11.       
  12.     public Toggle(Context context, AttributeSet attrs) {  
  13.         this(context, attrs, 0);  
  14.     }  
  15.   
  16.     public Toggle(Context context, AttributeSet attrs, int defStyleAttr) {  
  17.         super(context, attrs, defStyleAttr);  
  18.           
  19.         mScroller = new Scroller(context, new LinearInterpolator());  
  20.           
  21.         // 获取两张图片  
  22.         TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.toggle, defStyleAttr, 0);  
  23.         mOpenDrawable = ta.getDrawable(R.styleable.toggle_drawable_open);  
  24.         mCloseDrawable = ta.getDrawable(R.styleable.toggle_drawable_close);  
  25.         ta.recycle();  
  26.     }  
  27.   
  28.     /** 
  29.      * 状态改变时,保存一下isOpen变量 
  30.      * 例如 屏幕旋转,防止在旋转后恢复原样了 
  31.      */  
  32.     @Override  
  33.     protected Parcelable onSaveInstanceState() {  
  34.         Bundle bundle = new Bundle();  
  35.         bundle.putBoolean("open", isOpen);  
  36.         bundle.putParcelable("state"super.onSaveInstanceState());  
  37.         return bundle;  
  38.     }  
  39.       
  40.     /** 
  41.      * 获取保存的isOpen 
  42.      */  
  43.     @Override  
  44.     protected void onRestoreInstanceState(Parcelable state) {  
  45.         if(state instanceof Bundle) {  
  46.             Bundle bundle = (Bundle) state;  
  47.             isOpen = bundle.getBoolean("open");  
  48.             state = bundle.getParcelable("state");  
  49.         }  
  50.           
  51.         super.onRestoreInstanceState(state);  
  52.     }  
  53.       
  54.     /** 
  55.      * 测量 
  56.      */  
  57.     @Override  
  58.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  59.         int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
  60.         int widthSize = MeasureSpec.getSize(widthMeasureSpec);  
  61.           
  62.         /** 
  63.          * 获取背景的宽高 
  64.          */  
  65.         Drawable background = getBackground();  
  66.         // background.getBounds().width()是在背景绘制完成后才返回值  
  67.         // 此时返回0  
  68. //      background.getBounds().width();  
  69.         // 获取宽度  
  70.         int width = Math.max(background.getIntrinsicWidth(), 50);  
  71.         // 获取高度  
  72.         int height = Math.max(background.getIntrinsicHeight(), 20);  
  73.           
  74.         // 父布局给指定了大小  
  75.         if(widthMode == MeasureSpec.EXACTLY) {  
  76.             width = widthSize;  
  77.         }else if(widthMode == MeasureSpec.AT_MOST) { // 父布局给指定了最大限度  
  78.             width = Math.min(width, widthSize);  
  79.         }  
  80.           
  81.         setMeasuredDimension(width, height);  
  82.         // 如果“关闭” 则滑块的位置为当前view宽度-关闭图片宽度  
  83.         if(!isOpen) mNowX = getMeasuredWidth() - mCloseDrawable.getIntrinsicWidth();  
  84.     }  
  85.       
  86.     @Override  
  87.     protected void onDraw(Canvas canvas) {  
  88.         // 根据isOpen获取当前要绘制的drawable  
  89.         Drawable drawing = isOpen ? mOpenDrawable : mCloseDrawable;  
  90.         // clip bounds  
  91.         drawing.setBounds(mNowX, 0, mNowX + mOpenDrawable.getIntrinsicWidth(), getMeasuredHeight());  
  92.         // draw on the canvas  
  93.         drawing.draw(canvas);  
  94.     }  
  95.       
  96.     @Override  
  97.     public void computeScroll() {  
  98.         if(mScroller.computeScrollOffset()) {  
  99.             mNowX = mScroller.getCurrX();  
  100.             if(mScroller.isFinished()) {  
  101.                 isOpen = !isOpen;  
  102.                 if(isOpen) mNowX = 0;  
  103.                 else mNowX = getMeasuredWidth() - mCloseDrawable.getIntrinsicWidth();  
  104.             }  
  105.               
  106.             postInvalidate();  
  107.         }  
  108.     }  
  109.       
  110.     public void toggle() {  
  111.         mScroller.abortAnimation();  
  112.         // open -> close  
  113.         if(isOpen) {  
  114.             mScroller.startScroll(00, getMeasuredWidth() - mOpenDrawable.getIntrinsicWidth() ,   
  115.                     0, mSmoothDuration);  
  116.         }else {  
  117.             mScroller.startScroll(getMeasuredWidth() - mCloseDrawable.getIntrinsicWidth(), 0,   
  118.                     mOpenDrawable.getIntrinsicWidth()-getMeasuredWidth(), 0, mSmoothDuration);  
  119.         }  
  120.           
  121.         postInvalidate();  
  122.     }  
  123.       
  124.     /** 
  125.      * 设置Scroller的Interpolator 
  126.      * @param interpolator 
  127.      */  
  128.     public void setInterpolator(Interpolator interpolator) {  
  129.         mScroller = new Scroller(getContext(), interpolator);  
  130.     }  
  131.       
  132.     /** 
  133.      * 设置动画完成的时间间隔 
  134.      * @param duration 
  135.      */  
  136.     public void setSmoothDuration(int duration) {  
  137.         mSmoothDuration = duration;  
  138.     }  
  139.       
  140.     public boolean isOpen() {  
  141.         return isOpen;  
  142.     }  
  143.       
  144.     public void open() {  
  145.         isOpen = true;  
  146.         mNowX = 0;  
  147.         postInvalidate();  
  148.     }  
  149.       
  150.     public void close() {  
  151.         isOpen = false;  
  152.         mNowX = getMeasuredWidth() - mCloseDrawable.getIntrinsicWidth();  
  153.         postInvalidate();  
  154.     }  
  155. }  

代码量不是很多,而且很清晰,下面我们就来分析分析几个方法。

三、代码分析

首先我们在第二个构造方法中,获取了两个Drawable,分别对应了开和关时的图片。
[java] view plaincopy
  1. TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.toggle, defStyleAttr, 0);  
  2. mOpenDrawable = ta.getDrawable(R.styleable.toggle_drawable_open);  
  3. mCloseDrawable = ta.getDrawable(R.styleable.toggle_drawable_close);  
  4. ta.recycle();  

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

onMeasure方法中,首先获取背景的高度和宽度,注意这里不能使用Drawable.getBounds().width(),因为这个方法只有在Drawable绘制完毕后才会返回值,此前都是返回的0。所以我们使用Drawable.getIntrinsicWidth()来获取Drawable的真实宽度。继续代码,下面的就是一个简单的测量流程了。再来看看最后一行代码
[java] view plaincopy
  1. if(!isOpen) mNowX = getMeasuredWidth() - mCloseDrawable.getIntrinsicWidth();  
因为关闭状态下,我们的滑动要位于view的右边,所以,在这里判断一下,如果是关闭状态,则初始化mNowX为视图的宽度减去Drawable的宽度,也就是Drawable正好位于视图的右边。

onDraw里主要就是根据状态和位置来绘制滑块了:
[java] view plaincopy
  1. // 根据isOpen获取当前要绘制的drawable  
  2. Drawable drawing = isOpen ? mOpenDrawable : mCloseDrawable;  
  3. // clip bounds  
  4. drawing.setBounds(mNowX, 0, mNowX + mOpenDrawable.getIntrinsicWidth(), getMeasuredHeight());  
  5. // draw on the canvas  
  6. drawing.draw(canvas);  

代码很简单, so, 跳过。

接下来再来看看toggle方法,toggle方法是提供给外部调用的,该方法根据现在的状态来调用Scroller.startScroll()方法。
[java] view plaincopy
  1. public void toggle() {  
  2. <span style="white-space:pre">    </span>mScroller.abortAnimation();  
  3.     // open -> close  
  4.     if(isOpen) {  
  5.         mScroller.startScroll(00, getMeasuredWidth() - mOpenDrawable.getIntrinsicWidth() ,   
  6.                 0, mSmoothDuration);  
  7.     }else {  
  8.         mScroller.startScroll(getMeasuredWidth() - mCloseDrawable.getIntrinsicWidth(), 0,   
  9.                 mOpenDrawable.getIntrinsicWidth()-getMeasuredWidth(), 0, mSmoothDuration);  
  10.     }  
  11.           
  12.     postInvalidate();  
  13. }  

可以看到,在该方法中并没有去改变isOpen的值,那isOpen是在什么时候改变的呢?答案是在Scroller停止的时候,来看看重载的computeScroll()的代码.
[java] view plaincopy
  1. @Override  
  2. public void computeScroll() {  
  3.     if(mScroller.computeScrollOffset()) {  
  4.         mNowX = mScroller.getCurrX();  
  5.         if(mScroller.isFinished()) {  
  6.             isOpen = !isOpen;  
  7.             if(isOpen) mNowX = 0;  
  8.             else mNowX = getMeasuredWidth() - mCloseDrawable.getIntrinsicWidth();  
  9.         }  
  10.               
  11.         postInvalidate();  
  12.     }  
  13. }  

可以看到,在if(mScroller.isFinished())中我们改变了isOpen的值,当然,最后别忘了postInvalidate一下,以刷新当前的视图。
到现在为止,Toggle的关键代码已经讲解完了,下面我们来看看效果吧:


代码下载:http://git.oschina.net/qibin/ToggleButton
0 0
原创粉丝点击