3.1 View的基礎(chǔ)知識
- ViewGroup和View組成了樹的結(jié)構(gòu)。合成模式
- View的位置參數(shù)
- top
- left
- right
- bottom
- translationX
- translationY
- x = left+ translationX
- y = top + translationY
- MotionEvent
- ACTION_DOWN
- ACTION_MOVE
- ACTION_UP
- 點擊事件發(fā)送的x/y坐標:getX/getY 和 getRawX/getRawY
- TouchSlop:滑動的最小距離
ViewConfiguration.get(getContext()).getScaledTouchSlope()。 - VelocityTracker 速度追蹤,用于追蹤手指在水平和豎直方向的滑動速度。使用方法為:
- 速度計算公式: 速度 = (終點位置 - 起點位置) / 時間段
速度可能為負值,例如當手指從屏幕右邊往左邊滑動的時候。 - 速度是單位時間內(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)用 - 速度計算公式: 速度 = (終點位置 - 起點位置) / 時間段
- 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。
- Scroller:彈性滑動對象,實現(xiàn)View的彈性滑動。
3.2 View的滑動
- 使用scrollTo/scrollBy:
改變View內(nèi)容的位置而不能改變View在布局中的位置。- scrollBy是基于當前位置的相對滑動,而scrollTo是基于所傳參數(shù)的絕對滑動。通過View的getScrollX和getScrollY方法可以得到滑動的距離。
- 有兩個重要參數(shù):
- mScrollX
- mScrollY
- 使用動畫
- 使用動畫操作Vew的移動,主要就是操作View的translationX和translationY。
- View動畫不會真正改變View的位置,屬性動畫可以
- 改變布局參數(shù),即改變LayoutParams。
MarginLayoutParams params = (MarginLayoutParams)mButton1.getLayoutParams();
params.width += 100;
params.leftMargin += 100;
mButton1.requestLayout();//或者mButton1.setLayoutParams(params);
- ViewHelper類提供了很多的get/set方法來為屬性動畫服務(wù),例如setTranslationX和setTranslationY方法,這些方法是沒有版本要求的。
3.3 View的彈性滑動
彈性滑動的共同思想:將一次大的滑動分成若干次小的滑動并在一個時間段內(nèi)完成。
- 使用Scroller(View內(nèi)容滑動):
原理:- 構(gòu)造一個Scroller對象,并調(diào)用startScroll,然后invalidate, Scroll內(nèi)部只是保存幾個參數(shù)。
- 當View重繪后會在draw中調(diào)用computeScroll,computeScroll又向Scroller獲取當前的scrollX和scrollY;
- 然后通過scrollTo實現(xiàn)滑動
- 接著再調(diào)用postInvalidate進行第二次重繪,又重復(fù)第2步,如此反復(fù),直到滑動過程結(jié)束。
- 通過動畫
- 使用延時策略。使用handler和postDelayed配合。
3.4 View的事件分發(fā)機制
- 事件分發(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;
}
- OnTouchListener的優(yōu)先級比onTouchEvent要高。
- 如果設(shè)置了onTouchListener,則onTouch會被回調(diào)。
- 如果onTouch返回flase,則onTouchEvent會調(diào)用
- 如果onTouch返回true,則onTouchEvent不調(diào)用
- onClickListener優(yōu)先級比onTouchEvent低。
- onTouchEvent()方法中,如果DOWN事件返回true,就會截斷事件的下傳,通俗點之后的ACTION_UP以及OnClick/OnLongClick就不會在觸發(fā)
- 事件傳遞機制的11個結(jié)論
- 同一事件序列指從down開始,中間含有數(shù)量不定的move,最終以up結(jié)束
- 正常情況下,一個事件序列只能被一個View攔截且消耗。
- 某個View一旦決定攔截,那么這一個事件序列都只能由它來處理,并且它的onInterceptTouchEvent不會再被調(diào)用。
- 某個View一旦開始處理事件,若它不消耗ACTION_DOWN事件(onTouchEvent返回false),那么同一事件序列中的其他事件都不會交給他處理,并且將事件重新交給它的父元素處理。
- 如果View不消耗除ACTION_DOWN以外的其他事件,那么這個點擊事件消失,也不會調(diào)用父元素的onTouchEvent,最終會傳遞給Activity處理。
- ViewGroup默認不攔截任何事件
- View沒有onInterceptTouchEvent,一旦事件傳遞給它,則它的onTouchEvent就調(diào)用
- View的onTouchEvent默認消耗事件,返回true。當然有特殊的TextView,不可點擊的View返回false。
- View的enable屬性不影響onTouchEvent的默認返回值。
- onClick的前提是可點擊
- 事件傳遞過程是從外向內(nèi)。
3.5 View的滑動沖突
- 常見的滑動沖突的場景:
- 外部滑動方向和內(nèi)部滑動方向不一致,例如viewpager中包含listview;
- 外部滑動方向和內(nèi)部滑動方向一致,SrollView中包含同樣方向的一個SrollView;
- 上面兩種情況的嵌套,例如viewpager中包含多個頁面,的每個頁面都是ScrollView,而ScrollView中又包含ScrollView。
- 滑動沖突處理規(guī)則
- 根據(jù)滑動距離和水平方向形成的夾角;
- 根據(jù)水平和豎直方向滑動的距離差;
- 兩個方向上的速度差等
- 滑動沖突的解決方式
- 外部攔截法:點擊事件都先經(jīng)過父容器的攔截處理,如果父容器需要此事件就攔截,如果不需要就不攔截。該方法需要重寫父容器的onInterceptTouchEvent方法,在內(nèi)部做相應(yīng)的攔截即可,其他均不需要做修改。
- 內(nèi)部攔截法:父容器不攔截任何事件,所有的事件都傳遞給子元素,如果子元素需要此事件就直接消耗掉,否則就交給父容器來處理。這種方法和Android中的事件分發(fā)機制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作。
- 個人一般使用外部攔截法