2018.03.08、View的事件分發機制筆記

Traning發表於2018-03-15

一、

1、Activity頁面構成元素

  • 1)在activity 中 通過 setContentView() 方法設定為根佈局。在每一個Activity 中 都包含一個Window 物件,它由 PhoneWindow來實現
  • 2)PhoneWindow 將一個 DecorView 設定為整個應用視窗的根View。
  • 3)DecorView,由TitleView 和ContentView組成, 前者承載的是一個Actionbar,後者承載的是一個FrameLayout,

2、MotionEvent和TouchSlop

  • 1)手指接觸螢幕後 會產生以下的事件:
ACTION_DOWN // 收支接觸
ACTION_MOVE // 手指在螢幕上移動
ACTION_UP //手指從螢幕鬆開
ACTION_CANCEL //取消
複製程式碼
  • 2)MotionEvent 物件獲取的 點選的 x 和 y ,
    ①.使用 getX / getY 獲取的是相對於當前View左上角的x和y,
    ②.而 getRawX / getRawY 獲取的是相對於手機螢幕左上角的座標。

  • 3)TouchSlop:是系統所能識別的最小滑動距離,小於它則視未發生滑動,在不同的裝置上獲取的值不同。通過:

ViewConfiguration.get(getContext()).getScaledTouchSlop() //獲得
複製程式碼

3、VelocityTracker、GestureDetector和Scroller

  • 1)VelocityTracker:速度追蹤,用於追蹤滑動過程中的速度,包括水平和豎直速度。
//1.在 onTouchEvent 方法中追蹤當前事件的速度
VelocityTracker  tracker = VelocityTracker.obtain();
tracker.addMovement(event);
//2.獲取當前速度
tracker.computeCurrentVelocity(1000);//計算速度
int xVelocity = (int) tracker.getXVelocity();
int yVelocity = (int) tracker.getYVelocity();
//3.在不需要的時候重置回收記憶體
tracker.recycle();
複製程式碼
  • 2)GestureDetector:用於檢測單擊、滑動、長按、雙擊等手勢。
    首先要實現 GestureDetector.OnGestureListener介面,如果需要雙擊,則需實現 GestureDetector.OnDoubleTapListener 介面;
//設定監聽
mGestureDetector = new GestureDetector(this);
//避免長按後無法拖動,自己測試時發現不設定,長按後也可以拖動
mGestureDetector.setIsLongpressEnabled(false);
複製程式碼
  • 3)Scroller:彈性滑動,可是實現有過度效果的滑動,View 的 ScrollTo/ScrollBy 都是瞬間滑動完成的。

4、View 的滑動

  • 1)View的滑動主要有如下三種方式:
    • scrollTo /scrollBy :適合對view 的內容改變;
    • 動畫: 主要用於沒有互動的View 和實現複雜的動畫效果;
    • 改變佈局引數:操作稍微複雜,適合有互動的View 。
  • 2)View 的 ScrollTo/ScrollBy 方法
  public void scrollTo(int x, int y) {
      if (mScrollX != x || mScrollY != y) {
          int oldX = mScrollX;
          int oldY = mScrollY;
          mScrollX = x;
          mScrollY = y;
          invalidateParentCaches();
          onScrollChanged(mScrollX, mScrollY, oldX, oldY);
          if (!awakenScrollBars()) {
              postInvalidateOnAnimation();
          }
      }
  }
  public void scrollBy(int x, int y) {
      scrollTo(mScrollX + x, mScrollY + y);
  }
複製程式碼

其中 scrollBy 呼叫的是 scrollTo,它實現了使用當前位置的相對滑動,而scrollTo 是基於所傳引數的絕對滑動.

  • 3)使用動畫
    通過動畫為View 新增平移效果,View 的 tanslationX 和 tanslationY 屬性,可以採用傳統的動畫和屬性動畫。
  • 4)改變佈局引數
    通過改變View 的LayoutParams 使得 View 重新佈局實現滑動。
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams)
                mButton.getLayoutParams();
params.width += 100;
params.leftMargin += 100;
mButton.setLayoutParams(params);  
// 或者  mButton.requestLayout();
複製程式碼

5、彈性滑動

為了避免 滑動的生硬,可以採用彈性滑動,提高使用者體驗。
這裡主要有 :Scroller、動畫、延時三種方式。

  • 1)Scroller 的典型使用,主要是 invalidate方法起的作用。
Scroller mScroller = new Scroller(context);

/**
 * 滑動到指定位置
 *
 * @param destX  X 滑動距離
 * @param destY  Y 滑動距離
 */
private void smoothScrollTo(int destX, int destY) {
    //滑動起點X
    int scrollX = getScrollX();
    //滑動起點Y
    int scrollY = getScrollY();
    //1000 ms內慢慢滑向 (destX,destY)
    mScroller.startScroll(scrollX, scrollY, destX, destY, 1000);
    //重繪
    invalidate();
}
 /**
 * 使View 不斷重繪
 */
