1. 程式人生 > >【自定義檢視控制元件】實戰篇--支援滑動和動畫的開關按鈕(SwitcheButton)

【自定義檢視控制元件】實戰篇--支援滑動和動畫的開關按鈕(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

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

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

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()函式呼叫的問題。你應該做有效的判斷使用者的操作是否重複。

第三步:滑動效果

實現這個效果你可以在觸屏事件新增一下程式碼
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;


相關推薦

定義檢視控制元件實戰--支援滑動動畫開關按鈕(SwitcheButton)

菜鳥日記: 之前嘗試去了解繼承View實現自定義檢視控制元件,學習了通過程式碼確實可以繪製一些:字元,幾何圖形。但是正真開發中我們可能做不到精確繪製圖形。想想工作量就很頭大。所以通過美術圖片來實現控制元件的內容是很明智便捷的達到目的的好辦法。現在我們就來看看用點陣圖資源來實

我的Android進階之旅Android定義電池控制元件

一、背景 最近公司有個業務,需要自定義一個電池控制元件,因此自己按照UI圖編寫了一個自定義View。 二、效果 三、實現過程 首先看下視覺給出的UI效果圖 從這裡我們可以看得出來,要自定義這個電池View的話,分為3部分來

QTQtcreator的設計模式中將控制元件提升為定義控制元件

測試環境 在工程中新增自定義的控制元件 如:MyWidget 進入設計模式,右鍵需要提升的控制元件(該控制元件的父類必須和自定義控制元件的父類相同,否則不能提升),選擇“提升為…”

WinForm中使用定義Tooltip控制元件

private ToolTip tooltipCtr; 建構函式中: 隱藏預設的Tooltip:this.ShowCellToolTips = false; this.tooltipCtr = new ToolTip(); 設定停留時間(還有許多其他時間設定):thi

定義控制元件 實現一個繞圓圈的箭頭

自定義的類 import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import

Android UI-定義日曆控制元件

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

定義titilbar控制元件

自定義titilbar控制元件 自定義佈局 <?xml version="1.0" encoding="utf-8"?> <ImageView android:id="@+id/imageView1" android:layout_width=

android開發:定義組合控制元件

內容介紹 本文記錄,自定義組合控制元件,為了可以程式碼複用,減少程式碼量 配置控制元件屬性檔案 開啟res/values/目錄下的arss.xml檔案,新增下面屬性程式碼,如果沒有建立arrs.xml檔案。 <?xml version="1.0" enc

android 多功能定義畫板控制元件(用於解決特定需求)

在專案中需要做一個可以自定義軌跡,但始終只有一條線,並且支援撤銷(撤銷單位為MotionEvent的down事件到up事件),還要支援動畫預覽等功能,最重要的是能夠按照間隔畫素來獲取所有點的座標,用於專案的其他功能。 整體的思路 1.專案中的應用場景需要畫板是一個圓形的,這個好實現用canv

(轉)C# 定義使用者控制元件

C# 自定義使用者控制元件   轉:https://blog.csdn.net/xiongxuanwen/article/details/2605109 本例是製作一個簡單的自定義控制元件,然後用一個簡單的測試程式,對於初學者來說,本例子比較簡單,只能起到拋石引玉的效

定義View控制元件

xml裡面的 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="

定義FlowLayout控制元件實現定義寬度並換行

最近的需求是實現新增購物車頁面,展示規格的時候會出現顯示不全,資料會自動剪下掉,後邊重新自定義了FlowLayout問題得到解決,下面直接上程式碼 public class FlowLayoutView extends ViewGroup { private final int DE

android-定義組合控制元件(EditText+選項)

一.前言 在開發中,或許一個業務需求中會出現很多系統控制元件組合成的佈局,並且經常需要複用。比如在一個表單中,裡面有個編輯框EditText右側還需要有個選項卡按鈕,需要有編輯框的輸入功能也需要有右側選項卡的點選事件,同時這兩個控制元件也存在一定關聯,且在一個介

定義控制元件 實現一個繞圓圈的箭頭

自定義的類 import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphic

定義UIPageControl 控制元件

                大家都見過iPhone上的那幾個小點點了。那就是iPhone用來控制翻頁的UIPageControl控制元件,但是許多人不會用UIPageControl,又不願意去看Apple的文件和例子。所以首先我們來講講這個控制元件的使用。1、新建專案UsingPageControl。刪除

Android定義複合控制元件

       在Android中,複合控制元件是非常常見的,下面以建立一個標題欄為例,講解建立自定義複合控制元件的過程。        以下圖為例:我們要建立一個標題欄,這個標題欄是由左邊的Button、右邊的Button以及中間的TextView複合而成的,而我們希望能夠

QML定義控制元件(TreeView 的style加上節點可拖動)

背景:          前段時間工作需要使用QML的TreeView,要通過拖動節點,對應節點執行對應的操作,查了很多的資料,沒有看到關於節點可拖動的資料,檢視TreeView的原始碼,貌似存在關於節點拖動的地方,但是始終沒有看到可以使用的介面,只好自己動手造輪子了

WPF程式設計之定義Button控制元件樣式

自.NET Framework 3.0 以後,WPF程式設計框架可使開發人員開發出更加令人耳目一新的桌面應用程式。它使開發工作更加方便快捷,它將設計人員和程式設計人員的工作分離開來。至於WPF的背景歷史、框架特點、框架結構這裡就不再贅述。有興趣的同袍可在百度搜索關於WPF的相

定義組合控制元件:Banner、輪播圖、廣告欄控制元件

1. 專案概述 這裡,我們使用自定義組合控制元件實現一個自動輪播的廣告條,也叫輪播圖,完整版的效果圖如下圖所示。其實,這就是我們經常見到的滾動廣告,預設情況下每隔N 秒會自動滾動,用手指左右滑動時也會切換到上一張或者下一張。當介面切換時,對應廣告圖片的標題也會

asp.net 定義伺服器控制元件屬性 [Bindable(true)]...

自定義伺服器控制元件屬性的特性:Bindable這個特性表示屬性是否可以繫結一個有效資料來源。通常使用布林值進行設定。例如:Bindable(true)。如果使用值true標記屬性,表示該屬性可以繫結一個有效資料來源,且應引發該屬性的屬性更改通知。Browsable指定屬性是否應該在屬性瀏覽器中顯示,使用布林