[Android] 零碎知識匯總 - 觸摸反饋

核心概念


  • Event : Down、Move、Up、Cancel
  • onTouchEvent() : 處理以上四個事件, 統(tǒng)稱為一個事件流
  • onInterceptTouchEvent : 默認 false, 在合適的時機, 返回 true
  • requestDisallowInterceptTouchEvent
    : 子類需要臨時接管事件流的時候, 調(diào)用該方法, 從而處理該事件流
  • dispatchTouchEvent() : 包含 onTouchEvent()onInterceptTouchEvent()

流程


1. View的事件流程

View.onTouchEvent(MotionEvent evetn) {
  /*
    evetn 包含了事件類型: 按下 抬起 or 其它
    以及 坐標 和 其他各種信息
  */
}

整個事件流, 可以分為一下三種情況:
點擊 : Down > Up
滑動 : Down > Move...Move > Up
取消 : Down > Move...Move > Cancel

2. 實現(xiàn)觸摸反饋算法

public class CustomClickableView extends View {

  @Override public boolean onTouchEvent(MotionEvent event) {
    switch (event.getActionMasked()) {
      case MotionEvent.ACTION_DOWN:
        // to do something
    }
    return true; // 攔截: 下一節(jié)有說到
  }
}

3. 事件分發(fā)

核心: 離用戶最近的可觸摸的控件, 是這組事件流的響應(yīng)者

如圖: 黃色View 的上層有一個 可點擊的粉色View, 和一個 不可點擊的文本View`

粉色View自己消費事件流

此時, 上層粉色View 是這組事件流的響應(yīng)者

TextView不可點擊, 事件傳遞給下面的黃色VIew(按下時, 變色為橘色)

此時, 下層黃色View 是這組事件流的響應(yīng)者

重復(fù)一遍: 離用戶最近的可觸摸的控件, 是這組事件流的響應(yīng)者

在代碼中, 體現(xiàn)在 onTouchEvent(MotionEvent event) 的返回值, 上一節(jié)的代碼塊中, 有提到這個返回值為 true 意味著:
我(View)希望處理以這個 Down 事件為起始點的這條事件流, 請把這之后的后續(xù)事件都交給我吧

4. 事件分發(fā)的攔截機制

截止到目前為止, 所有的邏輯符合直覺:

事件從屏幕最頂部的那個 View 向下傳遞

在這個過程前, 還有一個過程:

在用戶觸摸屏幕的時候, 每一個觸摸事件到達 ViewonTouchEvent() 之前,
Android 會從整個 Activity 里面最底層的那個 根View 向上一級級地去詢問: "你要不要攔截這組事件?"
攔截的意思就是, 事件我就不交給子 View 了, 我直接轉(zhuǎn)而自己來處理了

這里就引出了 ViewGroup.onInterceptTouchEvent() 如圖

事件分發(fā)的攔截機制
  1. 先層底層像上層詢問(onInterceptTouchEvent): 你是否攔截這組事件
    • 攔截(true): 走自己的 onTouchEvent() 后續(xù)事件不會再詢問
    • 不攔截(false): 繼續(xù)向上層詢問
  2. 如果到了最頂層的 ViewGroup 都返回了 false (不攔截), 此時開始走下一個流程
  3. 從最頂層的 View.onTouchEvent() 開始向下執(zhí)行, 然后就是之前所說的邏輯了

整個邏輯流程像一個 形狀: 先向上,再向下
ps: 當 onInterceptTouchEvent 返回 true 時, 會先向子 View 發(fā)送一個 Cancel 事件, 恢復(fù)其之前的狀態(tài)

代碼如下:

public class CustomLayout extends ViewGroup {

  @Override public boolean onInterceptTouchEvent(MotionEvent event) {
    switch (event.getActionMasked()) {
      ...
      if (符合條件) {
        return true;
      }
    }
    return false;
  }

  @Override public boolean onTouchEvent(MotionEvent event) {
    switch (event.getActionMasked()) {
      ...
    }
    return true;
  }
}

以上說的都是 父View 攔截 子View 的情況, 那如果是 子View 想主動的告知 父View 不要攔截呢?
例如: 列表中, 長按 item 重排功能, 你需要在長按之后的上下滑動, 是移動列表項, 而不是滑動列表
這就引出了 View.requestDisallowInterceptTouchEvent(boolean disallowIntercept) 該方法不需要重寫, 直接調(diào)用, 告知 父View 不要攔截, 我自己處理當前事件流, 僅限當前事件流, 下次使用時需要重新調(diào)用
最后說一句, dispatchTouchEvent() 是事件分發(fā)的總調(diào)度方法, 上面說的 onTouchEvent()onInterceptTouchEvent() 都在發(fā)生在 dispatchTouchEvent() 里面的
所以: 一個事件分發(fā)的過程, 實質(zhì)上就是 從根 View 遞歸地調(diào)用了一次 dispatchTouchEvent() 的過程

其它


本篇內(nèi)容, 總結(jié)自 https://hencoder.com/ui-3-1/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容