簡書上有一篇寫的蠻不錯的...Android View的事件分發及攔截機制分析,不過我們也自己列印下看看流程吧! 然後琢磨下哪些場景需要處理這個事件分發,需要解決這個事件衝突。小白印象中,ViewPaper與橫向滑動的RecycleView/ListView事件衝突的問題比較經常看到!(這個小白會看sdk文檔裡面的一些方法介紹).

小白也列印下看看妮?

CustomConstraintLayout.java

package me.heyclock.hl.customcopy;

import android.content.Context;
import android.support.constraint.ConstraintLayout;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;

public class CustomConstraintLayout extends ConstraintLayout {
public CustomConstraintLayout(Context context) {
this(context, null);
}

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

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

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("event", "CustomConstraintLayout dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d("event", "CustomConstraintLayout onInterceptTouchEvent" );
return super.onInterceptTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("event", "CustomConstraintLayout onTouchEvent" );
return super.onTouchEvent(event);
}
}

CustomTextView.java

package me.heyclock.hl.customcopy;

import android.annotation.SuppressLint;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.TextView;

@SuppressLint("AppCompatCustomView")
public class CustomTextView extends TextView {
public CustomTextView(Context context) {
this(context, null);
}

public CustomTextView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0, 0);
}

public CustomTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr,0);
}

public CustomTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("event", "CustomTextView dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("event", "CustomTextView onTouchEvent" );
return super.onTouchEvent(event);
}
}

setContentView(R.layout.custom_viewgroup_event);

<?xml version="1.0" encoding="utf-8"?>
<me.heyclock.hl.customcopy.CustomConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width_="match_parent"
android:layout_height="match_parent"
android:background="#ffff00ee">

<me.heyclock.hl.customcopy.CustomTextView
android:layout_width_="200dp"
android:layout_height="200dp"
android:background="#ffaa00ee"
android:text="大大的中間"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<me.heyclock.hl.customcopy.CustomTextView
android:layout_width_="100dp"
android:layout_height="100dp"
android:background="#ffddaaee"
android:text="小小的中間"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</me.heyclock.hl.customcopy.CustomConstraintLayout>

當點擊大大的中間以及小小的中間,結果都是類似:

10-30 03:47:28.676 8046-8046/me.heyclock.hl.customcopy D/event: CustomConstraintLayout dispatchTouchEvent
CustomConstraintLayout onInterceptTouchEvent
CustomTextView dispatchTouchEvent
CustomTextView onTouchEvent
CustomConstraintLayout onTouchEvent
10-30 03:47:43.932 8046-8046/me.heyclock.hl.customcopy D/event: CustomConstraintLayout dispatchTouchEvent
CustomConstraintLayout onInterceptTouchEvent
CustomTextView dispatchTouchEvent
CustomTextView onTouchEvent
CustomTextView dispatchTouchEvent
CustomTextView onTouchEvent
CustomConstraintLayout onTouchEvent

如碼友分析的一樣。事件的傳遞順序是上冊控制項依次傳遞到下層控制項,直到View. 而事件的處理順序,則由底層的View依次返給上級,直到最頂層進行處理。

事件傳遞的返回值:true,攔截,不繼續;false,不攔截,繼續流程。
  事件處理的返回值:true,處理了,不用審核了;false,給上級處理。
  初始情況下,返回值都是false。

為true就表示處理了,不用在往上傳遞了。So,我們就可以選擇某個傳遞的環節消費掉該事件,或者攔截掉事件傳遞,讓其不往下層傳遞;比如在CustomConstraintLayout 的onInterceptTouchEvent中返回true進行攔截,這樣中間的空間就不會響應了。

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d("event", "CustomConstraintLayout onInterceptTouchEvent" );
//return super.onInterceptTouchEvent(ev);
return true;
}

10-30 04:04:30.397 8337-8337/me.heyclock.hl.customcopy D/event: CustomConstraintLayout dispatchTouchEvent
CustomConstraintLayout onInterceptTouchEvent
CustomConstraintLayout onTouchEvent
10-30 04:04:32.916 8337-8337/me.heyclock.hl.customcopy D/event: CustomConstraintLayout dispatchTouchEvent
CustomConstraintLayout onInterceptTouchEvent
CustomConstraintLayout onTouchEvent

