Android SwitchButton(滑动开关)

  在介绍SwitchButton之前,先来看一下系统Button是如何实现的。源码如下:
[html]  view plain  copy
  1. @RemoteView  
  2. public class Button extends TextView {  
  3.     public Button(Context context) {  
  4.         this(context, null);  
  5.     }  
  6.   
  7.     public Button(Context context, AttributeSet attrs) {  
  8.         this(context, attrs, com.android.internal.R.attr.buttonStyle);  
  9.     }  
  10.   
  11.     public Button(Context context, AttributeSet attrs, int defStyle) {  
  12.         super(context, attrs, defStyle);  
  13.     }  
  14.   
  15.     @Override  
  16.     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {  
  17.         super.onInitializeAccessibilityEvent(event);  
  18.         event.setClassName(Button. class.getName());  
  19.     }  
  20.   
  21.     @Override  
  22.     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {  
  23.         super.onInitializeAccessibilityNodeInfo(info);  
  24.         info.setClassName(Button. class.getName());  
  25.     }  
  26. }  
  是直接继承于TextView,所不同的是在构造方法中添加了Button的样式,并且在初始化可见性方面交由Button类自己来处理。虽然Button的实现比较简单,但是它的子类并不是这样。看一下:

  直接子类只有有一个,CompoundButton。它是一个抽象类,而实现这个类的控件正是 CheckBox RadioButton Switch ToggleButton 这四个,所以先重点说一下它。源码如下:
  
