第3章 View的事件體系

3.1 View的基礎(chǔ)知識

  1. ViewGroup和View組成了樹的結(jié)構(gòu)。合成模式
  2. View的位置參數(shù)
    • top
    • left
    • right
    • bottom
    • translationX
    • translationY
    • x = left+ translationX
    • y = top + translationY
  3. MotionEvent
    • ACTION_DOWN
    • ACTION_MOVE
    • ACTION_UP
    • 點擊事件發(fā)送的x/y坐標:getX/getY 和 getRawX/getRawY
  4. TouchSlop:滑動的最小距離
    ViewConfiguration.get(getContext()).getScaledTouchSlope()。
  5. VelocityTracker 速度追蹤,用于追蹤手指在水平和豎直方向的滑動速度。使用方法為:
    1. 速度計算公式: 速度 = (終點位置 - 起點位置) / 時間段
      速度可能為負值,例如當手指從屏幕右邊往左邊滑動的時候。
    2. 速度是單位時間內(nèi)移動的像素數(shù),單位時間不一定是1秒鐘,可以使用方法computeCurrentVelocity(xxx)指定單位時間是多少,單位是ms。例如通過computeCurrentVelocity(1000)來獲取速度,手指在1s中滑動了100個像素,那么速度是100,即100(像素/1000ms)。如果computeCurrentVelocity(100)來獲取速度,在100ms內(nèi)手指只是滑動了10個像素,那么速度是10,即10(像素/100ms)。
    //初始化
    VelocityTracker mVelocityTracker = VelocityTracker.obtain();
    //在onTouchEvent方法中
    mVelocityTracker.addMovement(event);
    //獲取速度
    mVelocityTracker.computeCurrentVelocity(1000);
    float xVelocity = mVelocityTracker.getXVelocity();
    //重置和回收
    mVelocityTracker.clear(); //一般在MotionEvent.ACTION_UP的時候調(diào)用
    mVelocityTracker.recycle(); //一般在onDetachedFromWindow中調(diào)用
    
  6. GestureDetector:輔助檢測用戶的單擊、滑動、長按、雙擊等。使用方法為:
GestrueDetector mGestrueDetector = new GestrueDetector(this);
mGestrueDetector.setIsLongpressEnabled(false); //解決長按屏幕后無法拖動的現(xiàn)象
boolean consume = mGestrueDetector.onTouchEvent(event);//接管目標View的onTouchEvent方法
return consume

然后就可以使用OnGestureListener和OnDoubleTapListener中的方法了。
注意:如果只是監(jiān)聽滑動相關(guān)的,建議onTouchEvent實現(xiàn);如果監(jiān)聽雙擊這種行為,使用GestureDetector。

  1. Scroller:彈性滑動對象,實現(xiàn)View的彈性滑動。

3.2 View的滑動

  1. 使用scrollTo/scrollBy:
    改變View內(nèi)容的位置而不能改變View在布局中的位置。
    1. scrollBy是基于當前位置的相對滑動,而scrollTo是基于所傳參數(shù)的絕對滑動。通過View的getScrollX和getScrollY方法可以得到滑動的距離。
    2. 有兩個重要參數(shù):
      • mScrollX
      • mScrollY
  2. 使用動畫
    1. 使用動畫操作Vew的移動,主要就是操作View的translationX和translationY。
    2. View動畫不會真正改變View的位置,屬性動畫可以
  3. 改變布局參數(shù),即改變LayoutParams。
MarginLayoutParams params = (MarginLayoutParams)mButton1.getLayoutParams();
params.width += 100;
params.leftMargin += 100;
mButton1.requestLayout();//或者mButton1.setLayoutParams(params);
  1. ViewHelper類提供了很多的get/set方法來為屬性動畫服務(wù),例如setTranslationX和setTranslationY方法,這些方法是沒有版本要求的。

3.3 View的彈性滑動

彈性滑動的共同思想:將一次大的滑動分成若干次小的滑動并在一個時間段內(nèi)完成。

  1. 使用Scroller(View內(nèi)容滑動):
    原理:
    1. 構(gòu)造一個Scroller對象,并調(diào)用startScroll,然后invalidate, Scroll內(nèi)部只是保存幾個參數(shù)。
    2. 當View重繪后會在draw中調(diào)用computeScroll,computeScroll又向Scroller獲取當前的scrollX和scrollY;
    3. 然后通過scrollTo實現(xiàn)滑動
    4. 接著再調(diào)用postInvalidate進行第二次重繪,又重復(fù)第2步,如此反復(fù),直到滑動過程結(jié)束。
  2. 通過動畫
  3. 使用延時策略。使用handler和postDelayed配合。