@Override
public void computeScroll() {
    /**
     *  computeScrollOffset 方法通過時間流逝百分比計算 scrollX和scrollY 
     *  返回true 表示滑動未結束
     */
    if (mScroller.computeScrollOffset()) {
        //滑動到當前位置,通過小幅度滑動實現彈性滑動
        scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
        //再次重繪
        postInvalidate();
    }
}
複製程式碼
  • 2)對於延時達到彈性滑動,主要是利用 了Handler 或者 View 的 postDelayed 方法,或者執行緒的 sleep方法。

6、View 的事件分發機制

  • 1)所有的Touch事件都封裝到 MotionEvent 裡面;
  • 2)事件處理包括三種情況,分別為:傳遞—-dispatchTouchEvent()函式、攔截——onInterceptTouchEvent()函式、消費—-onTouchEvent()函式和OnTouchListener;
  • 3)事件型別分為 ACTION_DOWN, ACTION_UP, ACTION_MOVE , ACTION_POINTER_DOWN, ACTION_POINTER_UP , ACTION_CANCEL 等,每個事件都是以 ACTION_DOWN 開始 ACTION_UP 結束。

用下面虛擬碼表示事件分發過程及其關係:

//事件分發
public boolean dispatchTouchEvent(MotionEvent event) {
    boolean consume = false;
    //是否被攔截
    if (onInterceptTouchEvent(event))
    {
        //被攔截,處理事件
        consume = onTouchEvent(event);
    } else {
        //未被攔截,向下分發
        consume = childView.dispatchTouchEvent(event);
    }
    return consume;
}
複製程式碼

1、activity --->ViewGroup --->view 正常流程:

  • 1)當activity dispatchTouchEvent() 返回 true :代表事件停止分發,activity 的onTouchEvent()方法不執行。其子view也接受不到事件。
  • 2)ViewGroup onInterceptTouchEvent() 返回ture時,onTouchEvent() 也返回 true時;

ViewGroup
①、
ViewGroup oninterceptTouchEnent 返回 ture時,表示:傳遞給子View的事件被攔截了,
ViewGroup onTouchEvent() 返回 true時,表示:該事件被VieWGroup自身消費了,不傳遞給父View的onTouch方法中處理了。
程式碼:

EmptyViewGroup: onInterceptTouchEvent--->ACTION_DOWN
EmptyViewGroup: onTouchEvent--->ACTION_DOWN
...
EmptyViewGroup: onTouchEvent--->ACTION_MOVE
...
EmptyViewGroup: onTouchEvent--->ACTION_UP

複製程式碼

②、
ViewGroup oninterceptTouchEnent 返回 ture時,表示:傳遞給子View事件被攔截了,
ViewGroup onTouchEvent() 返回 false時,表示:ViewGroup也不處理此事件,交給上層父View(avtivity)的OnTouchEvent()方法處理。
程式碼:

//viewGroup只消費 ACTION_DOWN 事件
EmptyViewGroup: onInterceptTouchEvent--->ACTION_DOWN
EmptyViewGroup: onTouchEvent--->ACTION_DOWN
//activity 將事件消費
DemoActivity: onTouchEvent--->ACTION_DOWN
...
DemoActivity: onTouchEvent--->ACTION_MOVE
...
DemoActivity: onTouchEvent--->ACTION_UP
複製程式碼

③、 ViewGroup onInterceptTouchEvent 返回 false時,表示事件傳遞給子View,不攔截。
ViewGroup onTouchEvent() 返回 false時,ViewGroup也不處理此事件,交給上層父View(avtivity)的OnTouchEvent()方法處理。
View onTouchEvent() 返回 true: 該子View消費了此事件。不傳遞到ViewGroup的OnTouchEvent()方法中。

EmptyViewGroup: onInterceptTouchEvent--->ACTION_DOWN
EmptyView: onTouchEvent--->ACTION_DOWN
EmptyViewGroup: onInterceptTouchEvent--->ACTION_MOVE
EmptyView: onTouchEvent--->ACTION_MOVE
...
EmptyViewGroup: onInterceptTouchEvent--->ACTION_UP
EmptyView: onTouchEvent--->ACTION_UP
複製程式碼

④、 ViewGroup onInterceptTouchEvent 返回 false時,表示事件傳遞給子View,不攔截。
ViewGroup onTouchEvent() 返回 false時,ViewGroup也不處理此事件,交給上層父View(avtivity)的OnTouchEvent()方法處理。
View onTouchEvent() 返回 false: 該子View不消費了此事件。傳遞到ViewGroup的OnTouchEvent()方法中。

EmptyViewGroup: onInterceptTouchEvent--->ACTION_DOWN
EmptyView: onTouchEvent--->ACTION_DOWN
EmptyViewGroup: onTouchEvent--->ACTION_DOWN
DemoActivity: onTouchEvent--->ACTION_DOWN

DemoActivity: onTouchEvent--->ACTION_MOVE
...
DemoActivity: onTouchEvent--->ACTION_UP

複製程式碼

由於activity作為頂級的View,當activity 中所有得子View 都不消費onTouchEvent()事件時,ativity的OnTouchEvent() 無論返回true/false。該activity都會消費使用者觸發的事件。在onTouchEvent()中。

相關文章