[html]  view plain  copy
  1. /**  
  2.  * <p>  
  3.  * A button with two states, checked and unchecked. When the button is pressed  
  4.  * or clicked, the state changes automatically.  
  5.  * </p>  
  6.  *  
  7.  * <p><strong>XML attributes </strong></p>  
  8.  * <p>  
  9.  * See {@link android.R.styleable#CompoundButton  
  10.  * CompoundButton Attributes}, {@link android.R.styleable#Button Button  
  11.  * Attributes}, {@link android.R.styleable#TextView TextView Attributes}, {@link  
  12.  * android.R.styleable #View View Attributes}  
  13.  * </p>  
  14.  */  
  15. public abstract class CompoundButton extends Button implements Checkable {  
  16.     private boolean mChecked ;  
  17.     private int mButtonResource ;  
  18.     private boolean mBroadcasting ;  
  19.     private Drawable mButtonDrawable;  
  20.     private OnCheckedChangeListener mOnCheckedChangeListener;  
  21.     private OnCheckedChangeListener mOnCheckedChangeWidgetListener ;  
  22.   
  23.     private static final int[] CHECKED_STATE_SET = {  
  24.         R.attr.state_checked  
  25.     };  
  26.   
  27.     public CompoundButton(Context context) {  
  28.         this(context, null);  
  29.     }  
  30.   
  31.     public CompoundButton(Context context, AttributeSet attrs) {  
  32.         this(context, attrs, 0);  
  33.     }  
  34.   
  35.     public CompoundButton(Context context, AttributeSet attrs, int defStyle) {  
  36.         super(context, attrs, defStyle);  
  37.   
  38.         TypedArray a =  
  39.                 context.obtainStyledAttributes(  
  40.                         attrs, com.android.internal.R.styleable.CompoundButton, defStyle, 0);  
  41.   
  42.         Drawable d = a.getDrawable(com.android.internal.R.styleable.CompoundButton_button);  
  43.         if (d != null ) {  
  44.             setButtonDrawable(d);  
  45.         }  
  46.   
  47.         boolean checked = a  
  48.                 .getBoolean(com.android.internal.R.styleable.CompoundButton_checked, false);  
  49.         setChecked(checked);  
  50.   
  51.         a.recycle();  
  52.     }  
  53.   
  54.     public void toggle() {  
  55.         setChecked(! mChecked);  
  56.     }  
  57.   
  58.     @Override  
  59.     public boolean performClick() {  
  60.         /*  
  61.          * XXX: These are tiny, need some surrounding 'expanded touch area',  
  62.          * which will need to be implemented in Button if we only override  
  63.          * performClick()  
  64.          */  
  65.   
  66.         /* When clicked, toggle the state */  
  67.         toggle();  
  68.         return super .performClick();  
  69.     }  
  70.   
  71.     @ViewDebug.ExportedProperty  
  72.     public boolean isChecked() {  
  73.         return mChecked ;  
  74.     }  
  75.   
  76.     /**  
  77.      * <p>Changes the checked state of this button.</p>  
  78.      *  
  79.      * @param checked true to check the button, false to uncheck it  
  80.      */  
  81.     public void setChecked(boolean checked) {  
  82.         if (mChecked != checked) {  
  83.             mChecked = checked;  
  84.             refreshDrawableState();  
  85.             notifyViewAccessibilityStateChangedIfNeeded(  
  86.                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED );  
  87.   
  88.             // Avoid infinite recursions if setChecked() is called from a listener  
  89.             if (mBroadcasting ) {  
  90.                 return;  
  91.             }  
  92.   
  93.             mBroadcasting = true ;  
  94.             if (mOnCheckedChangeListener != null) {  
  95.                 mOnCheckedChangeListener.onCheckedChanged(this, mChecked);  
  96.             }  
  97.             if (mOnCheckedChangeWidgetListener != null) {  
  98.                 mOnCheckedChangeWidgetListener .onCheckedChanged(this, mChecked);  
  99.             }  
  100.   
  101.             mBroadcasting = false ;             
  102.         }  
  103.     }  
  104.   
  105.     /**  
  106.      * Register a callback to be invoked when the checked state of this button  
  107.      * changes.  
  108.      *  
  109.      * @param listener the callback to call on checked state change  
  110.      */  
  111.     public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {  
  112.         mOnCheckedChangeListener = listener;  
  113.     }  
  114.   
  115.     /**  
  116.      * Register a callback to be invoked when the checked state of this button  
  117.      * changes. This callback is used for internal purpose only.  
  118.      *  
  119.      * @param listener the callback to call on checked state change  
  120.      * @hide  
  121.      */  
  122.     void setOnCheckedChangeWidgetListener(OnCheckedChangeListener listener) {  
  123.         mOnCheckedChangeWidgetListener = listener;  
  124.     }  
  125.   
  126.     /**  
  127.      * Interface definition for a callback to be invoked when the checked state  
  128.      * of a compound button changed.  
  129.      */  
  130.     public static interface OnCheckedChangeListener {  
  131.         /**  
  132.          * Called when the checked state of a compound button has changed.  
  133.          *  
  134.          * @param buttonView The compound button view whose state has changed.  
  135.          * @param isChecked  The new checked state of buttonView.  
  136.          */  
  137.         void onCheckedChanged(CompoundButton buttonView, boolean isChecked);  
  138.     }  
  139.   
  140.     /**  
  141.      * Set the background to a given Drawable, identified by its resource id.  
  142.      *  
  143.      * @param resid the resource id of the drawable to use as the background  
  144.      */  
  145.     public void setButtonDrawable(int resid) {  
  146.         if (resid != 0 && resid == mButtonResource ) {  
  147.             return;  
  148.         }  
  149.   
  150.         mButtonResource = resid;  
  151.   
  152.         Drawable d = null;  
  153.         if (mButtonResource != 0) {  
  154.             d = getResources().getDrawable(mButtonResource );  
  155.         }  
  156.         setButtonDrawable(d);  
  157.     }  
  158.   
  159.     /**  
  160.      * Set the background to a given Drawable  
  161.      *  
  162.      * @param d The Drawable to use as the background  
  163.      */  
  164.     public void setButtonDrawable(Drawable d) {  
  165.         if (d != null ) {  
  166.             if (mButtonDrawable != null) {  
  167.                 mButtonDrawable.setCallback(null);  
  168.                 unscheduleDrawable( mButtonDrawable);  
  169.             }  
  170.             d.setCallback( this);  
  171.             d.setVisible(getVisibility() == VISIBLE, false);  
  172.             mButtonDrawable = d;  
  173.             setMinHeight(mButtonDrawable .getIntrinsicHeight());  
  174.         }  
  175.   
  176.         refreshDrawableState();  
  177.     }  
  178.   
  179.     @Override  
  180.     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {  
  181.         super.onInitializeAccessibilityEvent(event);  
  182.         event.setClassName(CompoundButton.class .getName());  
  183.         event.setChecked( mChecked);  
  184.     }  
  185.   
  186.     @Override  
  187.     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {  
  188.         super.onInitializeAccessibilityNodeInfo(info);  
  189.         info.setClassName(CompoundButton.class .getName());  
  190.         info.setCheckable( true);  
  191.         info.setChecked( mChecked);  
  192.     }  
  193.   
  194.     @Override  
  195.     public int getCompoundPaddingLeft() {  
  196.         int padding = super.getCompoundPaddingLeft();  
  197.         if (!isLayoutRtl()) {  
  198.             final Drawable buttonDrawable = mButtonDrawable;  
  199.             if (buttonDrawable != null) {  
  200.                 padding += buttonDrawable.getIntrinsicWidth();  
  201.             }  
  202.         }  
  203.         return padding;  
  204.     }  
  205.   
  206.     @Override  
  207.     public int getCompoundPaddingRight() {  
  208.         int padding = super.getCompoundPaddingRight();  
  209.         if (isLayoutRtl()) {  
  210.             final Drawable buttonDrawable = mButtonDrawable;  
  211.             if (buttonDrawable != null) {  
  212.                 padding += buttonDrawable.getIntrinsicWidth();  
  213.             }  
  214.         }  
  215.         return padding;  
  216.     }  
  217.   
  218.     /**  
  219.      * @hide  
  220.      */  
  221.     @Override  
  222.     public int getHorizontalOffsetForDrawables() {  
  223.         final Drawable buttonDrawable = mButtonDrawable ;  
  224.         return (buttonDrawable != null) ? buttonDrawable.getIntrinsicWidth() : 0;  
  225.     }  
  226.   
  227.     @Override  
  228.     protected void onDraw(Canvas canvas) {  
  229.         super.onDraw(canvas);  
  230.   
  231.         final Drawable buttonDrawable = mButtonDrawable ;  
  232.         if (buttonDrawable != null) {  
  233.             final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK ;  
  234.             final int drawableHeight = buttonDrawable.getIntrinsicHeight();  
  235.             final int drawableWidth = buttonDrawable.getIntrinsicWidth();  
  236.   
  237.             int top = 0;  
  238.             switch (verticalGravity) {  
  239.                 case Gravity.BOTTOM :  
  240.                     top = getHeight() - drawableHeight;  
  241.                     break;  
  242.                 case Gravity.CENTER_VERTICAL :  
  243.                     top = (getHeight() - drawableHeight) / 2;  
  244.                     break;  
  245.             }  
  246.             int bottom = top + drawableHeight;  
  247.             int left = isLayoutRtl() ? getWidth() - drawableWidth : 0;  
  248.             int right = isLayoutRtl() ? getWidth() : drawableWidth;  
  249.   
  250.             buttonDrawable.setBounds(left, top, right, bottom);  
  251.             buttonDrawable.draw(canvas);  
  252.         }  
  253.     }  
  254.   
  255.     @Override  
  256.     protected int[] onCreateDrawableState(int extraSpace) {  
  257.         final int [] drawableState = super.onCreateDrawableState(extraSpace + 1);  
  258.         if (isChecked()) {  
  259.             mergeDrawableStates(drawableState, CHECKED_STATE_SET);  
  260.         }  
  261.         return drawableState;  
  262.     }  
  263.   
  264.     @Override  
  265.     protected void drawableStateChanged() {  
  266.         super.drawableStateChanged();  
  267.          
  268.         if (mButtonDrawable != null) {  
  269.             int[] myDrawableState = getDrawableState();  
  270.              
  271.             // Set the state of the Drawable  
  272.             mButtonDrawable.setState(myDrawableState);  
  273.              
  274.             invalidate();  
  275.         }  
  276.     }  
  277.   
  278.     @Override  
  279.     protected boolean verifyDrawable(Drawable who) {  
  280.         return super .verifyDrawable(who) || who == mButtonDrawable;  
  281.     }  
  282.   
  283.     @Override  
  284.     public void jumpDrawablesToCurrentState() {  
  285.         super.jumpDrawablesToCurrentState();  
  286.         if (mButtonDrawable != null) mButtonDrawable.jumpToCurrentState();  
  287.     }  
  288.   
  289.     static class SavedState extends BaseSavedState {  
  290.         boolean checked ;  
  291.   
  292.         /**  
  293.          * Constructor called from {@link CompoundButton#onSaveInstanceState()}  
  294.          */  
  295.         SavedState(Parcelable superState) {  
  296.             super(superState);  
  297.         }  
  298.          
  299.         /**  
  300.          * Constructor called from {@link #CREATOR}  
  301.          */  
  302.         private SavedState(Parcel in) {  
  303.             super(in);  
  304.             checked = (Boolean)in.readValue( null);  
  305.         }  
  306.   
  307.         @Override  
  308.         public void writeToParcel(Parcel out, int flags) {  
  309.             super.writeToParcel(out, flags);  
  310.             out.writeValue( checked);  
  311.         }  
  312.   
  313.         @Override  
  314.         public String toString() {  
  315.             return "CompoundButton.SavedState{"  
  316.                     + Integer.toHexString(System.identityHashCode(this))  
  317.                     + " checked=" + checked + "}" ;  
  318.         }  
  319.   
  320.         public static final Parcelable.Creator<SavedState> CREATOR  
  321.                 = new Parcelable.Creator<SavedState>() {  
  322.             public SavedState createFromParcel(Parcel in) {  
  323.                 return new SavedState(in);  
  324.             }  
  325.   
  326.             public SavedState[] newArray(int size) {  
  327.                 return new SavedState[size];  
  328.             }  
  329.         };  
  330.     }  
  331.   
  332.     @Override  
  333.     public Parcelable onSaveInstanceState() {  
  334.         // Force our ancestor class to save its state  
  335.         setFreezesText( true);  
  336.         Parcelable superState = super.onSaveInstanceState();  
  337.   
  338.         SavedState ss = new SavedState(superState);  
  339.   
  340.         ss. checked = isChecked();  
  341.         return ss;  
  342.     }  
  343.   
  344.     @Override  
  345.     public void onRestoreInstanceState(Parcelable state) {  
  346.         SavedState ss = (SavedState) state;  
  347.    
  348.         super.onRestoreInstanceState(ss.getSuperState());  
  349.         setChecked(ss. checked);  
  350.         requestLayout();  
  351.     }  
  352. }  
   先从构造方法开始,在构造方法中,
