1. 程式人生 > >android自定義控制元件_完全自定義控制元件(自定義開關)

android自定義控制元件_完全自定義控制元件(自定義開關)

前面總結到自定義控制元件分為

  • 組合控制元件
  • 繼承已有控制元件 比如自定義SmartImageView繼承ImageView
  • 完全自定義控制元件

上一篇寫了自定義控制元件的自定義屬性深入理解點選連結檢視,是自定控制元件比較難以理解的地方,但是是很重要滴,是基礎。這一篇是完全自定義控制元件就是繼承基類View,實現一個開關控制元件的功能,會用到以下幾個知識點:
- 自定義屬性
- View的繪製流程
- View的事件處理
- 回撥函式

最後實現的開關效果如下:
這裡寫圖片描述

目錄

1 開關需求整體介紹

    開關是一個單純的控制元件view,不是已有控制元件的組合即組合控制元件,他實際上就是根據兩張圖繪製而成,下層是開關背景,上層是滑塊,繪製成view控制元件後動態的控制滑塊的位置就能實現開關開啟、關閉的效果,比如本文例子的材料就是2張圖,通過圖繪製而成的開關控制元件,圖一:
這裡寫圖片描述


圖二:
這裡寫圖片描述

佈局中引用控制元件MyToggleButton:

<com.yezhu.myapplication.togglebutton.MyToggleButton
            android:id="@+id/mtb"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            yezhu:slideBackGround="@drawable/slide_background"
            yezhu:slideIcon="@drawable/slide_icon"
/>

自定義屬性attrs:

 <!--自定義開關按鈕-->
    <declare-styleable name="MyToggleButton">
        <attr name="slideBackGround" format="reference" />
        <attr name="slideIcon" format="reference" />
        <attr name="state" format="boolean" />
    </declare-styleable>

拿到控制元件的屬性:

  public MyToggleButton(Context context) {
        this(context, null);
    }

    public MyToggleButton(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyToggleButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView(context, attrs);
    }

    private void initView(Context context, AttributeSet attrs) {
        //1.拿到控制元件的引用值
        TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.MyToggleButton);
        int resSlideBack = attributes.getResourceId(R.styleable.MyToggleButton_slideBackGround, 0);
        int resSlideIcon = attributes.getResourceId(R.styleable.MyToggleButton_slideIcon, 0);

        //2.根據引用值
        bitmapSlideBack = BitmapFactory.decodeResource(getResources(), resSlideBack);
        bitmapSlideIcon = BitmapFactory.decodeResource(getResources(), resSlideIcon);

        //3.拿到圖之後  根據圖繪製控制元件 呼叫onMeasure 測量 排版 繪製

        //4.走完onDraw後介面上已經能顯示控制元件 後設置控制元件的事件onTouchEvent
    }

拿到圖之後就要開始繪製控制元件,view的繪製流程是

  1. 測量onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法
  2. 排版
  3. 繪製 onDraw(Canvas canvas) 方法

似乎每一個過程展開講都能說繁多,view的繪製渲染過程很複雜還有諸多如view過度渲染的原因等面試題,本例子只是簡單的說明其流程。

2 畫開關的背景和滑塊

   想要繪製view首先要測量,我們拿到背景圖和滑塊圖2個bitmap,那開關的大小肯定是背景圖的大小,所以有:

  @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //根據值設定控制元件的大小
        setMeasuredDimension(bitmapSlideBack.getWidth(), bitmapSlideBack.getHeight());

    }

setMeasuredDimension()就指定了你要繪製的view控制元件的寬高。

   其次是排版,由於我們的基類是View,所以不需要排版。繼承ViewGroup時需要根據要求進行排版的,之後會有例子。

   再者是繪製,繪製是根據canvas畫布來實現的,當代碼走完這一步的時候我們在介面上就能看到真實的控制元件了,當然還沒有設定具體的事件監聽。

    @Override
    protected void onDraw(Canvas canvas) {

        canvas.drawBitmap(bitmapSlideBack, 0, 0, null);

        canvas.drawBitmap(bitmapSlideIcon, 30, 0, null);
    }

canvas.drawBitmap(bitmapSlideIcon, 30, 0, null);方法中有四個引數,引數1是根據哪個圖片繪製、引數2 距離左側的距離、引數3 距離頂部的距離、引數4 Paint畫筆是否需要。

   這裡我們把left寫死30,效果如下:顯然上層滑塊距初始位置右移30,也為我們動態控制開關提供了思路,只要在控制元件onTouchEvent(MotionEvent event)方法中根據手指的按下、移動、擡起動態的改變left值,再重新呼叫onDraw(Canvas canvas)就能實現開關的切換效果。
