• 【转】Android SwitchButton(滑动开关)


    原文网址:http://blog.csdn.net/wangjinyu501/article/details/27961303

    版本:1.0
    日期:2014.5.17 2014.6.1
    版权:© 2014 kince 转载注明出处
     
      在介绍SwitchButton之前,先来看一下系统Button是如何实现的。源码如下:
    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    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。它是一个抽象类,而实现这个类的控件正是CheckBoxRadioButtonSwitchToggleButton这四个,所以先重点说一下它。源码如下:
      
    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    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<SavedStateCREATOR  
    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 plaincopy在CODE上查看代码片派生到我的代码片
     
    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 plaincopy在CODE上查看代码片派生到我的代码片
     
    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 plaincopy在CODE上查看代码片派生到我的代码片
     
    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 plaincopy在CODE上查看代码片派生到我的代码片
     
    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 plaincopy在CODE上查看代码片派生到我的代码片
     
    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 plaincopy在CODE上查看代码片派生到我的代码片
     
    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 plaincopy在CODE上查看代码片派生到我的代码片
     
    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 plaincopy在CODE上查看代码片派生到我的代码片
     
    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 plaincopy在CODE上查看代码片派生到我的代码片
     
    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 plaincopy在CODE上查看代码片派生到我的代码片
     
    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 plaincopy在CODE上查看代码片派生到我的代码片
     
    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. }  
      未完待续。
  • 相关阅读:
    shell编程-项目部署(优化篇)
    数据库相关
    python基础面试
    scrapy爬取数据进行数据库存储和本地存储
    C# 对字符串操 替换数字 替换非数字 去除首尾字符 长沙
    还在为删除集合中的相同项而烦恼吗?
    C#之Task&匿名方法
    如何在火狐里面实现如下功能
    valueOf和toString曾经欺骗过你吗?
    JS 实现Json查询方法
  • 原文地址:https://www.cnblogs.com/wi100sh/p/4290209.html
Copyright © 2020-2023  润新知