[html]  view plain  copy
  1. public CompoundButton(Context context, AttributeSet attrs, int defStyle) {  
  2.        super(context, attrs, defStyle);  
  3.   
  4.        TypedArray a =  
  5.                context.obtainStyledAttributes(  
  6.                        attrs, com.android.internal.R.styleable.CompoundButton, defStyle, 0);  
  7.   
  8.        Drawable d = a.getDrawable(com.android.internal.R.styleable.CompoundButton_button);  
  9.        if (d != null ) {  
  10.            setButtonDrawable(d);  
  11.        }  
  12.   
  13.        boolean checked = a  
  14.                .getBoolean(com.android.internal.R.styleable.CompoundButton_checked, false);  
  15.        setChecked(checked);  
  16.   
  17.        a.recycle();  
  18.    }  
  19.    
   先是从attrs中读取定义的属性,一个是Drawable用于设置背景;一个是布尔类型变量用于判断是否check过。设置背景使用的是setButtonDrawable()方法,代码如下:
[html]  view plain  copy
  1. /**  
  2.      * Set the background to a given Drawable  
  3.      *  
  4.      * @param d The Drawable to use as the background  
  5.      */  
  6.     public void setButtonDrawable(Drawable d) {  
  7.         if (d != null ) {  
  8.             if (mButtonDrawable != null) {  
  9.                 mButtonDrawable.setCallback(null);  
  10.                 unscheduleDrawable( mButtonDrawable);  
  11.             }  
  12.             d.setCallback( this);  
  13.             d.setVisible(getVisibility() == VISIBLE, false);  
  14.             mButtonDrawable = d;  
  15.             setMinHeight(mButtonDrawable .getIntrinsicHeight());  
  16.         }  
  17.   
  18.         refreshDrawableState();  
  19.     }  
  这个方法写的就比较完善,可以作为一个学习的典范。首先判断传递过来的Drawable是否为空,如果不为空并且默认的Drawable也不为空,那么取消默认Drawable的callback,然后调用 unscheduleDrawable 方法。这个方法代码如下:
[html]  view plain  copy
  1. /**  
  2.     * Unschedule any events associated with the given Drawable.  This can be  
  3.     * used when selecting a new Drawable into a view, so that the previous  
  4.     * one is completely unscheduled.  
  5.     *  
  6.     * @param who The Drawable to unschedule.  
  7.     *  
  8.     * @see #drawableStateChanged  
  9.     */  
  10.    public void unscheduleDrawable(Drawable who) {  
  11.        if (mAttachInfo != null && who != null) {  
  12.            mAttachInfo.mViewRootImpl .mChoreographer.removeCallbacks(  
  13.                    Choreographer.CALLBACK_ANIMATION, null, who);  
  14.        }  
  15.    }  
   从方法注释中可以看出它的用途,正是更换Drawable时候使用的。接下来开始重新设置Drawable,包括回调、可见性、最小高度。最后调用 refreshDrawableState() 方法,这个是View类的方法,用于更新Drawable状态。
  然后再回过头看一下 setChecked (checked)方法,这个用于设置check,也就是button的点击状态。代码如下:
[html]  view plain  copy
  1. /**  
  2.      * <p>Changes the checked state of this button.</p>  
  3.      *  
  4.      * @param checked true to check the button, false to uncheck it  
  5.      */  
  6.     public void setChecked( boolean checked) {  
  7.         if (mChecked != checked) {  
  8.             mChecked = checked;  
  9.             refreshDrawableState();  
  10.             notifyViewAccessibilityStateChangedIfNeeded(  
  11.                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED );  
  12.   
  13.             // Avoid infinite recursions if setChecked() is called from a listener  
  14.             if (mBroadcasting ) {  
  15.                 return;  
  16.             }  
  17.   
  18.             mBroadcasting = true ;  
  19.             if (mOnCheckedChangeListener != null) {  
  20.                 mOnCheckedChangeListener.onCheckedChanged(this, mChecked);  
  21.             }  
  22.             if (mOnCheckedChangeWidgetListener != null) {  
  23.                 mOnCheckedChangeWidgetListener .onCheckedChanged(this, mChecked);  
  24.             }  
  25.             mBroadcasting = false ;             
  26.         }  
  27.     }  
  在这个方法中多出了一个接口,这个接口真是check的一个回调接口,代码如下:
[html]  view plain  copy
  1. /**  
  2.      * Interface definition for a callback to be invoked when the checked state  
  3.      * of a compound button changed.  
  4.      */  
  5.     public static interface OnCheckedChangeListener {  
  6.         /**  
  7.          * Called when the checked state of a compound button has changed.  
  8.          *  
  9.          * @param buttonView The compound button view whose state has changed.  
  10.          * @param isChecked  The new checked state of buttonView.  
  11.          */  
  12.         void onCheckedChanged(CompoundButton buttonView, boolean isChecked);  
  13.     }  
  这种回调接口在Android中处处可见,之前的文章也有介绍过。但是在上面的方法,它使用了一个mBroadcasting变量,进而巧妙地避免了重复递归的问题,大家自己感受一下。
  然后就是ondraw()方法了,把之前的drawable画出来。代码如下:
[html]  view plain  copy
  1. @Override  
  2.   protected void onDraw(Canvas canvas) {  
  3.       super.onDraw(canvas);  
  4.   
  5.       final Drawable buttonDrawable = mButtonDrawable ;  
  6.       if (buttonDrawable != null) {  
  7.           final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK ;  
  8.           final int drawableHeight = buttonDrawable.getIntrinsicHeight();  
  9.           final int drawableWidth = buttonDrawable.getIntrinsicWidth();  
  10.   
  11.           int top = 0;  
  12.           switch (verticalGravity) {  
  13.               case Gravity.BOTTOM :  
  14.                   top = getHeight() - drawableHeight;  
  15.                   break;  
  16.               case Gravity.CENTER_VERTICAL :  
  17.                   top = (getHeight() - drawableHeight) / 2;  
  18.                   break;  
  19.           }  
  20.           int bottom = top + drawableHeight;  
  21.           int left = isLayoutRtl() ? getWidth() - drawableWidth : 0;  
  22.           int right = isLayoutRtl() ? getWidth() : drawableWidth;  
  23.   
  24.           buttonDrawable.setBounds(left, top, right, bottom);  
  25.           buttonDrawable.draw(canvas);  
  26.       }  
  27.   }  
  看得出来,在onDrawable()方法中,最主要的部分还是如何确定上下左右四个参数。确定完后就可以画出来了。但是,CompoundButton是一个抽象类,并不能直接使用,那看一下它的子类是如何实现的:

1、CheckBox
[html]  view plain  copy
  1.  public class CheckBox extends CompoundButton {  
  2.     public CheckBox(Context context) {  
  3.         this(context, null);  
  4.     }  
  5.      
  6.     public CheckBox(Context context, AttributeSet attrs) {  
  7.         this(context, attrs, com.android.internal.R.attr.checkboxStyle);  
  8.     }  
  9.   
  10.     public CheckBox(Context context, AttributeSet attrs, int defStyle) {  
  11.         super(context, attrs, defStyle);  
  12.     }  
  13.   
  14.     @Override  
  15.     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {  
  16.         super.onInitializeAccessibilityEvent(event);  
  17.         event.setClassName(CheckBox. class.getName());  
  18.     }  
  19.   
  20.     @Override  
  21.     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {  
  22.         super.onInitializeAccessibilityNodeInfo(info);  
  23.         info.setClassName(CheckBox. class.getName());  
  24.     }  
  25. }  
  和Button的实现差不多,使用了一个自己的样式。并且也是重写了那两个方法。再来看一下RadioButton,
