1. 程式人生 > >android開發筆記之自定義開關按鈕

android開發筆記之自定義開關按鈕

今天來講講自定義單個控制元件,就拿開關按鈕來講講,相信大家見了非常多這樣的了,先看看效果:

這裡寫圖片描述

我們可以看到一個很常見的開關按鈕,那就來分析分析。

首先:

這是由兩張圖片構成:

①一張為有開和關的背景圖片

②一張為控制開和關的滑動按鈕

第一步:

寫個類繼承View,並重寫幾個方法:

第一個為建構函式,重寫一個引數的函式和兩個引數的函式就夠了,因為兩個引數的函式能夠使用自定義屬性

第二個為控制控制元件的大小–>protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {}

第三個為繪製控制元件的方法–>protected void onDraw(Canvas canvas) {}

第二步:

將使用者指定的兩張圖片載入進來,這裡使用自定義屬性載入, 在values目錄下新建attrs.xml,在xml檔案中指定自定義屬性名和自定義屬性的欄位及值型別(即背景圖和滑塊圖)即可:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="switchView_attrs">
        <attr name="background" format="reference"></attr>
        <attr
name="slide" format="reference">
</attr> </declare-styleable> </resources>

各個欄位的含義我在這就不講了,不懂的就去看看前幾篇《android開發筆記之自定義組合控制元件》有講過,寫好就只需在建構函式中載入進來