再比如僅僅把CustomTextView的

@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("event", "CustomTextView onTouchEvent" );
//return super.onTouchEvent(event);
return true;
}

事件傳遞繼續到底,onTouch不再往上到頂

10-30 04:11:57.038 8840-8840/me.heyclock.hl.customcopy D/event: CustomConstraintLayout dispatchTouchEvent
CustomConstraintLayout onInterceptTouchEvent
CustomTextView dispatchTouchEvent
CustomTextView onTouchEvent
10-30 04:11:57.132 8840-8840/me.heyclock.hl.customcopy D/event: CustomConstraintLayout dispatchTouchEvent
CustomConstraintLayout onInterceptTouchEvent
CustomTextView dispatchTouchEvent
CustomTextView onTouchEvent

目前小白也只是知道這樣的一個效果。現在有兩個問題要考慮下:

1. 運行兩遍?

2. 有什麼場景我們可以考慮實踐一下?

第一個問題,因為down和up都會走.....所以會進行兩次事件傳遞。也就是down先傳遞到底層,然後up再次傳遞到底層(如果你進行了滑動,還會有move事件傳遞...)。到底層後我們在View裡面return true - 消費掉了這個事件,所以上層不再收到相關事件處理。

第二個問題,有什麼場景需要做特殊處理呢?

比如我們要實現這樣的處理(子View可以進行左右滑動, 同時, 當在子View上面進行上下滑動時,依然是上層ViewGroup進行上下滑動.)

思路一下:

1. 子View控制項onTouchEvent返回true,保證響應處理相關touch事件(down,move,up)

2. 父ViewGroup在onInterceptTouchEvent中進行處理和攔截move事件(由於左右滑動時,子View需要做響應,所以只能攔截上下滑動的情況 - 這種情況返回true,表示父ViewGroup自己處理move事件,不再交給子View)

So,我們簡單定義一下ViewGroup和View

CustomTextView.java

package me.heyclock.hl.customcopy;

import android.annotation.SuppressLint;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.TextView;

@SuppressLint("AppCompatCustomView")
public class CustomTextView extends TextView {
public CustomTextView(Context context) {
this(context, null);
}

public CustomTextView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0, 0);
}

public CustomTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}

public CustomTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("event", "CustomTextView dispatchTouchEvent" + ev.getAction());
return super.dispatchTouchEvent(ev);
}

private float x1, x2;
private float y1, y2;
private float swing = 0;
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("event", "CustomTextView onTouchEvent" + event.getAction());
//return super.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
x1 = event.getX();
break;
case MotionEvent.ACTION_MOVE: ///< 上下滑動已經被上層攔截了,所以這裡肯定就是左右滑動了
x2 = event.getX();

///< 5作為閥值就可以了,可以根據效果調整
if(y1 - y2 > 5) { ///< 上
} else if(y2 - y1 > 5) { ///< 下
} else if(x1 - x2 > 5) { ///< 左
swing = x1 - x2;
///< 採用系統View類的滾動方法
scrollBy((int) swing, 0);
invalidate();
} else if(x2 - x1 > 5) { ///< 右
swing = -(x2 - x1);
///< 採用系統View類的滾動方法
scrollBy((int) swing, 0);
invalidate();
}
x1 = x2;
y1 = y2;
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
}

CustomConstraintLayout.java

package me.heyclock.hl.customcopy;

import android.content.Context;
import android.support.constraint.ConstraintLayout;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;

public class CustomConstraintLayout extends ConstraintLayout {
public CustomConstraintLayout(Context context) {
this(context, null);
}

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

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

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("event", "CustomConstraintLayout dispatchTouchEvent"+ ev.getAction());
return super.dispatchTouchEvent(ev);
}

private float x1, x2;
private float y1, y2;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d("event", "CustomConstraintLayout onInterceptTouchEvent" + ev.getAction());
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
x1 = ev.getX();
y1 = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
x2 = ev.getX();
y2 = ev.getY();