這裡寫圖片描述

3 實現開關的功能

   如上所述只要在控制元件onTouchEvent(MotionEvent event)方法中根據手指的按下、移動、擡起時動態的改變left值,再重新調onDraw(Canvas canvas)就能實現開關的切換效果。
   程式碼:

 @Override
    public boolean onTouchEvent(MotionEvent event) {

        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                //手指按下 上層icon距離左側的位置 最左側代表是關 最右側代表開
                //iconLeft 距離左側的距離 畫圖體會
                iconLeft = (int) event.getX() - bitmapSlideIcon.getWidth() / 2;

                break;

            case MotionEvent.ACTION_MOVE:
                //手指移動
                iconLeft = (int) event.getX() - bitmapSlideIcon.getWidth() / 2;

                break;

            case MotionEvent.ACTION_UP:
                //手指擡起 要是當前的手指擡起時的位置 已經是超過了背景的一半 鬆開手就是開啟開關
                // 要是手指擡起時(離開控制元件)的位置 尚不足背景的一半  鬆開手預設關閉開關

                if ((int) event.getX() > (int) bitmapSlideBack.getWidth() / 2) {
                    iconLeft = bitmapSlideBack.getWidth() - bitmapSlideIcon.getWidth();

                } else {
                    iconLeft = 0;
                }


                break;
        }

        invalidate();//呼叫這個方法 內部預設走一遍onDraw()方法

        // 設定成true 是指當前監聽自己消費此事件
        return true;
    }

需要解釋的是:

event.getX()可以獲取當前你點選的位置相對按鈕橫座標,還有一個getRowX()方法時獲取觸控點相對於螢幕的座標;

在手指按下和移動時滑塊距離左側的位置都是event.getX()-背景寬度的一半,單純的說特別難以理解,畫圖佐證一下:
這裡寫圖片描述

再者當擡起時要根據當前滑塊的位置判斷一下到底是關閉還是開啟,畫圖佐證:
這裡寫圖片描述
當擡起時位置超過了背景的一半就預設開啟,否則執行關閉。

invalidate();必須要呼叫,內部呼叫的是onDraw()方法。
返回值是true意味著此次事件由當前監聽自己消費掉。

4 開關回調

   在初學button時總是寫點選事件setOnClickListener(new OnOnClickListener ..),那麼我們這裡也可以模仿點選事件寫一個回撥函式,當控制元件開啟是toast提示或者做一些儲存開關標誌的操作。

關於回撥我自己的解釋是假設你寫一個函式是為了顯示當前控制元件的狀態, 當控制元件有了事件觸發的時候, 你寫的那個函式就會顯示改變之後的,就是回撥。這樣一想有沒有感覺好多了,再者有沒有感覺回撥好像只有一個觀察者的觀察者模式,當被觀察者發生變化時通知註冊了的觀察者做對應操作。

   那麼在onTouchEvent()方法中手指擡起時就可以這樣操作:

 case MotionEvent.ACTION_UP:
                //手指擡起 要是當前的手指擡起時的位置 已經是超過了背景的一半 鬆開手就是開啟開關
                // 要是手指擡起時(離開控制元件)的位置 尚不足背景的一半  鬆開手預設關閉開關

                if ((int) event.getX() > (int) bitmapSlideBack.getWidth() / 2) {
                    iconLeft = bitmapSlideBack.getWidth() - bitmapSlideIcon.getWidth();

                    isOpen = true;
                } else {
                    iconLeft = 0;

                    isOpen = false;
                }

                //手指擡起 開關開啟 介面toast 開關如何操作 ? 回撥 介面引用開關處 多一個監聽
                if (mListener != null) {
                    mListener.onToggleState(isOpen);
                }

                break;

   對於控制元件使用時:

  MyToggleButton myToggleButton = (MyToggleButton) findViewById(R.id.mtb);

        //做開關狀態監聽 回撥 開關開啟 吐司
        myToggleButton.setOnToggleStateListener(new MyToggleButton.OnToggleStateListener() {
            @SuppressLint("WrongConstant")
            @Override
            public void onToggleState(boolean state) {
                if (state) {
                    Toast.makeText(MainActivity.this, "開啟", Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(MainActivity.this, "關閉", Toast.LENGTH_SHORT).show();
                }

            }
        });

   以上就是全部的實現,希望能對大家對自定義控制元件時view繪製流程有一個大概認識。文章中如有敘述錯誤或不明確的地方請大傢俬信或留言指出。煩勞點個贊可好?