【自定義視圖控件】實戰篇--支持滑動和動畫的開關按鈕(SwitcheButton)

菜鳥日記:

之前嘗試去了解繼承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 

第二步:繪製開關按鈕

首先我們要明白按鈕資源位圖相關參數是繪製這個按鈕尺寸和圖形的基本資源。所以我們應該在構造器對象裏面去初始化資源對象
那麼我們就需要準備美工我們提供好看的圖片資源。
:mask:bottom:frame
:btn_unpressedbtn_pressed
 //解碼圖像引用的資源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);//滑動原點:按住
 

現在我們來思考:支持滑動的開關按鈕原點圖片是要根據事件切換的,開關狀態按鈕是要根據事件切換不同的顯示狀態的
明白這一點,我們先就繪製一個靜態的開關按鈕應該應該是什麼樣的


1.重寫onMeasurs()
    @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();
    }
    

在繪製圖形思路時,我們講的有些位圖對象是根據事件切換的,因此我們要設置變量對象,
所以我們在定義變量對象:mBottom, mRealPos , mCurBtnPic

也許你你需要完整的代碼:mSwitchButton.java

第三步:監聽自定義控件的觸屏事件

如果沒有意外的話,完成上面的步驟能看到一個靜態開關按鈕。那麼我們要讓他從關閉狀態切換到開啓狀態。

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();
    }
上面的主要是告訴我們怎麼去重寫一個觸屏事件的函數,重新給mCurBtnPic變量賦值,會實現用戶觸屏摁住控件和放開控件的原點圖片的切換
或許你執行上面的步驟基本明白我們是怎麼去實現效果切換的:給mBottom, mRealPos , mCurBtnPic重新賦值。

注意:關於請求重繪視圖的函數問題:你可以主動請求重繪,請調用invalidate()函數。
invalidate()函數的主要作用是請求View樹進行重繪,該函數可以由應用程序調用,或者由系統函數間接調用,例如setEnable(), setSelected(), setVisiblity()都會間接調用到invalidate()來請求View樹重繪,更新View樹的顯示

2.實現動畫效果

功能表述:當用戶觸屏事件結束(手指離開屏幕)按鈕從開啓狀態緩慢的切換到關閉狀態。或者狀態反向切換
首先在自定義mSwitchButton01類重寫父類performClick()函數。我們可以在觸屏事件裏調用此函數就可以了。當然你也可以在觸屏事件函數裏直接startAnimation()方法
    /**
     * 開關按鈕的動畫效果執行內部類
     * @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()函數調用的問題。你應該做有效的判斷用戶的操作是否重複。

關於上面完整代碼下載:mSwitchButton_02.java

第三步:滑動效果

實現這個效果你可以在觸屏事件添加一下代碼
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;







發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章