3.4 View的事件分發(fā)機制

  1. 事件分發(fā)過程的三個重要方法
    • public boolean dispatchTouchEvent(MotionEvent ev)
    • public boolean onInterceptTouchEvent(MotionEvent event)
    • public boolean onTouchEvent(MotionEvent event)
      三個方法的基本邏輯關(guān)系見下面的偽代碼
public boolean dispatchTouchEvent(MotionEvent ev) {
    boolean consume = false;
    if (onInterceptTouchEvent(ev)) { //當前view截斷
        consume = onTouchEvent(ev); //如果截斷,則只執(zhí)行本層的onTouchEvent
    } else {
        consume = child.dispatchTouchEvent(ev); //如果不截斷,則向下一級View傳遞Event
    }
    return consume;
}
  1. OnTouchListener的優(yōu)先級比onTouchEvent要高。
    1. 如果設(shè)置了onTouchListener,則onTouch會被回調(diào)。
    2. 如果onTouch返回flase,則onTouchEvent會調(diào)用
    3. 如果onTouch返回true,則onTouchEvent不調(diào)用
  2. onClickListener優(yōu)先級比onTouchEvent低。
    1. onTouchEvent()方法中,如果DOWN事件返回true,就會截斷事件的下傳,通俗點之后的ACTION_UP以及OnClick/OnLongClick就不會在觸發(fā)
  3. 事件傳遞機制的11個結(jié)論
    1. 同一事件序列指從down開始,中間含有數(shù)量不定的move,最終以up結(jié)束
    2. 正常情況下,一個事件序列只能被一個View攔截且消耗。
    3. 某個View一旦決定攔截,那么這一個事件序列都只能由它來處理,并且它的onInterceptTouchEvent不會再被調(diào)用。
    4. 某個View一旦開始處理事件,若它不消耗ACTION_DOWN事件(onTouchEvent返回false),那么同一事件序列中的其他事件都不會交給他處理,并且將事件重新交給它的父元素處理。
    5. 如果View不消耗除ACTION_DOWN以外的其他事件,那么這個點擊事件消失,也不會調(diào)用父元素的onTouchEvent,最終會傳遞給Activity處理。
    6. ViewGroup默認不攔截任何事件
    7. View沒有onInterceptTouchEvent,一旦事件傳遞給它,則它的onTouchEvent就調(diào)用
    8. View的onTouchEvent默認消耗事件,返回true。當然有特殊的TextView,不可點擊的View返回false。
    9. View的enable屬性不影響onTouchEvent的默認返回值。
    10. onClick的前提是可點擊
    11. 事件傳遞過程是從外向內(nèi)。

3.5 View的滑動沖突

  1. 常見的滑動沖突的場景:
    1. 外部滑動方向和內(nèi)部滑動方向不一致,例如viewpager中包含listview;
    2. 外部滑動方向和內(nèi)部滑動方向一致,SrollView中包含同樣方向的一個SrollView;
    3. 上面兩種情況的嵌套,例如viewpager中包含多個頁面,的每個頁面都是ScrollView,而ScrollView中又包含ScrollView。
  2. 滑動沖突處理規(guī)則
    1. 根據(jù)滑動距離和水平方向形成的夾角;
    2. 根據(jù)水平和豎直方向滑動的距離差;
    3. 兩個方向上的速度差等
  3. 滑動沖突的解決方式
    1. 外部攔截法:點擊事件都先經(jīng)過父容器的攔截處理,如果父容器需要此事件就攔截,如果不需要就不攔截。該方法需要重寫父容器的onInterceptTouchEvent方法,在內(nèi)部做相應(yīng)的攔截即可,其他均不需要做修改。
    2. 內(nèi)部攔截法:父容器不攔截任何事件,所有的事件都傳遞給子元素,如果子元素需要此事件就直接消耗掉,否則就交給父容器來處理。這種方法和Android中的事件分發(fā)機制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作。
    3. 個人一般使用外部攔截法
最后編輯于
?著作權(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ù)。

相關(guān)閱讀更多精彩內(nèi)容

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