1. 程式人生 > >自定義view系列(3)--給自定義View新增點選事件

自定義view系列(3)--給自定義View新增點選事件

這幾天一直在看《android開發藝術探索》和《android群英傳》中關於自定義view的章節,結合著網上大神的一些心得分享,感覺自定義view這一塊受益匪淺,這裡做個心得筆記,主要記錄一下view與使用者互動的一些知識。

自定義view與使用者互動用的最多的就是單擊事件,其次的還有雙擊事件、長按事件、滑動事件等,所以就需要做好view的事件監聽。

如果我們繼承了View,也繪製好了控制元件,但是不重寫onTouchEvent()方法的話,設定點選事件一般也是沒用的,但也不是一定沒用,下面先介紹一種比較簡單的點選事件實現方式,也是從張鴻洋大神的一篇部落格中看到的。

設定view的點選事件實現方式一:

在View的構造方法中直接setOnClickListener,如下程式碼:

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

    public TouchEventTest(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TouchEventTest(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Logger.e("view內部設定OnClickListener","");
            }
        });
    }
如上程式碼即可實現該自定義view的點選監聽,但是有很大的侷限性,首先這樣設定監聽很死板,不能供外部呼叫,其次經過測試,如果你這樣寫了程式碼,並且又重寫的該View的OnTouchEvent()方法,那麼不管你的onTouchEvent方法中是直接return super.onTouchEvent()還是做了任何其他邏輯,都不能觸發這個監聽事件,也就是說在這種情況下這種方式設定監聽根本沒用了,而這直接影響到了我們自定義View的擴充套件性,所以建議別這麼用,雖然簡單,但是麻煩事也多,不過這種方式也有一種適用情況,就是如果自定義View真的只需要自己內部處理邏輯,不需要外部的參與,那麼這樣設定也行,不過真的不推薦大家使用這種方式除非業務邏輯允許。

下面就介紹第二種給自定義view設定監聽的方式,也是個人認為比較正常且通用的一種方式。

即重寫onTouchEvent方法,然後判斷所點選的座標是否處於自定義view內部。如果處於自定義view內部,則回撥點選監聽介面,否則不予理會。

在說這種方式之前,先普及一下各種座標知識,畢竟一會要用到。


如上面的圖示:清楚的體現了各個座標的含義。

接著,我們需要重寫setOnClickListener()方法,程式碼如下:

@Override
    public void setOnClickListener(OnClickListener l) {
        mListener = l;
    }

就是把OnClickListener賦值,當然我們也可以不使用Override註解,又或者我們可以自定義介面和方法,並提供一個設定自定義監聽的方法,程式碼如下:
public void setOnViewClick(onViewClick click) {
        this.mViewClick = click;
    }


    public interface onViewClick {
        /**
         * @param scrollX 從按下到擡起,X軸方向移動的距離
         * @param scrollY 從按下到擡起,Y軸方向移動的距離
         */
        void onClick(float scrollX, float scrollY);
    }


然後我們重寫onTouchEvent方法,如下是一個通用的設定點選事件模板方法,

@Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE: 
                break;
            case MotionEvent.ACTION_UP:
                if (x + getLeft() < getRight() && y + getTop() < getBottom()) {
                    mListener.onClick(this);
                    mViewClick.onClick(x - startX, y - startY);
                }
                break;
        }
        return true;
    }

注意這裡一定要返回true或者根據自己的業務邏輯決定返回true還是false。根據view的事件分發流程,如果該自定義view不處理ACTION_DOWN事件,那麼以後的所有事件都不會給他處理,結合程式碼說一下:當用戶按下時,進入到MotionEvent.ACTION_DOWN這個case語句中,然後break掉,之後返回了true,重點就在這裡,如果這裡你返回了false,說明該view不處理事件,所以以後的各種狀態如ACTION_MOVE和ACTION_UP都不會再觸發,因為該事件已經交由該view的某個父類處理了,不會再傳遞給子view了。而如果返回了true,就說明該view接管了以後的所有操作,由該view處理相應的事件。

另外再說明一點:處理點選事件一般都是在ACTION_UP時進行,而處理滑動之類的一般都在ACTION_MOVE時進行!!!

下面我們在程式碼中驗證一下是否有效,程式碼如下(佈局檔案就不貼了):

public class TouchEventTestActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_touch_event_test);
        TouchEventTest testView = (TouchEventTest) findViewById(R.id.TouchEventTest);
        testView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Logger.e("setOnClickListener,view.getClass():" + v.getClass() + "");
            }
        });
        testView.setOnViewClick(new TouchEventTest.onViewClick() {
            @Override
            public void onClick(float scrollX, float scrollY) {
                Logger.e("x軸移動了:" + scrollX + " px,y軸移動了:" + scrollY + " px", "");
            }
        });

    }
}

我們看下打印出的Log資訊:


最後貼出完整程式碼,讓大家更好理解:

package com.lanma.customviewproject.views;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

/**
 * 作者 qiang_xi on 2016/8/23 15:05.
 */
public class TouchEventTest extends View {
    private OnClickListener mListener;
    private onViewClick mViewClick;
    private int startRawX;
    private int startRawY;

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

    public TouchEventTest(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TouchEventTest(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
//        this.setOnClickListener(new OnClickListener() {
//            @Override
//            public void onClick(View v) {
//                Logger.e("view內部設定OnClickListener", "");
//            }
//        });
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        int rawX = (int) event.getRawX();
        int rawY = (int) event.getRawY();
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                startRawX = rawX;
                startRawY = rawY;
                break;
            case MotionEvent.ACTION_MOVE:

                break;
            case MotionEvent.ACTION_UP:
                if (x + getLeft() < getRight() && y + getTop() < getBottom()) {
                    mListener.onClick(this);
                    mViewClick.onClick(rawX - startRawX, rawY - startRawY);
                }
                break;
        }
        return true;
    }

    @Override
    public void setOnClickListener(OnClickListener l) {
        mListener = l;
    }

    public void setOnViewClick(onViewClick click) {
        this.mViewClick = click;
    }


    public interface onViewClick {
        /**
         * @param scrollX 從按下到擡起,X軸方向移動的距離
         * @param scrollY 從按下到擡起,Y軸方向移動的距離
         */
        void onClick(float scrollX, float scrollY);
    }
}