[html]  view plain  copy
  1. public class RadioButton extends CompoundButton {  
  2.      
  3.     public RadioButton(Context context) {  
  4.         this(context, null);  
  5.     }  
  6.      
  7.     public RadioButton(Context context, AttributeSet attrs) {  
  8.         this(context, attrs, com.android.internal.R.attr.radioButtonStyle);  
  9.     }  
  10.   
  11.     public RadioButton(Context context, AttributeSet attrs, int defStyle) {  
  12.         super(context, attrs, defStyle);  
  13.     }  
  14.   
  15.     /**  
  16.      * {@inheritDoc}  
  17.      * <p>  
  18.      * If the radio button is already checked, this method will not toggle the radio button.  
  19.      */  
  20.     @Override  
  21.     public void toggle() {  
  22.         // we override to prevent toggle when the radio is already  
  23.         // checked (as opposed to check boxes widgets)  
  24.         if (!isChecked()) {  
  25.             super.toggle();  
  26.         }  
  27.     }  
  28.   
  29.     @Override  
  30.     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {  
  31.         super.onInitializeAccessibilityEvent(event);  
  32.         event.setClassName(RadioButton. class.getName());  
  33.     }  
  34.   
  35.     @Override  
  36.     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {  
  37.         super.onInitializeAccessibilityNodeInfo(info);  
  38.         info.setClassName(RadioButton. class.getName());  
  39.     }  
  40. }  
   和CheckBox实现差不多,区别在于多重写了一个方法,用于防止按钮被重复点击。另外还有ToggleButton以及Switch,前者实现也比较简单,后者稍微麻烦了一些,感兴趣可以自己分析。
  最后切入正题,看看滑动Button要如何实现呢?首先看一下效果图:
 图1-1
  
 图1-2
  图1-1所示的滑动Button实现的思路是这样的,背景图片有开和关的文字,一个按钮在其上面左右滑动,遮住相应的部分,使其在一个位置时候只能看到一个开关。

  如图1-3,在实现的时候,先画一个开关背景图片只,然后在其上面画一个按钮,滑动开关的时候对上面的按钮进行处理即可。
  准备:
    1、按钮图片
       
      
    2、背景图片       
       
 编码:
    在自定义滑动按钮控件的时候,可以有多种选择,可以继承于Button,也可以继承于Button的子类,也可以继承于View类等。我们知道滑动按钮是一个很简单的控件,就是左右滑动改变显示内容,不需要其他的额外东西在里面,所以直接继承于View来实现即可。如果继承于系统的一些控件,那么有很多东西用不到,会造成浪费。
    1、定义一个类继承于View,初始化构造方法,在构造方法中加载图片及其信息。
    2、重写onMeasure()方法,计算控件的大小。
    3、重写onTouchEvent()方法,对滑动事件进行判别处理。
    4、定义接口,实现回调。
    5、重写onDraw()方法,动态画出按钮。
