菜鳥日記:
之前嘗試去了解繼承View實現自定義視圖控件,學習了通過代碼確實可以繪製一些:字符,幾何圖形。但是正真開發中我們可能做不到精確繪製圖形。想想工作量就很頭大。所以通過美術圖片來實現控件的內容是很明智便捷的達到目的的好辦法。現在我們就來看看用位圖資源來實現一個開關按鈕。
聲明:全部內容摘自互聯網
第一步:創建一個類(mSwitchButton)繼承CheckBox
/**
* 自定義一個支持滑動和動畫的開關按鈕(源碼來自互聯網)
* 1.繼承view及其子類
* 2.重寫onXX()函數
* 3.創建一個執行:開關按鈕的動畫效果執行內部類
* 4.創建一個執行:框架動畫控制器類(UI更新)
*
* @author xxx Zhang
*/
public class mSwitchButton extends CheckBox {
//添加構造器
//重寫onMeasure()、onDraw()、onTouchEvent()
}
checkBox類是複選框按鈕。那麼我們做開關按鈕爲什麼要繼承他呢?
首先他是View的子類這個大家都知道,但是你應該也要知道他是android.widget.CompoundButton直接子類,而他有實現Checkable接口:複選
說這麼多其實就是想讓大家明白:CompoundButton抽象類的子類具有:選中狀態的方法。
所以做開關按鈕便捷的做法你可以繼承:CheckBox, RadioButton, ToggleButton
第二步:繪製開關按鈕
//解碼圖像引用的資源ID,初始化位圖對象。
Resources resources = context.getResources();
Bitmap mMask = BitmapFactory.decodeResource(resources, R.drawable.mask);//底層:黑色
Bitmap mFrame = BitmapFactory.decodeResource(resources, R.drawable.frame);//框架:白色
Bitmap mBottom = BitmapFactory.decodeResource(resources, R.drawable.bottom);//按鈕:兩個狀態(開和關)
Bitmap mBtnNormal = BitmapFactory.decodeResource(resources, R.drawable.btn_unpressed);//滑動原點:常規
Bitmap mBtnPressed = BitmapFactory.decodeResource(resources, R.drawable.btn_pressed);//滑動原點:按住
現在我們來思考:支持滑動的開關按鈕原點圖片是要根據事件切換的,開關狀態按鈕是要根據事件切換不同的顯示狀態的
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO 第一步:自定義視圖控件的大小尺寸(寬高),缺省時,系統提供一個100*100的尺寸
//初始化視圖的尺寸(寬高),以Mask底層位圖
mMaskWidth = mMask.getWidth();
mMaskHeight = mMask.getHeight();
//增大點擊區域
final float density = getResources().getDisplayMetrics().density;//手機屏幕密度
//mVelocity = (int) (VELOCITY * density + 0.5f);//?
mExtendOffsetY = (int) (EXTENDED_OFFSET_Y * density + 0.5f);//Y軸方向擴大
setMeasuredDimension((int) mMaskWidth, (int) (mMaskHeight + 2 * mExtendOffsetY));
}
2.重寫onDraw() @Override
protected void onDraw(Canvas canvas) {
// TODO 第二步:自定義視圖控件的內容
/*爲了方便一些轉換操作,Canvas提供了保存和回滾屬性的方法(save和restore)*/
/* saveLayerAlpha和save方法差不多,但是它單獨分配了一個畫布用於繪製圖層
* 此方法之後的所有繪製都在此區域中繪製
* bounds-畫板最大的尺寸(封裝成一個矩形)
* 參考:http://www.haodaima.net/art/2551186
*/
canvas.saveLayerAlpha(mSaveLayerRectF, mAlpha,LAYER_FLAGS );
//第一張圖層:繪製圖層:位圖(你可以是別的內容:矩形、路徑、文本、位圖)
canvas.drawBitmap(mMask, 0, mExtendOffsetY, mPaint);// 位圖、左邊位置、頂部位置、畫筆
//處理兩張圖層的處理模式:http://trylovecatch.iteye.com/blog/1189452
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
//第二張圖層: 開關按鈕
canvas.drawBitmap(mBottom, mRealPos, mExtendOffsetY, mPaint);//開關按鈕默認在
mPaint.setXfermode(null);
//第三張:繪製邊框
canvas.drawBitmap(mFrame, 0, mExtendOffsetY, mPaint);
// 第四張:滑動原點
canvas.drawBitmap(mCurBtnPic, mRealPos, mExtendOffsetY, mPaint);//滑動原點默認在關閉狀態的位置
//回滾:回到上一個save調用之前的狀態,如果restore調用的次數大於save方法,會出錯。
canvas.restore();
}
在繪製圖形思路時,我們講的有些位圖對象是根據事件切換的,因此我們要設置變量對象,
第三步:監聽自定義控件的觸屏事件
1.重寫onTouchEvent()事件函數
/**
* android.View.onTouchEvent:
* 實現這個方法來處理觸摸屏運動事件。
* http://blog.csdn.net/android_tutor/article/details/7193090
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
//mChecked = isChecked();//重新獲取按鈕的選中狀態
//
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
//向下動作
//attemptClaimDrag();//申明監聽事件可用
mCurBtnPic = mBtnPressed;//重新定義蒙板上的位圖對象(動原點:按住狀態)
break;
case MotionEvent.ACTION_MOVE:
//移動動作
//動畫效果
break;
case MotionEvent.ACTION_UP:
//向上動作
mCurBtnPic = mBtnNormal;//重新定義蒙板上的位圖對象(動原點:默認狀態)
/*執行按鈕的動畫效果*/
//....
break;
case MotionEvent.ACTION_CANCEL:
//取消動作
}
//請求重繪View樹,即onDraw()過程
invalidate();
return isEnabled();
}
2.實現動畫效果
/**
* 開關按鈕的動畫效果執行內部類
* @author xxx Zhang
*/
public static final int ANIMATION_FRAME_DURATION = 1000 / 60;
private final class SwitchAnimation implements Runnable {
@Override
public void run() {
if (!mAnimating) {
return;
}
//
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);
FrameAnimationController.requestAnimationFrame(this);
}
}
/**
* 移動位置
* @param position 按鈕停留到什麼位置(視圖尺寸的X抽)
*/
private void moveView(float position) {
mBtnPos = position;
mRealPos = getRealPos(mBtnPos);
invalidate();
} /**
* android.view.View.performClick
* 使用代碼主動去調用控件的點擊事件(模擬人手去觸摸控件)
*/
@Override
public boolean performClick() {
startAnimation(!mChecked);//啓動動畫
return true;
}
/**
* android.view.View.startAnimation
* 開始畫指定的內容
* @param turnOn
*/
private void startAnimation(boolean turnOn) {
Log.i(TAG, "//startAnimation:開始跟新按鈕視圖內容");
mAnimating = true;//動畫標識符號
mAnimatedVelocity = turnOn ? -mVelocity : mVelocity;//移動速度,可以爲負值
mAnimationPosition = mBtnPos;//按鈕的位置
<span style="color:#ff0000;">new SwitchAnimation().run();//開關按鈕的動畫效果執行內部類</span>
}
/**
* 結束畫
*/
private void stopAnimation() {
Log.i(TAG, "//stopAnimation");
mAnimating = false;
}
請注意:startAnimation()方法裏我們要給兩個變量賦值:移動速度和按鈕位置(在這裏我們該叫我動畫位置,因爲這個mBtnPos自己也是變量。在滑動效果中我們要在用到)。所以這樣的寫方法解耦程度比較好。
/**
* 開關按鈕的動畫效果執行內部類
* @author xxx Zhang
*/
public static final int ANIMATION_FRAME_DURATION = 1000 / 60;
private final class SwitchAnimation implements Runnable {
@Override
public void run() {
if (!mAnimating) {
return;
}
//計算移動的速度,負值就反向移動
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);
FrameAnimationController.requestAnimationFrame(this);//UI更新
}
}
/**
* 移動位置
* @param position 按鈕停留到什麼位置(視圖尺寸的X抽)
*/
private void moveView(float position) {
mBtnPos = position;
mRealPos = getRealPos(mBtnPos);
invalidate();
}
/**
* 框架動畫控制器
* @author xxx Zhang
*
*/
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;
}
}
}
}
按鈕動畫效果的代碼就基本完成。值得注意的:關於在觸屏事件ACTION_UP值調用performClick()函數調用的問題。你應該做有效的判斷用戶的操作是否重複。
第三步:滑動效果
case MotionEvent.ACTION_MOVE:
//移動動作
Log.i(TAG, "//onTouchEvent:移動動作");
/*執行按鈕滑動效果*/
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;