第3章-View的事件體系讀書筆記
@[TOC](目錄)
1 View 基礎知識
1.1 對於 View 的理解
View 是 Android 中所有控制元件的基類;ViewGroup 繼承了 View,這樣 View 本身就可以是單個控制元件也可以多個控制元件組成的一組控制元件,通過這種關係就形成了 View 樹的結構。
1.2 View 的位置引數有哪些?
View 的位置由四個頂點來確定,對應 View 的四個屬性:left、top、right、bottom。注意,這些座標都是相對於 View 的父容器的。
從 Android 3.0 開始,新增的幾個引數:x、y、translationX 和 translationY。x 是左上角的橫座標,y 是左上角的縱座標, translationX 是左上角相對父容器橫向的偏移量,translationY 是左上角相對父容器縱向的偏移量。
public float getX() {
return mLeft + getTranslationX();
}
public float getY() {
return mTop + getTranslationY();
}
View 在平移的過程中,top 和 left 表示的是原始左上角的位置資訊,其值不會發生改變,發生改變的是 x、y、translationX 和 translationY。
1.3 MotionEvent 類中的 getX()/getY() 和 getRawX()/getRawY() 這兩組方法的區別是什麼?
getX()/getY() 返回的是點選事件距離當前 View 左邊/頂邊的距離,對應於檢視座標系,是檢視座標;而 getRawX()/getRawY() 返回的是點選事件距離整個螢幕左邊/頂邊的距離,對應的是 Android 座標系,是絕對座標。可以看一下下邊的圖:
需要注意的是 getLeft()、getTop()、getRight() 和 getBottom() 是 View 類中的方法。
1.4 如何獲取滑動的最小距離?
ViewConfiguration.get(getContext()).getScaledTouchSlop();
當處理滑動時,可以利用這個常量來做一些過濾,用來判斷是不是滑動,可以有更好的使用者體驗。在不同的裝置上,這個值可能是不同的。
1.5 VelocityTracker、GestureDetector 和 Scroller 怎麼使用?
VelocityTracker 用於速度追蹤,方便根據獲取到的速度來作進一步的操作。注意的地方有,獲取速度前必須先計算速度,速度指的是一段時間內手指滑過的畫素數,不使用的時候需要呼叫 recycle 方法來重置並回收記憶體;
GestureDectector 用於手勢檢測,其中監聽雙擊行為是 onTouchEvent() 方法沒有的,自己使用過用於手勢切換 Activity;
Scroller 用於實現 View 的彈性滑動。當使用 View 的 scrollTo/scrollBy 方法進行滑動時,是瞬間完成的,沒有過渡效果。而使用 Scroller 可以實現有過渡效果的滑動。但是,Scroller 本身無法讓 View 彈性滑動,它必須和 View 的 computeScroll 方法配合使用才能完成這個功能。
2 View 的滑動
2.1 實現 View 的滑動的方法有哪些?
1,使用 scrollTo/scrollBy 方法;
2,使用動畫,注意 View 動畫只是對 View 的影像做操作,不能真正改變 View 的位置引數,而屬性動畫可以;
3,改變佈局引數,需要使用 MarginLayoutParams 的 leftMargin,topMargin 屬性。
2.2 View 的 scrollTo、scrollBy 方法的區別是什麼?
scrollBy 內部呼叫了 scrollTo 方法,
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
scrollBy 實現的是基於當前位置的相對滑動,當傳入都為負值時會向右下角移動,而 scrollTo 實現的是基於所傳遞引數的絕對滑動。
scrollTo 和 scrollBy 都是隻能改變 View 內容的位置而不能改變 View 在佈局中的位置。
2.3 View 內部的兩個屬性 mScrollX 和 mScrollY 的改變規則
mScrollX 指的是 View 的內容在橫向滑動的距離,即 View 左邊緣和 View 內容左邊緣在水平方向的距離;
mScrollY 指的是 View 的內容在縱向滑動的距離,即 View 上邊緣和 View 內容上邊緣在豎直方向的距離;
mScrollX 和 mScrollY 的單位是畫素,可以分別通過 getScrollX 和 getScrollY 來獲取;
當 View 左邊緣在 View 內容左邊緣的右邊時,mScrollX 的值為正,反之,為負;
當 View 上邊緣在 View 內容上邊緣的下邊時,mScrollY 的值為正,反之,為負。
3 彈性滑動
3.1 實現彈性滑動的共同思想是什麼?
將一次大的滑動分成若干次小的滑動並在一定時間內完成,實現漸進式滑動,或者說有過渡效果的滑動。
3.2 實現彈性滑動的方法有哪些?
1,使用 Scroller
2,通過動畫
3,使用延時策略
3.3 Scroller 的工作原理是什麼?
這裡寫一個使用 Scroller 實現彈性滑動的例子:
public class ScrollerLayout extends LinearLayout {
private Scroller mScroller;
public ScrollerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mScroller = new Scroller(context);
}
public void smoothScrollTo(int destX, int destY) {
int scrollX = getScrollX();
int deltaX = destX - scrollX;
// 1000 ms 內滑向 destX, 效果就是慢慢滑動
mScroller.startScroll(scrollX, 0, deltaX, 0, 1000);
invalidate();
}
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
}
第一步,先構造一個 Scroller 物件;
第二步,呼叫 Scroller 的 startScroll 方法,傳入的引數是滑動的起點,要滑動的距離,滑動的時間。但是僅呼叫 startScroll 方法並不能實現滑動,可以看 startScroll 方法的內部只是儲存了傳入的引數而已。
第三步,在 startScroll 後面,呼叫 invalidate 方法,這樣會導致 View 重繪,在 View 的 draw 方法中又會去呼叫 computeScroll 方法,computeScroll 方法在 View 裡是空實現的。
第四步,重寫 computeScroll 方法,在裡面呼叫 Scroller 的 computeScrollOffset 方法,這個方法的作用是判斷滑動是否結束了,根據經過的時間計算出要滑動到的位置。如果這個方法返回 false,表示彈性滑動結束了。
第五步,呼叫 scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); 滑動到要滑動的位置。
第六步,呼叫 postInvalidate 方法,再次導致 View 重繪,會繼續走第四步。
總之,Scroller 正是將一次大的滑動分成若干次小的滑動並在一定時間內完成,實現漸進式滑動這一思想的程式碼實現。
4 View 的事件分發機制
4.1 什麼是事件分發?
首先要知道,Android 的檢視是由一個個 View 構成的層級檢視,也就是說一個 View 裡可以包含多個子 View,而每個子 View 又可以包含更多的子 View;當用戶觸控式螢幕幕產生一系列事件時,事件會由高到低,由外向內依次傳遞,最終把事件傳遞給一個具體的 View,這個傳遞的過程就叫做事件分發。
4.2 當一個點選事件產生後,它的傳遞過程遵循什麼順序?
Activity -> Window -> View,即事件總是先傳給 Activity,Activity 再傳遞給 Window,最後 Window 再傳遞給頂級 View。頂級 View 接收到事件後,就會按照事件分發機制去分發事件。如果一個 View 的 onTouchEvent 返回 false,那麼它的父容器的 onTouchEvent 將會被呼叫,依此類推。如果所有的元素都不處理這個事件,那麼這個事件最終會傳遞給 Activity 處理,即 Activity 的 onTouchEvent 方法會被呼叫。
4.3 當 View 需要處理事件時,它的 OnTouchListener,OnTouchEvent 和 OnClickListener 的優先順序是怎樣的?
可以閱讀 View 類的 dispatchTouchEvent(MotionEvent event) 方法得到答案:如果這個 View 設定了 OnTouchListener,
public interface OnTouchListener {
boolean onTouch(View v, MotionEvent event);
}
那麼 OnTouchListener 的 onTouch 方法就會被回撥。這時如果 onTouch 方法返回 true,那麼 onTouchEvent 方法就不會被呼叫;如果 onTouch 方法返回 false,那麼 onTouchEvent 方法會被呼叫。所以,View 設定的 OnTouchListener,其優先順序比 onTouchEvent 方法要高。這樣做的好處是方便在外界處理 View 的點選事件。具體可以看這段原始碼:
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
在 View 的 onTouchEvent 方法中,如果當前設定了 OnClickListener,那麼它的 onClick 方法會被呼叫。所以,onTouchEvent 方法的優先順序比 OnCLickListener 要高。具體可以看下面的原始碼:
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
4.4 事件分發的三個重要方法是什麼,以及它們在 Activity,ViewGroup 和 View 中的存在狀態是怎樣的?
三個重要方法是
public boolean dispatchTouchEvent(MotionEvent ev);
public boolean onInterceptTouchEvent(MotionEvent ev) ;
public boolean onTouchEvent(MotionEvent event);
dispatchTouchEvent 方法的作用是分發點選事件,當點選事件能夠傳遞給當前 View時就會被呼叫;
onInterceptTouchEvent 方法的作用是用於判斷是否攔截點選事件,在 ViewGroup 的 dispatchTouchEvent 方法內部呼叫;
onTouchEvent 方法的作用是處理點選事件,在 dispatchTouchEvent 方法內部呼叫。
對應的存在狀態如下:
方法 | Activity | ViewGroup | View |
---|---|---|---|
dispatchTouchEvent | √ | √ | √ |
onInterceptTouchEvent | × | √ | × |
onTouchEvent | √ | √ | √ |
可以看到,只有 ViewGroup 具有 onInterceptTouchEvent 方法,而在 Activity 和 View 中是沒有這個方法的。