代码如下:
[html]  view plain  copy
  1. /**  
  2. *  
  3. */  
  4. package com.kince.slidebutton;  
  5.   
  6. import android.content.Context;  
  7. import android.graphics.Bitmap;  
  8. import android.graphics.BitmapFactory;  
  9. import android.graphics.Canvas;  
  10. import android.util.AttributeSet;  
  11. import android.util.Log;  
  12. import android.view.MotionEvent;  
  13. import android.view.View;  
  14.   
  15. /**  
  16. * @author kince  
  17. * @category 左右手势滑动button  
  18. * @serial 1.0.0  
  19. * @since 2014.5.17  
  20. * @see http://blog.csdn.net/wangjinyu501  
  21. *  
  22. */  
  23. public class SlideButton extends View {  
  24.   
  25.      private Bitmap slideBitMap;// 滑动图片  
  26.      private Bitmap switchBitMap;// 背景图片  
  27.   
  28.      private int slideBitMapWidth;// 滑动图片宽度  
  29.      private int switchBitMapWidth;// 背景图片宽度  
  30.      private int switchBitMapHeight;// 背景图片高度  
  31.   
  32.      private boolean currentState;// 开关状态  
  33.      private boolean isSliding = false; // 是否正在滑动中  
  34.   
  35.      private int currentX; // 当前开关的位置  
  36.   
  37.      private OnToggleStateChangedListener mChangedListener;// 回调接口  
  38.   
  39.      /**  
  40.      * @param context  
  41.      *            在java代码中直接调用使用此构造方法  
  42.      */  
  43.      public SlideButton(Context context) {  
  44.           this(context, null);  
  45.           // TODO Auto-generated constructor stub  
  46.      }  
  47.   
  48.      /**  
  49.      * @param context  
  50.      * @param attrs  
  51.      *            在xml中使用要用到这个方法  
  52.      */  
  53.      public SlideButton(Context context, AttributeSet attrs) {  
  54.           this(context, attrs, 0);  
  55.           // TODO Auto-generated constructor stub  
  56.      }  
  57.   
  58.      /**  
  59.      * @param context  
  60.      * @param attrs  
  61.      * @param defStyleAttr  
  62.      *            指定一个样式  
  63.      */  
  64.      public SlideButton(Context context, AttributeSet attrs, int defStyleAttr) {  
  65.           super(context, attrs, defStyleAttr);  
  66.           initBitmap();  
  67.      }  
  68.   
  69.      /**  
  70.      * @category 加载背景图片以及开关图片 然后获取各自的宽高  
  71.      *  
  72.      */  
  73.      private void initBitmap() {  
  74.           // TODO Auto-generated method stub  
  75.           slideBitMap = BitmapFactory.decodeResource(getResources(),  
  76.                     R.drawable.slide_button_background);  
  77.           switchBitMap = BitmapFactory.decodeResource(getResources(),  
  78.                     R.drawable.switch_background);  
  79.           slideBitMapWidth = slideBitMap.getWidth();  
  80.           switchBitMapWidth = switchBitMap.getWidth();  
  81.           switchBitMapHeight = switchBitMap.getHeight();  
  82.           Log.i("switchBitMapWidth", switchBitMapWidth + "");  
  83.      }  
  84.   
  85.      @Override  
  86.      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  87.           // TODO Auto-generated method stub  
  88.           super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  89.           setMeasuredDimension(switchBitMapWidth, switchBitMapHeight);// 设置控件的宽高  
  90.      }  
  91.   
  92.      @Override  
  93.      protected void onDraw(Canvas canvas) {  
  94.           // 绘制button背景图片  
  95.           canvas.drawBitmap(switchBitMap, 0, 0, null);  
  96.           // 绘制滑动开关  
  97.           if (isSliding) {// 如果当前状态是滑动中 则动态绘制开关  
  98.                int dis = currentX - slideBitMapWidth / 2;  
  99.                if (dis < 0) {  
  100.                     dis = 0;  
  101.                } else if (dis > switchBitMapWidth - slideBitMapWidth) {  
  102.                     dis = switchBitMapWidth - slideBitMapWidth;  
  103.                }  
  104.                canvas.drawBitmap(slideBitMap, dis, 0, null);  
  105.           } else {  
  106.                if (currentState) { // 绘制开关为开的状态  
  107.                     canvas.drawBitmap(slideBitMap, switchBitMapWidth  
  108.                               - slideBitMapWidth, 0, null);  
  109.                } else { // 绘制开关为关的状态  
  110.                     canvas.drawBitmap(slideBitMap, 0, 0, null);  
  111.                }  
  112.           }  
  113.           super.onDraw(canvas);  
  114.      }  
  115.   
  116.      @Override  
  117.      public boolean onTouchEvent(MotionEvent event) {  
  118.           // 手势识别 判断滑动方向  
  119.           int action = event.getAction();  
  120.           switch (action) {  
  121.           case MotionEvent.ACTION_DOWN:  
  122.                isSliding = true;  
  123.                currentX = (int) event.getX();  
  124.                break;  
  125.   
  126.           case MotionEvent.ACTION_MOVE:  
  127.                currentX = (int) event.getX();  
  128.                Log.i("currentX", currentX + "");  
  129.   
  130.                break;  
  131.           case MotionEvent.ACTION_UP:  
  132.                isSliding = false;  
  133.                int bgCenter = switchBitMapWidth / 2;  
  134.                boolean state = currentX > bgCenter; // 改变后的状态  
  135.                if (state != currentState && mChangedListener != null) {// 添加回调  
  136.                     mChangedListener.onToggleStateChanged(state);  
  137.                }  
  138.                currentState = state;  
  139.                break;  
  140.           default:  
  141.                break;  
  142.           }  
  143.           invalidate();  
  144.           return true;  
  145.      }  
  146.   
  147.      public OnToggleStateChangedListener getmChangedListener() {  
  148.           return mChangedListener;  
  149.      }  
  150.   
  151.      public void setmChangedListener(  
  152.                OnToggleStateChangedListener mChangedListener) {  
  153.           this.mChangedListener = mChangedListener;  
  154.      }  
  155.   
  156.      public boolean isToggleState() {  
  157.           return currentState;  
  158.      }  
  159.   
  160.      public void setToggleState(boolean currentState) {  
  161.           this.currentState = currentState;  
  162.      }  
  163.   
  164. }  
  回调接口,
