標籤: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開關控制項