1. 程式人生 > >Android自定義控制元件之實現滑動選擇開關

Android自定義控制元件之實現滑動選擇開關

前言:今天我們仿照著Google給我們提供的Switch控制元件來進行一次模仿,自己動手打造一個可以換滑動圖片以及背景的圖片。

-----------------分割線---------------

先看一下google提供的Switc控制元件:


其實用法很簡單就當普通的控制元件使用即可!

    <Switch
        android:id="@+id/switch1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Switch" />

-----------------分割線---------------

先來看下我們自定義的效果:


-----------------分割線---------------

其實自定義控制元件的套路都差不多,都需要onMeasure -> onLayout -> onDraw,然後在裡面寫一個介面回撥,把狀態傳遞給監聽者。如何配置自定義屬性請看我的另外一篇部落格《Android自定義組合控制元件之實現CheckBox變化》在這裡重複的程式碼就不囉嗦了,後面給出完整程式碼的下載連結!

注意點:

1.我們可以依據背景圖片來確定控制元件大小。

2.繪製滑塊的時候一定要判斷滑塊滑動的範圍。

3.在onTouch裡面呼叫invalidate()可引起onDraw重繪,頁面會更新。

4.在onTouch裡面返回true,表示消費了使用者的點選事件,可收到其他的事件。

其他的可以想看程式碼裡面的註解。

-----------------分割線---------------

程式碼擼去:

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

/**
 * 自定義滑動選擇開關
 *
 */
public class SlideSwitchView extends View {

    private Bitmap switchBackgroupBitmap; // 背景圖片
    private Bitmap slideButtonBitmap; // 滑塊圖片
    private Paint paint; // 畫筆
    private boolean mSwitchState = false; // 開關狀態, 預設false
    private float currentX;

    /**
     * 用於程式碼建立控制元件
     *
     * @param context
     */
    public SlideSwitchView(Context context) {
        super(context);
        init();
    }

    /**
     * 用於在xml裡使用, 可指定自定義屬性
     *
     * @param context
     * @param attrs
     */
    public SlideSwitchView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
        // 獲取配置的自定義屬性
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SlideSwitchView);
//        int switchBackgroundResource = ta.getResourceId(R.styleable.SlideSwitchView_switch_background, R.drawable.switch_background);//可以設定預設圖片
        int switchBackgroundResource = ta.getResourceId(R.styleable.SlideSwitchView_switch_background, -1);
        int slideButtonResource = ta.getResourceId(R.styleable.SlideSwitchView_slide_button, -1);
        mSwitchState = ta.getBoolean(R.styleable.SlideSwitchView_switch_state, false);
        setSwitchBackgroundResource(switchBackgroundResource);
        setSlideButtonResource(slideButtonResource);
    }

    /**
     * 用於在xml裡使用, 可指定自定義屬性, 如果指定了樣式, 則走此建構函式
     *
     * @param context
     * @param attrs
     * @param defStyle
     */
    public SlideSwitchView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        paint = new Paint();
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 依據背景圖片來確定控制元件大小
        setMeasuredDimension(switchBackgroupBitmap.getWidth(), switchBackgroupBitmap.getHeight());
    }

    // Canvas 畫布, 畫板. 在上邊繪製的內容都會顯示到介面上.
    @Override
    protected void onDraw(Canvas canvas) {
        // 1. 繪製背景
        canvas.drawBitmap(switchBackgroupBitmap, 0, 0, paint);
        // 2. 繪製滑塊
        if (isTouchMode) {
            // 根據當前使用者觸控到的位置畫滑塊
            // 讓滑塊向左移動自身一半大小的位置
            float newLeft = currentX - slideButtonBitmap.getWidth() / 2.0f;
            int maxLeft = switchBackgroupBitmap.getWidth() - slideButtonBitmap.getWidth();

            // 限定滑塊範圍
            if (newLeft < 0) {
                newLeft = 0; // 左邊範圍
            } else if (newLeft > maxLeft) {
                newLeft = maxLeft; // 右邊範圍
            }
            canvas.drawBitmap(slideButtonBitmap, newLeft, 0, paint);
        } else {
            // 根據開關狀態boolean, 直接設定圖片位置
            if (mSwitchState) {// 開
                int newLeft = switchBackgroupBitmap.getWidth() - slideButtonBitmap.getWidth();
                canvas.drawBitmap(slideButtonBitmap, newLeft, 0, paint);
            } else {// 關
                canvas.drawBitmap(slideButtonBitmap, 0, 0, paint);
            }
        }

    }

    boolean isTouchMode = false;
    private OnSwitchStateUpdateListener onSwitchStateUpdateListener;

    // 重寫觸控事件, 響應使用者的觸控.
    @Override
    public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                isTouchMode = true;
                currentX = event.getX();
                break;
            case MotionEvent.ACTION_MOVE:
                currentX = event.getX();
                break;
            case MotionEvent.ACTION_UP:
                isTouchMode = false;
                currentX = event.getX();

                float center = switchBackgroupBitmap.getWidth() / 2.0f;

                // 根據當前按下的位置, 和控制元件中心的位置進行比較.
                boolean state = currentX > center;

                // 如果開關狀態變化了, 通知介面. 裡邊開關狀態更新了.
                if (state != mSwitchState && onSwitchStateUpdateListener != null) {
                    // 把最新的boolean, 狀態傳出去了
                    onSwitchStateUpdateListener.onStateUpdate(state);
                }

                mSwitchState = state;
                break;
        }

        // 重繪介面
        invalidate(); // 會引發onDraw()被呼叫, 裡邊的變數會重新生效.介面會更新

        return true; // 消費了使用者的觸控事件, 才可以收到其他的事件.
    }

    /**
     * 設定背景圖
     *
     * @param switchBackground
     */
    public void setSwitchBackgroundResource(int switchBackground) {
        if (switchBackground > 0) {
            switchBackgroupBitmap = BitmapFactory.decodeResource(getResources(), switchBackground);
        } else {
            Log.e("---SlideSwitchView--->", "沒有設定開關背景圖,應設定app:switch_background=\"@drawable/xxx\"");
        }
    }

    /**
     * 設定滑塊圖片資源
     *
     * @param slideButton
     */
    public void setSlideButtonResource(int slideButton) {
        if (slideButton > 0) {
            slideButtonBitmap = BitmapFactory.decodeResource(getResources(), slideButton);
        } else {
            Log.e("---SlideSwitchView--->", "沒有設定滑動按鈕圖,應設定app:slide_button=\"@drawable/xxx\"");
        }
    }

    /**
     * 設定開關狀態
     */
    public void setSwitchState(boolean mSwitchState) {
        this.mSwitchState = mSwitchState;
    }

    public interface OnSwitchStateUpdateListener {
        // 狀態回撥, 把當前狀態傳出去
        void onStateUpdate(boolean state);
    }

    public void setOnSwitchStateUpdateListener(OnSwitchStateUpdateListener onSwitchStateUpdateListener) {
        this.onSwitchStateUpdateListener = onSwitchStateUpdateListener;
    }

}
-----------------完整程式碼下載以及圖片資源---------------