///< 5作為閥值就可以了,可以根據效果調整
if(y1 - y2 > 15) { ///< 上
return true;
} else if(y2 - y1 > 15) { ///< 下
return true;
} else if(x1 - x2 > 15) { ///< 左
} else if(x2 - x1 > 15) { ///< 右
}
x1 = x2;
y1 = y2;
break;
case MotionEvent.ACTION_UP:
break;
}
return super.onInterceptTouchEvent(ev);
}

/**
* 滾動相關(上下滑動)
*/
private float wx2;
private float wy2;
private float swing = 0;
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("event", "CustomConstraintLayout onTouchEvent" + event.getAction());
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
wx2 = event.getX();
wy2 = event.getY();

///< 5作為閥值就可以了,可以根據效果調整
if(y1 - wy2 > 5) { ///< 上
swing = y1 - wy2;
///< 採用系統View類的滾動方法
scrollBy(0, (int) swing);
invalidate();

Log.e("test", "上滑動");
} else if(wy2 - y1 > 5) { ///< 下
swing = -(wy2 - y1);
///< 採用系統View類的滾動方法
scrollBy(0, (int) swing);
invalidate();

Log.e("test", "下滑動");
} else if(x1 - wx2 > 50) { ///< 左
} else if(wx2 - x1 > 50) { ///< 右
}
x1 = wx2;
y1 = wy2;
break;
case MotionEvent.ACTION_UP:
break;
}
return super.onTouchEvent(event);
}
}

布局 custom_viewgroup_event.xml

<?xml version="1.0" encoding="utf-8"?>
<me.heyclock.hl.customcopy.CustomConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width_="match_parent"
android:layout_height="match_parent"
android:background="#ffff00ee">

<me.heyclock.hl.customcopy.CustomTextView
android:layout_width_="200dp"
android:layout_height="200dp"
android:background="#ffaa00ee"
android:text="大大的中間"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</me.heyclock.hl.customcopy.CustomConstraintLayout>

效果...

還有問題,就是上下滑動子View的外部還不行?

這個也很簡單了,有時候一直盯著子View,所以忘記了onTouch事件處理順序了!只需要把父ViewGroup的onTouchEvent返回true不就可以接收後續的move,up事件了么....

/**
* 滾動相關(上下滑動)
*/
private float wx2;
private float wy2;
private float swing = 0;
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("event", "CustomConstraintLayout onTouchEvent" + event.getAction());
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
x1 = event.getX();
y1 = event.getY();
break;
case MotionEvent.ACTION_MOVE:
wx2 = event.getX();
wy2 = event.getY();

///< 5作為閥值就可以了,可以根據效果調整
if(y1 - wy2 > 5) { ///< 上
swing = y1 - wy2;
///< 採用系統View類的滾動方法
scrollBy(0, (int) swing);
invalidate();

Log.e("test", "上滑動");
} else if(wy2 - y1 > 5) { ///< 下
swing = -(wy2 - y1);
///< 採用系統View類的滾動方法
scrollBy(0, (int) swing);
invalidate();

Log.e("test", "下滑動");
} else if(x1 - wx2 > 50) { ///< 左
} else if(wx2 - x1 > 50) { ///< 右
}
x1 = wx2;
y1 = wy2;
break;
case MotionEvent.ACTION_UP:
break;
}
return true;//super.onTouchEvent(event);
}

這樣我們就大體實現了這個目標...

這個的理解我們先這樣,回去再清醒清醒。下一篇打算去看sdk的官方文檔ViewGroup | Android Developers 以及一些個常用的方法,比如類似這種getParent().requestDisallowInterceptTouchEvent(false);

下班之前妮看兩篇文章吧...

事件分發:onTouchEvent返回false一定不執行ACTION_MOVE嗎?

(轉)淺談onInterceptTouchEvent、onTouchEvent與onTouch - oZuiJiaoWeiYang的專欄 - CSDN博客


推薦閱讀:
查看原文 >>
相关文章