天天看點

Android完全自定義控件并且實作監聽事件

本篇文章帶來Android的完全自定義控件。載體是自定義一個開關的控件,并且能夠響應事件,首先我們先創一個項目,名字就叫ToggleView,修改MainActivity

public class MainActivity extends Activity {

    private ToggleView toggleView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        toggleView = (ToggleView) findViewById(R.id.toggleView);
//        toggleView.setSwitchBackgroundResource(R.drawable.switch_background);
//        toggleView.setSlideButtonResource(R.drawable.slide_button);
//        toggleView.setSwitchState(true);
//        
        // 設定開關更新監聽
        toggleView.setOnSwitchStateUpdateListener(new ToggleView.OnSwitchStateUpdateListener(){

            @Override
            public void onStateUpdate(boolean state) {
                Toast.makeText(getApplicationContext(), "state: " + state, Toast.LENGTH_SHORT).show();
            }

        });
    }

//  @Override
//  protected void onResume() {
//      super.onResume();
//  }
//    
}
           

寫一個繼承View的自定義控件的類ToggleView

/**
 * 自定義開關
 * @author poplar
 * 
 * Android 的界面繪制流程
 * 測量            擺放     繪制
 * measure  ->  layout  ->  draw
 *    |           |          |
 * onMeasure -> onLayout -> onDraw 重寫這些方法, 實作自定義控件
 * 
 * onResume()之後執行
 * 
 * View
 * onMeasure() (在這個方法裡指定自己的寬高) -> onDraw() (繪制自己的内容)
 * 
 * ViewGroup
 * onMeasure() (指定自己的寬高, 所有子View的寬高)-> onLayout() (擺放所有子View) -> onDraw() (繪制内容)
 */
public class ToggleView extends View {

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

    /**
     * 用于代碼建立控件
     * @param context
     */
    public ToggleView(Context context) {
        super(context);
        init();
    }

    /**
     * 用于在xml裡使用, 可指定自定義屬性
     * @param context
     * @param attrs
     */
    public ToggleView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
        // 擷取配置的自定義屬性
        String namespace = "http://schemas.android.com/apk/res-auto";
        int switchBackgroundResource = attrs.getAttributeResourceValue(namespace , "switch_background", -);
        int slideButtonResource = attrs.getAttributeResourceValue(namespace , "slide_button", -);

        mSwitchState = attrs.getAttributeBooleanValue(namespace, "switch_state", false);
        setSwitchBackgroundResource(switchBackgroundResource);
        setSlideButtonResource(slideButtonResource);
    }

    /**
     * 用于在xml裡使用, 可指定自定義屬性, 如果指定了樣式, 則走此構造函數
     * @param context
     * @param attrs
     * @param defStyle
     */
    public ToggleView(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, , , paint);

        // 2. 繪制滑塊

        if(isTouchMode){
            // 根據目前使用者觸摸到的位置畫滑塊

            // 讓滑塊向左移動自身一半大小的位置
            float newLeft = currentX - slideButtonBitmap.getWidth() / f;

            int maxLeft = switchBackgroupBitmap.getWidth() - slideButtonBitmap.getWidth();

            // 限定滑塊範圍
            if(newLeft < ){
                newLeft = ; // 左邊範圍
            }else if (newLeft > maxLeft) {
                newLeft = maxLeft; // 右邊範圍
            }

            canvas.drawBitmap(slideButtonBitmap, newLeft, , paint);
        }else {
            // 根據開關狀态boolean, 直接設定圖檔位置
            if(mSwitchState){// 開
                int newLeft = switchBackgroupBitmap.getWidth() - slideButtonBitmap.getWidth();
                canvas.drawBitmap(slideButtonBitmap, newLeft, , paint);
            }else {// 關
                canvas.drawBitmap(slideButtonBitmap, , , paint);
            }
        }

    }

    boolean isTouchMode = false;
    private OnSwitchStateUpdateListener onSwitchStateUpdateListener;
    // 重寫觸摸事件, 響應使用者的觸摸.
    @Override
    public boolean onTouchEvent(MotionEvent event) {

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

            float center = switchBackgroupBitmap.getWidth() / f;

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

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

            mSwitchState = state;
            break;

        default:
            break;
        }

        // 重繪界面
        invalidate(); // 會引發onDraw()被調用, 裡邊的變量會重新生效.界面會更新

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

    /**
     * 設定背景圖
     * @param switchBackground
     */
    public void setSwitchBackgroundResource(int switchBackground) {
        switchBackgroupBitmap = BitmapFactory.decodeResource(getResources(), switchBackground);
    }

    /**
     * 設定滑塊圖檔資源
     * @param slideButton
     */
    public void setSlideButtonResource(int slideButton) {
        slideButtonBitmap = BitmapFactory.decodeResource(getResources(), slideButton);
    }

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

    public interface OnSwitchStateUpdateListener{
        // 狀态回調, 把目前狀态傳出去
        void onStateUpdate(boolean state);
    }

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

}
           

修改activity_main.xml

<RelativeLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:my="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <com.mengxin.toggleview.ui.ToggleView
        android:id="@+id/toggleView"
        android:layout_centerInParent="true"
        my:switch_background="@drawable/switch_background"
        my:slide_button="@drawable/slide_button"
        my:switch_state="false"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</RelativeLayout>
           

在value檔案夾下面建一個attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="ToggleView">
        <attr name="switch_background" format="reference"/>
        <attr name="slide_button" format="reference" />
        <attr name="switch_state" format="boolean" />
    </declare-styleable>

</resources>
           

具體實作參考源碼