Android 事件分發詳解

一、建立事件分發Demo

我們都知道事件分發與3個對象有關,Activity Viewgroup ,View.
所以下表顯示了他們之間與事件相關的方法:
這裏寫圖片描述
可以看到,Activity與View是沒有攔截方法的。這很容易理解,如果Activity剛分發出來就攔截,還不如不分發呢,對吧!如果View做攔截是不是多此一舉呢?後面沒誰了啊。View就是最後一個。那有人就問了,那爲什麼View還有分發方法呢?這就與源碼扯上關係了。
爲了能看到時間分發的流程,我們就得創建這3對象的子類,並重寫相應的方法。代碼如下:
首先建立一個EventView吧

package com.dx.demi.View;


import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;

/**
 * Created by demi on 16/12/6.
 */

public class EventView extends View {
    private Paint mPaint = new Paint();
    private float x = 0, y = 0;
    private TextView textView;

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

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

    public EventView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mPaint.setColor(Color.GREEN);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(2);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        System.out.println("View onTouchEvent..." + event.getAction());
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                x = event.getX();
                y = event.getY();
                invalidate();
                break;
            case MotionEvent.ACTION_MOVE:
                x = event.getX();
                y = event.getY();
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                x = 0;
                y = 0;
                invalidate();
                break;
        }
        return super.onTouchEvent(event);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        System.out.println("View dispatchTouchEvent..." + event.getAction());
        return super.dispatchTouchEvent(event);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawLine(x, 0, x, y, mPaint);
        canvas.drawLine(0, y, x, y, mPaint);
        if (textView != null) {
            textView.setText(getTextString());
        }
    }

    private String getTextString() {
        return "座標 ( " + x + "," + y + " )";
    }

    public void setTextView(TextView textView) {
        this.textView = textView;
    }

}

然後,EventLinearLayout跟上

package com.dx.demi.View;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.LinearLayout;

/**
 * Created by demi on 16/12/8.
 */

public class EventLinearLayout extends LinearLayout {
    public EventLinearLayout(Context context) {
        super(context);
    }

    public EventLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public EventLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        System.out.println("LinearLayout onInterceptTouchEvent..." + event.getAction());
        return super.onInterceptTouchEvent(event);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        System.out.println("LinearLayout dispatchTouchEvent..." + event.getAction());
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        System.out.println("LinearLayout onTouchEvent..." + event.getAction());
        return super.onTouchEvent(event);
    }
}

接下來是EventActivity:

package com.dx.demi.activity;

import android.app.Activity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.widget.TextView;

import com.dx.demi.R;
import com.dx.demi.View.EventView;

/**
 * Created by demi on 16/12/6.
 */

public class EventActivity extends Activity {
    private float x =0,y =0 ;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_event);
        TextView tv = (TextView) findViewById(R.id.tv);
        EventView eventView = (EventView) findViewById(R.id.event);
        eventView.setTextView(tv);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        System.out.println("activty dispatchTouchEvent..."+event.getAction());
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        System.out.println("activty onTouchEvent..."+event.getAction());
        return super.onTouchEvent(event);
    }

}

然後佈局文件:

<?xml version="1.0" encoding="utf-8"?>
<com.dx.demi.View.EventLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="40dp"
        android:layout_margin="10dp"/>
    <com.dx.demi.View.EventView
        android:id="@+id/event"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorAccent"/>

</com.dx.demi.View.EventLinearLayout>

代碼比較簡單,EventView 就做了一個功能,獲取用戶手指點擊的座標,畫出x ,y 軸到這個座標的線條。我在每個事件方法中都做了輸出。demo建立完畢,接下來就開始列舉情況,分析數據了。

二、列舉情況,數據分析

這一共有7個方法,每個方法有3種情況,return true,return false,return super,一共有3的7次方種,如果我每一種情況都列舉出來的話,那我要到何年何月才能完成這篇博文呢?我覺得,首先可以去除一些情況,其實,我們處理點擊事件都是在View中和Viewgroup中處理的,最關注的是View的兩個方法,和ViewGroup的三個方法。所以Activity完全可以不管,return super即可。而且在ViewGroup做攔截的時候,View的兩個方法的返回值不起作用了。因爲根本就不會運行這兩個方法。
我列舉了以下幾種情況:
1.所有返回值爲super()(默認);
這種情況就是,View不做處理,ViewGoup不做攔截。

這裏寫圖片描述

可以看到,默認情況下,事件從activity分發到ViewGroup,ViewGroup不做攔截,到達View的dispatchTouchEvent(),隨後到達View的onTouchEvent(),View默認不消耗事件,傳遞到ViewGroup的onTouchEvent(),ViewGroup也不消耗事件。傳遞到了Activity的onTouchEvent(),最終ACTION_DOWN,就這樣結束。接下來可以看到,ACTION_UP直接從分發開始就到Activity的onTouchEvent(),就結束了。沒有經過ViewGroup,和View。

2.ViewGroup做攔截(ViewGroup的 onInterceptTouchEvent返回值爲true,onTouchEvent()返回值爲true,其他super)

這裏寫圖片描述

可以看到,在ViewGroup做了攔截之後,View就不會運行那兩個方法了,所有處理在ViewGroup層了,而且ACTION_UP,事件也傳遞到了ViewGroup層,讓ViewGroup做了處理。

3.View消耗事件(View 的 onTouchEvent()返回值爲true,其他爲super)
這裏寫圖片描述
可以看到,View消耗了事件之後,ViewGroup和Activity不會去再運行OnTouchEvent()了。而且ACTION_UP,事件也傳遞到了View層,讓View做了處理。

4.ViewGroup停止分發事件(dispatchTouchEvent()返回值爲false,其他爲super)

這裏寫圖片描述

ViewGroup 不往下分發事件了,所以可以看到ViewGroup和View的onTouchEvent()方法都沒調用。View的dispatchTouchEvent(),也沒調用,但Activity的onTouchEvent()方法調用了。這說明了什麼?只要dispatchTouchEvent()調用了,並返回super,onTouchEvent()就會調用。所以說父類的dispatchTouchEvent()中調用了onTouchEvent()。

這4種情況是比較普遍的,其他情況很少見,甚至是不會出現,如果出現了,那程序就有bug了.

三、查看源碼

源碼我就不帶大家看了,解析源碼的大神應有盡有,附上友情鏈接:
http://blog.csdn.net/mynameishuangshuai/article/details/52912641

四、總結

事件分發從Activity到ViewGroup再到View,中途如果返回了false ,或者true,沒有返回super,就會終止事件的分發。事件處理是從View到ViewGroup再到Activity.只要前一個消耗了事件,後面的就不會處理事件了,事件攔截就只有ViewGroup了,攔截後,事件不會分發到View層。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章