Android 自訂SwitchButton開關控制項

來源:互聯網
上載者:User

標籤:canvas   控制項   checkbox   android   

SwitchButton開關控制項早已經非常流行。有各種各樣的樣式,SwitchButton開關控制項一般用於app軟體佈建那裡,控制緩衝、聲音、提示、下載等等。是具有很好的UI體驗以及使用者的習慣性。那麼再下面介紹一個SwitchButton開關控制項。並附上源碼。

源碼下載:點擊

一、看實現的


二、自訂SwitchButton

這是一個繼承CheckBox的SwitchButton類。來實現做這些動畫效果的,首先準備好這些圖片,然後canvas繪製控制項 的邊框、背景、以及按鈕。繪製時候加上相應的圖片。然後用onTouchEvent這個函數,接受當按下,滑動,鬆開後的效果。那麼大概就出來了。接下來看具體的代碼。

package com.org.switchbtn;import com.switchbutton.activity.R;import android.content.Context;import android.content.res.Resources;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.PorterDuff;import android.graphics.PorterDuffXfermode;import android.graphics.RectF;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.ViewConfiguration;import android.view.ViewParent;import android.widget.CheckBox;public class SwitchButton extends CheckBox {    private Paint mPaint;    private ViewParent mParent;    private Bitmap mBottom;    private Bitmap mCurBtnPic;    private Bitmap mBtnPressed;    private Bitmap mBtnNormal;    private Bitmap mFrame;    private Bitmap mMask;    private RectF mSaveLayerRectF;    private PorterDuffXfermode mXfermode;    private float mFirstDownY; // 首次按下的Y    private float mFirstDownX; // 首次按下的X    private float mRealPos; // 圖片的繪製位置    private float mBtnPos; // 按鈕的位置    private float mBtnOnPos; // 開關開啟的位置    private float mBtnOffPos; // 開關關閉的位置    private float mMaskWidth;    private float mMaskHeight;    private float mBtnWidth;    private float mBtnInitPos;    private int mClickTimeout;    private int mTouchSlop;    private final int MAX_ALPHA = 255;    private int mAlpha = MAX_ALPHA;    private boolean mChecked = false;    private boolean mBroadcasting;    private boolean mTurningOn;    private PerformClick mPerformClick;    private OnCheckedChangeListener mOnCheckedChangeListener;    private OnCheckedChangeListener mOnCheckedChangeWidgetListener;    private boolean mAnimating;    private final float VELOCITY = 350;    private float mVelocity;    private final float EXTENDED_OFFSET_Y = 15;    private float mExtendOffsetY; // Y軸方向擴大的地區,增大點擊地區    private float mAnimationPosition;    private float mAnimatedVelocity;    public SwitchButton(Context context, AttributeSet attrs) {        this(context, attrs, android.R.attr.checkboxStyle);    }    public SwitchButton(Context context) {        this(context, null);    }    public SwitchButton(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        initView(context);    }    private void initView(Context context) {        mPaint = new Paint();        mPaint.setColor(Color.WHITE);        Resources resources = context.getResources();        // get viewConfiguration        mClickTimeout = ViewConfiguration.getPressedStateDuration()                + ViewConfiguration.getTapTimeout();        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();        // get Bitmap        mBottom = BitmapFactory.decodeResource(resources, R.drawable.bottom);        mBtnPressed = BitmapFactory.decodeResource(resources, R.drawable.btn_pressed);        mBtnNormal = BitmapFactory.decodeResource(resources, R.drawable.btn_unpressed);        mFrame = BitmapFactory.decodeResource(resources, R.drawable.frame);        mMask = BitmapFactory.decodeResource(resources, R.drawable.mask);        mCurBtnPic = mBtnNormal;        mBtnWidth = mBtnPressed.getWidth();        mMaskWidth = mMask.getWidth();        mMaskHeight = mMask.getHeight();        mBtnOffPos = mBtnWidth / 2;        mBtnOnPos = mMaskWidth - mBtnWidth / 2;        mBtnPos = mChecked ? mBtnOnPos : mBtnOffPos;        mRealPos = getRealPos(mBtnPos);        final float density = getResources().getDisplayMetrics().density;        mVelocity = (int) (VELOCITY * density + 0.5f);        mExtendOffsetY = (int) (EXTENDED_OFFSET_Y * density + 0.5f);        mSaveLayerRectF = new RectF(0, mExtendOffsetY, mMask.getWidth(), mMask.getHeight()                + mExtendOffsetY);        mXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);    }    @Override    public void setEnabled(boolean enabled) {        mAlpha = enabled ? MAX_ALPHA : MAX_ALPHA / 2;        super.setEnabled(enabled);    }    public boolean isChecked() {        return mChecked;    }    public void toggle() {        setChecked(!mChecked);    }    /**     * 內部調用此方法設定checked狀態,此方法會順延強制各種回呼函數,保證動畫的流暢度     *      * @param checked     */    private void setCheckedDelayed(final boolean checked) {        this.postDelayed(new Runnable() {            @Override            public void run() {                setChecked(checked);            }        }, 10);    }    /**     * <p>     * Changes the checked state of this button.     * </p>     *      * @param checked true to check the button, false to uncheck it     */    public void setChecked(boolean checked) {        if (mChecked != checked) {            mChecked = checked;            mBtnPos = checked ? mBtnOnPos : mBtnOffPos;            mRealPos = getRealPos(mBtnPos);            invalidate();            // Avoid infinite recursions if setChecked() is called from a            // listener            if (mBroadcasting) {                return;            }            mBroadcasting = true;            if (mOnCheckedChangeListener != null) {                mOnCheckedChangeListener.onCheckedChanged(SwitchButton.this, mChecked);            }            if (mOnCheckedChangeWidgetListener != null) {                mOnCheckedChangeWidgetListener.onCheckedChanged(SwitchButton.this, mChecked);            }            mBroadcasting = false;        }    }    /**     * Register a callback to be invoked when the checked state of this button     * changes.     *      * @param listener the callback to call on checked state change     */    public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {        mOnCheckedChangeListener = listener;    }    /**     * Register a callback to be invoked when the checked state of this button     * changes. This callback is used for internal purpose only.     *      * @param listener the callback to call on checked state change     * @hide     */    void setOnCheckedChangeWidgetListener(OnCheckedChangeListener listener) {        mOnCheckedChangeWidgetListener = listener;    }    @Override    public boolean onTouchEvent(MotionEvent event) {        int action = event.getAction();        float x = event.getX();        float y = event.getY();        float deltaX = Math.abs(x - mFirstDownX);        float deltaY = Math.abs(y - mFirstDownY);        switch (action) {            case MotionEvent.ACTION_DOWN:                attemptClaimDrag();                mFirstDownX = x;                mFirstDownY = y;                mCurBtnPic = mBtnPressed;                mBtnInitPos = mChecked ? mBtnOnPos : mBtnOffPos;                break;            case MotionEvent.ACTION_MOVE:                float time = event.getEventTime() - event.getDownTime();                mBtnPos = mBtnInitPos + event.getX() - mFirstDownX;                if (mBtnPos >= mBtnOffPos) {                    mBtnPos = mBtnOffPos;                }                if (mBtnPos <= mBtnOnPos) {                    mBtnPos = mBtnOnPos;                }                mTurningOn = mBtnPos > (mBtnOffPos - mBtnOnPos) / 2 + mBtnOnPos;                mRealPos = getRealPos(mBtnPos);                break;            case MotionEvent.ACTION_UP:                mCurBtnPic = mBtnNormal;                time = event.getEventTime() - event.getDownTime();                if (deltaY < mTouchSlop && deltaX < mTouchSlop && time < mClickTimeout) {                    if (mPerformClick == null) {                        mPerformClick = new PerformClick();                    }                    if (!post(mPerformClick)) {                        performClick();                    }                } else {                    startAnimation(!mTurningOn);                }                break;        }        invalidate();        return isEnabled();    }    private final class PerformClick implements Runnable {        public void run() {            performClick();        }    }    @Override    public boolean performClick() {        startAnimation(!mChecked);        return true;    }    /**     * Tries to claim the user's drag motion, and requests disallowing any     * ancestors from stealing events in the drag.     */    private void attemptClaimDrag() {        mParent = getParent();        if (mParent != null) {            mParent.requestDisallowInterceptTouchEvent(true);        }    }    /**     * 將btnPos轉換成RealPos     *      * @param btnPos     * @return     */    private float getRealPos(float btnPos) {        return btnPos - mBtnWidth / 2;    }    @Override    protected void onDraw(Canvas canvas) {        canvas.saveLayerAlpha(mSaveLayerRectF, mAlpha, Canvas.MATRIX_SAVE_FLAG                | Canvas.CLIP_SAVE_FLAG | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG                | Canvas.FULL_COLOR_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);        // 繪製蒙板        canvas.drawBitmap(mMask, 0, mExtendOffsetY, mPaint);        mPaint.setXfermode(mXfermode);        // 繪製底部圖片        canvas.drawBitmap(mBottom, mRealPos, mExtendOffsetY, mPaint);        mPaint.setXfermode(null);        // 繪製邊框        canvas.drawBitmap(mFrame, 0, mExtendOffsetY, mPaint);        // 繪製按鈕        canvas.drawBitmap(mCurBtnPic, mRealPos, mExtendOffsetY, mPaint);        canvas.restore();    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        setMeasuredDimension((int) mMaskWidth, (int) (mMaskHeight + 2 * mExtendOffsetY));    }    private void startAnimation(boolean turnOn) {        mAnimating = true;        mAnimatedVelocity = turnOn ? -mVelocity : mVelocity;        mAnimationPosition = mBtnPos;        new SwitchAnimation().run();    }    private void stopAnimation() {        mAnimating = false;    }    private final class SwitchAnimation implements Runnable {        @Override        public void run() {            if (!mAnimating) {                return;            }            doAnimation();            FrameAnimationController.requestAnimationFrame(this);        }    }    private void doAnimation() {        mAnimationPosition += mAnimatedVelocity * FrameAnimationController.ANIMATION_FRAME_DURATION                / 1000;        if (mAnimationPosition <= mBtnOnPos) {            stopAnimation();            mAnimationPosition = mBtnOnPos;            setCheckedDelayed(true);        } else if (mAnimationPosition >= mBtnOffPos) {            stopAnimation();            mAnimationPosition = mBtnOffPos;            setCheckedDelayed(false);        }        moveView(mAnimationPosition);    }    private void moveView(float position) {        mBtnPos = position;        mRealPos = getRealPos(mBtnPos);        invalidate();    }}

二、控制輔助類FrameAnimationController

package com.org.switchbtn;import android.os.Handler;import android.os.Message;public class FrameAnimationController {private static final int MSG_ANIMATE = 1000;public static final int ANIMATION_FRAME_DURATION = 1000 / 60;private static final Handler mHandler = new AnimationHandler();private FrameAnimationController() {throw new UnsupportedOperationException();}public static void requestAnimationFrame(Runnable runnable) {Message message = new Message();message.what = MSG_ANIMATE;message.obj = runnable;mHandler.sendMessageDelayed(message, ANIMATION_FRAME_DURATION);}public static void requestFrameDelay(Runnable runnable, long delay) {Message message = new Message();message.what = MSG_ANIMATE;message.obj = runnable;mHandler.sendMessageDelayed(message, delay);}private static class AnimationHandler extends Handler {public void handleMessage(Message m) {switch (m.what) {case MSG_ANIMATE:if (m.obj != null) {((Runnable) m.obj).run();}break;}}}}
三、看最後監聽,調用,實現MainActivity

因為自訂SwitchButton這個類是繼承checkbox。所以還是用這個介面OnCheckedChangeListener。用這個介面實現監聽。

package com.switchbutton.activity;import com.org.switchbtn.SwitchButton;import android.os.Bundle;import android.app.Activity;import android.widget.CompoundButton;import android.widget.CompoundButton.OnCheckedChangeListener;import android.widget.Toast;public class MainActivity extends Activity implements OnCheckedChangeListener{private SwitchButton mMsgNotifySwitch;private SwitchButton mMsgSoundSwitch;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initUI();}private void initUI() {mMsgNotifySwitch = (SwitchButton)findViewById(R.id.message_notify_switch);mMsgSoundSwitch = (SwitchButton)findViewById(R.id.message_sound_switch);mMsgNotifySwitch.setOnCheckedChangeListener(this);mMsgSoundSwitch.setOnCheckedChangeListener(this);}@Overridepublic void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {switch (buttonView.getId()) {case R.id.message_notify_switch:if (isChecked) {Toast.makeText(this, "新訊息提醒開啟", Toast.LENGTH_SHORT).show();}else {Toast.makeText(this, "新訊息提醒關閉", Toast.LENGTH_SHORT).show();}break;case R.id.message_sound_switch:if (isChecked) {Toast.makeText(this, "聲音提醒開啟", Toast.LENGTH_SHORT).show();}else {Toast.makeText(this, "聲音提醒關閉", Toast.LENGTH_SHORT).show();}break;default:break;}}}
四、附上最後的xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent">    <RelativeLayout        android:id="@+id/sound_and_vibrate"        android:layout_width="fill_parent"        android:layout_height="44.0dip"        android:background="@drawable/common_strip_setting_bottom"        android:clickable="false"        android:focusable="false" >        <TextView            style="@style/B4_Font_white"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_centerVertical="true"            android:layout_marginLeft="12.0dip"            android:duplicateParentState="true"            android:gravity="center_vertical"            android:text="@string/set_message_sound" />        <com.org.switchbtn.SwitchButton            android:id="@+id/message_sound_switch"            android:layout_width="80dip"            android:layout_height="28dip"            android:layout_alignParentRight="true"            android:layout_centerVertical="true"            android:layout_marginRight="8.0dip" />    </RelativeLayout>    <RelativeLayout        android:id="@+id/pushSetting"        android:layout_width="fill_parent"        android:layout_height="44.0dip"        android:layout_alignParentLeft="true"        android:layout_below="@+id/sound_and_vibrate"        android:background="@drawable/common_strip_setting_top"        android:clickable="false"        android:focusable="false" >        <TextView            android:id="@+id/encoding_style"            style="@style/B4_Font_white"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_centerVertical="true"            android:layout_marginLeft="12.0dip"            android:duplicateParentState="true"            android:gravity="center_vertical"            android:text="@string/set_message_notify" />        <com.org.switchbtn.SwitchButton            android:id="@+id/message_notify_switch"            android:layout_width="80dip"            android:layout_height="28dip"            android:layout_alignParentRight="true"            android:layout_centerVertical="true"            android:layout_marginRight="8.0dip" />    </RelativeLayout></RelativeLayout>
到這裡結束了,歡迎交流學習自訂控制項。

源碼下載:點擊

著作權聲明:本文為博主原創文章,未經博主允許不得轉載。

Android 自訂SwitchButton開關控制項

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.