public SwitchView(Context context, AttributeSet attrs) {
        super(context, attrs);
        //拿到自定義屬性
        TypedArray ta = context.obtainStyledAttributes
(attrs, R.styleable.switchView_attrs); //拿到自定義欄位的值 Drawable switchBackground = ta.getDrawable(R.styleable.switchView_attrs_background); Drawable switchView_slide = ta.getDrawable(R.styleable.switchView_attrs_slide); //把值設定到相應元件上 backgroundBitmap = convertDrawable2BitmapSimple(switchBackground); switchSlide = convertDrawable2BitmapSimple(switchView_slide); }

不過要注意的是:

因為從自定義屬性中取到的是Drawable物件,而我們要的是一個Bitmap物件,所以我們先得把Drawable物件轉成Bitmap物件,其實有很多種方法來轉,這裡就介紹種最簡單的方法,藉助與BitmapDrawable類:

//將Drawable轉成Bitmap
    public Bitmap convertDrawable2BitmapSimple(Drawable drawable) {
        BitmapDrawable bd= (BitmapDrawable)drawable;
        return bd.getBitmap();
    }

第三步:

onMeasure方法來控制控制元件的大小,我們可以看到這個開關按鈕的大小就跟背景的大小一樣大,只需要設定為背景的大小:

//控制控制元件的大小
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (backgroundBitmap != null) {
            //控制元件大小設定為背景的大小
            setMeasuredDimension(backgroundBitmap.getWidth(), backgroundBitmap.getHeight());
        }else {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }

第四步:

既然這個開關按鈕需要通過點選或移動來控制控制元件的開和關,所以就需要實現onTouchEvent方法,當然應該有三個事件會觸發:按下、移動和擡起的時候,每次點選、移動或擡起都需要重繪,我們先來分析下滑塊的狀態有哪些(應該有四種狀態)一開始預設狀態為空:

1.點選的時候
2.移動的時候
3.擡起的時候
4.空的時候(即什麼都沒幹的時候)

先分析下點選的時候的情況:

①當按下或移動的座標大於滑塊寬度一半時將滑塊右移

②當按下或移動的座標小於滑塊寬度一半時滑塊不動

注:防止滑塊移至背景外面,最大是滑塊右邊和背景右邊對齊(即最大離左邊為背景寬度-滑塊寬度)

這裡寫圖片描述

再來看看移動的時候的情況應該是和點選的時候是一樣的。

再來分析擡起的時候的情況:

①如果開關狀態是開啟的就將滑塊移動至右邊

②如果開關狀態是關閉的就將滑塊移動至左邊

那怎麼判斷什麼時候是開啟狀態和關閉狀態呢??

①擡起的座標大於背景寬度一半的時候設為開啟狀態

②擡起的座標小於背景寬度座標一 半的時候設為關閉狀態

這裡寫圖片描述

再來分析下空的時候,可以發現它和擡起的時候的情況是一樣的。

第五步:

onDraw方法中將背景和滑塊繪製出來。剛才分析了onTouchEvent方法,這次是一樣的,滑塊的四個狀態分別處理,前面onTouchEvent方法中滑塊的狀態改變,然後通過invalidate()方法來通知系統重繪。

第六步:

我們做這個自定義控制元件是為了讓使用者使用的,現在這個是沒有什麼用的,使用者用不了,所以可以通過設定監聽器來對外提供介面。

/** 
     * switchView開關監聽介面
     *  
     *  */
    interface OnSwitchChangedListener {
        public void onSwitchChange(boolean isOpen);
    }
    /** 
     * 設定 switchView狀態監聽 
     * */
    public void setOnChangeListener(OnSwitchChangedListener listener) {
        switchListener = listener;
    }

這個監聽器中的boolean值需要賦值,那在什麼時候賦值呢,應該是在擡起或空的狀態的時候給它賦值,因為那個時候才真正確定開關按鈕是開啟的還是關閉的。

第七步:

到這一步就是來使用了,在佈局檔案中把自定義的這個控制元件定義出來

<com.example.custom.SwitchView
        minguo:background="@drawable/switch_background"
        minguo:slide="@drawable/slide_button_background"
        android:id="@+id/switchView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

這裡寫圖片描述

使用定義好的View應該都會用了,不會的去看看《android開發筆記之自定義組合控制元件》。

核心程式碼:

佈局檔案

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:minguo="http://schemas.android.com/apk/res/com.example.custom"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.custom.MainActivity" >
    <com.example.custom.SwitchView
        minguo:background="@drawable/switch_background"
        minguo:slide="@drawable/slide_button_background"
        android:id="@+id/switchView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</LinearLayout>

自定義View: SwitchView.java

/**
 * 自定義開關按鈕
 * @author Administrator
 *
 */
public class SwitchView extends View {
    //背景圖片和滑塊圖片
    private Bitmap backgroundBitmap,switchSlide;
    //畫筆
    private Paint paint;
    //得到的x座標(點選、移動、擡起)
    private float currentX;
    //判斷開關是否開啟的標記位
    private boolean isOpen = false;
    //開關開啟與關閉的監聽器
    private OnSwitchChangedListener switchListener;
    //滑塊的四種狀態
    public static final int STATE_DOWN = 1; //按下的時候
    public static final int STATE_MOVE = 2; //移動的時候
    public static final int STATE_UP = 3;   //擡起的時候
    public static final int STATE_NONE = 0; //空的時候(即什麼都沒幹的時候)
    //標記狀態(預設為空狀態)
    private int state = STATE_NONE;
    public SwitchView(Context context) {
        super(context,null);
    }
    public SwitchView(Context context, AttributeSet attrs) {
        super(context, attrs);
        //拿到自定義屬性
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.switchView_attrs);
        //拿到自定義欄位的值
        Drawable switchBackground = ta.getDrawable(R.styleable.switchView_attrs_background);
        Drawable switchView_slide = ta.getDrawable(R.styleable.switchView_attrs_slide);
        //把值設定到相應元件上
        backgroundBitmap = convertDrawable2BitmapSimple(switchBackground);
        switchSlide = convertDrawable2BitmapSimple(switchView_slide);
    }
    //將Drawable轉成Bitmap
    public Bitmap convertDrawable2BitmapSimple(Drawable drawable) {
        BitmapDrawable bd = (BitmapDrawable)drawable;
        return bd.getBitmap();
    }
    //控制控制元件的大小
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (backgroundBitmap != null) {
            //控制元件大小設定為背景的大小
            setMeasuredDimension(backgroundBitmap.getWidth(), backgroundBitmap.getHeight());
        }else {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }
    //繪製控制元件的樣式
    @Override
    protected void onDraw(Canvas canvas) {
        //背景為空的時候才將背景繪製
        if (backgroundBitmap != null) {
            paint = new Paint();
            //第一個引數表示需要畫的Bitmap
            //第二個引數表示Bitmap左邊離控制元件的左邊距
            //第三個引數表示Bitmap上邊離控制元件的上邊距
            //第四個引數表示畫筆
            canvas.drawBitmap(backgroundBitmap, 0, 0, paint);
        }
        switch (state) {
        case STATE_DOWN:        //按下和移動的觸發事件都一樣,都是將滑塊移動
        case STATE_MOVE:
            //當按下或移動的座標大於滑塊寬度一半時將滑塊右移
            if (currentX > switchSlide.getWidth()/2f) {
                //讓滑塊向右滑動(重新繪製滑塊的位置)
                float left = currentX - switchSlide.getWidth()/2f;
                //防止滑塊移至背景外面,最大是滑塊右邊和背景右邊對齊(即最大離左邊為背景寬度-滑塊寬度)
                float maxLeft = backgroundBitmap.getWidth() - switchSlide.getWidth();
                if (left > maxLeft) {
                    left = maxLeft;
                }
                canvas.drawBitmap(switchSlide, left, 0, paint);
            //當按下或移動的座標小於滑塊寬度一半時滑塊不動
            }else if (currentX < switchSlide.getWidth()/2f) {
                //讓滑塊不動就可以了
                canvas.drawBitmap(switchSlide, 0, 0, paint);
            }
            break;
        case STATE_NONE:        //空或擡起的時候將滑塊至於左邊或右邊
        case STATE_UP:
            //如果是開啟的將滑塊移動至右邊,並將開啟狀態傳至監聽器
            if (isOpen) {
                if (switchListener != null) {
                    switchListener.onSwitchChange(true);
                }
                canvas.drawBitmap(switchSlide, 
                        backgroundBitmap.getWidth() - switchSlide.getWidth(), 0, paint);
            //如果是關閉的將滑塊至於左邊,並將關閉狀態傳至監聽器
            }else {
                if (switchListener != null) {
                    switchListener.onSwitchChange(false);
                }
                canvas.drawBitmap(switchSlide, 0, 0, paint);
            }
            break;
        default:
            break;
        }
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            currentX = event.getX();
            //將標記位修改成按下的狀態
            state = STATE_DOWN;
            //通知系統重新繪製介面
            invalidate();//在主執行緒
//          postInvalidate();//在子執行緒
            break;
        case MotionEvent.ACTION_MOVE:
            currentX = event.getX();
            //將標記位修改為移動狀態
            state = STATE_MOVE;
            invalidate();
            break;
        case MotionEvent.ACTION_UP:
            currentX = event.getX();
            //將標記為修改為擡起狀態
            state = STATE_UP;
            //擡起的座標大於背景寬度一半的時候設為開啟狀態
            if (currentX > backgroundBitmap.getWidth()/2f) {
                //滑塊在右邊,開啟
                isOpen = true;
            //擡起的座標小於背景寬度座標一 半的時候設為關閉狀態
            }else if (currentX < backgroundBitmap.getWidth()) {
                //滑塊在左邊,關閉
                isOpen = false;

            }
            invalidate();
            break;
        }
        return true;
    }
    /** 
     * switchView開關監聽介面
     *  
     *  */
    interface OnSwitchChangedListener {
        public void onSwitchChange(boolean isOpen);
    }
    /** 
     * 設定 switchView狀態監聽 
     * */
    public void setOnChangeListener(OnSwitchChangedListener listener) {
        switchListener = listener;
    }
}

MainActivity.java

public class MainActivity extends Activity {

    private SwitchView switchView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        switchView = (SwitchView) findViewById(R.id.switchView);
        switchView.setOnChangeListener(new OnSwitchChangedListener() {
            @Override
            public void onSwitchChange(boolean isOpen) {
                if (isOpen) {
                    //開啟開關的時候的邏輯
                    Toast.makeText(MainActivity.this, "開關打開了", Toast.LENGTH_LONG).show();
                }else {
                    //關閉開關的時候的邏輯
                    Toast.makeText(MainActivity.this, "開關關閉了", Toast.LENGTH_LONG).show();
                }
            }
        });
    }
}

大家看起來這麼簡單的一個寫了這麼多,其實我們學習這個不是為了寫這個,比這個好的開源多的是,而是為了學習這種思路與思維。大家趕緊試試吧!