[html]  view plain  copy
  1.  package com.kince.slidebutton;  
  2.   
  3. **  
  4. * @author kince  
  5. *  
  6. */  
  7. ublic interface OnToggleStateChangedListener {  
  8.   
  9.     /**  
  10.      * @category  
  11.      * @param state  
  12.      */  
  13.     public void onToggleStateChanged(boolean state);  
  Activity代码,
[html]  view plain  copy
  1. package com.kince.slidebutton;  
  2.   
  3. import android.support.v7.app.ActionBarActivity;  
  4. import android.support.v7.app.ActionBar;  
  5. import android.support.v4.app.Fragment;  
  6. import android.support.v4.app.FragmentActivity;  
  7. import android.os.Bundle;  
  8. import android.view.LayoutInflater;  
  9. import android.view.Menu;  
  10. import android.view.MenuItem;  
  11. import android.view.View;  
  12. import android.view.ViewGroup;  
  13. import android.widget.Toast;  
  14. import android.os.Build;  
  15.   
  16. public class MainActivity extends ActionBarActivity {  
  17.   
  18.      @Override  
  19.      protected void onCreate(Bundle savedInstanceState) {  
  20.           super.onCreate(savedInstanceState);  
  21.           setContentView(R.layout.activity_main);  
  22.   
  23.           if (savedInstanceState == null) {  
  24.                getSupportFragmentManager().beginTransaction()  
  25.                          .add(R.id.container, new PlaceholderFragment()).commit();  
  26.           }  
  27.      }  
  28.   
  29.      @Override  
  30.      public boolean onCreateOptionsMenu(Menu menu) {  
  31.   
  32.           // Inflate the menu; this adds items to the action bar if it is present.  
  33.           getMenuInflater().inflate(R.menu.main, menu);  
  34.           return true;  
  35.      }  
  36.   
  37.      @Override  
  38.      public boolean onOptionsItemSelected(MenuItem item) {  
  39.           // Handle action bar item clicks here. The action bar will  
  40.           // automatically handle clicks on the Home/Up button, so long  
  41.           // as you specify a parent activity in AndroidManifest.xml.  
  42.           int id = item.getItemId();  
  43.           if (id == R.id.action_settings) {  
  44.                return true;  
  45.           }  
  46.           return super.onOptionsItemSelected(item);  
  47.      }  
  48.   
  49.      /**  
  50.      * A placeholder fragment containing a simple view.  
  51.      */  
  52.      public static class PlaceholderFragment extends Fragment implements  
  53.                OnToggleStateChangedListener {  
  54.   
  55.           private SlideButton slidebutton;  
  56.   
  57.           public PlaceholderFragment() {  
  58.           }  
  59.   
  60.           @Override  
  61.           public View onCreateView(LayoutInflater inflater, ViewGroup container,  
  62.                     Bundle savedInstanceState) {  
  63.                View rootView = inflater.inflate(R.layout.fragment_main, container,  
  64.                          false);  
  65.                slidebutton = (SlideButton) rootView.findViewById(R.id.slidebutton1);  
  66.                // 设置一下开关的状态  
  67.                slidebutton.setToggleState(true); // 设置开关的状态为打开  
  68.                slidebutton.setmChangedListener(this);  
  69.                return rootView;  
  70.           }  
  71.   
  72.           @Override  
  73.           public void onToggleStateChanged(boolean state) {  
  74.                // TODO Auto-generated method stub  
  75.                FragmentActivity activity = getActivity();  
  76.                if (state) {  
  77.                     Toast.makeText(activity, "开关打开", 0).show();  
  78.                } else {  
  79.                     Toast.makeText(activity, "开关关闭", 0).show();  
  80.                }  
  81.           }  
  82.      }  
  83.   
  84. }  
  未完待续。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值