書中把自定義View分為下面4類:
1、繼承View重寫onDraw方法
這種方法主要用于實作一些不規則的效果,不友善通過布局的組合方式來達到,一般需要自己繪制圖形并實作動畫等效果。需要重寫onDraw方法。采用該方法需要自己支援wrap_content和padding。
2、繼承ViewGroup派生特殊的Layout
這種方法主要用于實作自定義的布局,除了系統的LinearLayout、RelativeLayout、FrameLayout這幾種布局外,有時候我們需要重新定義新的布局,把多個View集合到一個ViewGroup裡,這個ViewGroup就需要我們自定義。采用這種方式需要處理ViewGroup的測量(onMeasure)、布局(onLayout)這兩個過程,并且還要處理子元素的測量和布局過程。
3、繼承特定的View(例如TextView)
這種方式的自定義View比較常見,也常用,比如平時項目中自定義的popwindow、progressbar等自定義View,都是繼承相應的控件來實作。這種方式不需要自己支援wrap_content和padding等。
4、繼承特定的ViewGroup(比如LinearLayout)
這種方式的自定義View也比較常見,當某種效果看起來像幾種View組合一起的時候,可以采用這種方式來實作。采用這種方式不需要自己處理ViewGroup的測量和布局這兩個過程。比起方法二,此方法更簡單,但方法二更靈活,能實作的View自由度高。
大家自定義View的時候可以根據自己需要定義的View需求來确定該使用上述方法的哪一種。
在自定義View前,前期我們需要做大量的自定義View的知識儲備,不然實作自定義View效果會很艱難。關于自定義View的知識點,我們需要掌握如下知識點:
1、View的位置參數
2、MotionEvent和TouchSlope
3、Velocity、GestureDetector和Scoller
4、scrollTo和scrollBy
5、彈性滑動
6、View的事件分發機制
7、View的滑動沖突
8、了解MeasureSpec
9、View的自定義過程onMeasure、onLayout和onDraw
不看不知道,一看吓一跳,自定義View的實作需要我們掌握并熟練這麼多知識點,這也是實作自定義View的必經之路,靜下心來好好學習吧,把每個知識點了解透。接下來,針對上述知識點,我們一個一個看過來吧。
1、View的位置參數
對于View的位置參數,把這幾個概念搞清楚就OK了,分别是left、top、right、bottom、x、y、translationX、translationY。left、top、right、bottom四個參數分别代表View的頂點坐标,left即View的左上角橫坐标,top即左上角縱坐标,right即右下角橫坐标,bottom即右下角縱坐标。注意,這些坐标都是相對于View的父容器來說的,是相對坐标。是以,我們可以根據這4個參數計算出View的寬度和高度:
width = right - left;
height = bottom - top;
在代碼中,我們可以分别用getLeft()、getTop()、getRight()、getBottom()方法擷取到4個參數的值。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2QvwVe0lmdhJ3ZvwFM38CXlZHbvN3cpR2Lc1TPB10QGtWUCpEMJ9CXsxWam9CXwADNvwVZ6l2c052bm9CXUJDT1wkNhVzLcRnbvZ2LcZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39TM1cTOxMDNyIzMwgDM2EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
x和y兩個參數分别代表View左上角的橫坐标和縱坐标,translationX和translationY代表View左上角相對于父容器的偏移量。這幾個參數的值可以用getX()、getY()、getTranslationX()和getTranslationY()方法擷取到。這幾個參數的之間的關系可以用下面的式子表示:
x = left + translationX;
y = right + translaitonY;
2、MotionEvent和TouchSlope
MotionEvent,在手指接觸螢幕後産生的一系列事件,比如
ACTION_DOWN 手指剛好接觸螢幕
ACTION_MOVE 手指在螢幕上移動
ACTION_UP 手指從螢幕上松開的一瞬間
等等
正常情況下,一次手指觸摸螢幕的行為會産生一系列點選事件,比如
點選螢幕後松開,事件序列為DOWN-->UP;
點選螢幕滑動一會兒後松開,事件序列為DOWN-->MOVE-->MOVE-->....>MOVE-->UP。
等等
在代碼中,我們可以根據MotionEvent對象得到點選事件發生的x和y坐标。通過getX()、getY()和getRawX()、getRawY()。兩者的差別在于,getX/getY傳回的是相對于目前View的左上角的x和y坐标,getRawX/getRawY傳回的是相對于手機螢幕左上角的x和y坐标。
TouchSlope是系統所能識别的被認為是滑動的最小距離。這個最小距離被系統定義為了一個常量,常量的值和裝置有關,在不同裝置上該值會有不同。在代碼中,我們可以根據該值來判斷目前觸摸螢幕的事件是否構成了一次滑動螢幕的行為,如果一次滑動之間的距離小于這個常量值,則系統會判斷這不是一次滑動操作。我們可以用下面代碼擷取到該常量值:
ViewConfiguration.get(getContext()).getScaledTouchSlop()。
3、Velocity、GestureDetector和Scoller
Veloctity,速度追蹤,用于追蹤手指在螢幕滑動的速度,包括水準方向和豎直方向的速度。在代碼中擷取手指滑動的水準方向速度和豎直方向速度,我們需要下面幾個步驟:
在View的onTouchEvent方法中把event事件綁定到速度追蹤對象上:
VelocityTracker vt = VelocityTracker.obtain();
vt.addMoveEvent(event);
擷取速度值:
vt.computeCurrentVelocity(1000);
int xVelocity = (int) vt.getXVelocity();
int yVelocity = (int) vt.getYVelocity();
注意,在調用getXVelocity和getYVelocity方法前必須調用computeCurrentVelocity方法;這裡獲得的速度值是指一段時間内手指滑過的像素數。上面我們設定的時間是1000毫秒,如果在1秒内滑動100像素,那麼水準速度就是100像素每秒。速度值可以為負。從右往左滑動,速度值為負,從左往右,速度值為正。當我們不需要計算速度時,要調用clear方法來重置并回收記憶體:
vt.clear();
vt.recycle();
GestureDetector,手勢檢測,用于輔助檢測使用者的單擊、滑動、長按、輕按兩下等行為。在代碼中注入手勢檢測,我們需要下面幾個步驟:
建立一個GestureDetector對象并實作OnGestureListener接口,根據需要我們還可以實作OnDoubleTapListener接口進而能夠監聽輕按兩下行為:
GestureDetector gd = new GestureDetector(this);
//解決長按螢幕後無法拖動的現象
gd.setIsLongpresEnabled(false);
接管目标的onTouchEvent方法,在View的onTouchEvent方法中實作:
gd.onTouchEvent(event);
通過上面兩個步驟,我們就把手勢檢測注入到View了。接下來我們可以有選擇的實作OnGestureListener和OnDoubleTapListener接口中的方法了。
onDown | 手指觸摸螢幕的瞬間 |
onShowPress | 手指觸摸螢幕,尚未松開或拖動 |
onSingleTapUp | 手指觸摸螢幕後松開,單擊行為 |
onScroll | 手指按下螢幕并拖動,拖動行為 |
onLongPress | 長久按着螢幕不放,長按行為 |
onFling | 按下觸摸屏,快速滑動後松開,快速滑動行為 |
onDoubleTap | 輕按兩下行為 |
onSingleTapConfirmed | 嚴格的單擊行為 |
onDoubleTapEvent | 表示發生了輕按兩下行為 |
在實際開發中,可以不使用GestureDetector,也可以在View的onTouchEvent中實作所需要的監聽。建議,如果隻是監聽滑動相關的,在onTouchEvent方法中實作即可,如果要監聽輕按兩下行為,那麼就使用GestureDetector。
Scroller,彈性滑動對象,用于實作View的彈性滑動。Scroller本身不能讓View實作彈性滑動,需要View的computeScroll方法配合使用才能實作彈性滑動。在實際開發中,實作的代碼基本固定,代碼很典型,需要記下:
Scroller scroller = new Scroller(context);
//緩慢滾動到指定位置
private void smoothScrollTo(int destX, int destY){
int scrollX = getScrollX();
int delta = destX - scrollX;
//1000毫秒内慢慢滑向destX
scroller.startSCroll(scrollX, 0, delta, 0, 1000);
invalidate();
}
@Override
public void computeScroll(){
if(scroller.computeScrollOffset()){
scrollTo(scroller.getCurrX(), scroller.getCurrY());
postInvalidate();
}
}
4、scrollTo和scrollBy
從andriod源碼可以看出,scrollBy實際上調用了scrollTo方法,實作了對View位置的相對滑動,而scrollTo則實作了View位置的絕對滑動。View滑動的時候有兩個參數很重要,mScrollX和mScrollY,這兩個屬性可以通過getScrollX和getScrollY方法擷取。mScrollX的值總是等于View左邊緣和View内容左邊緣在水準方向的距離,mScrollY的值總是等于View上邊緣和View内容上邊緣在豎直方向的距離。注意:scrollTo和scrollBy隻能改變View内容的位置而不能改變View在布局中的位置。
5、彈性滑動
實作彈性滑動,可以用下面三種方法來實作:
1)使用Scroller 上面第3點已經介紹如何用Scroller實作彈性滑動了;
2)通過動畫 關于如何使用動畫讓View動起來可以參考部落格;
3)使用延時政策 思想是通過發送一系列延時消息進而達到一種漸進式效果。可以使用Handler或View的postDelayed方法來實作,也可以用線程的sleep方法來實作。
6、View事件的分發機制和View的滑動沖突
View事件的分發機制以及View的滑動沖突,是重點也是難點,介紹清楚需要大量的篇幅,這裡先不做介紹,後面另起部落格專門介紹View事件的分發機制和View的滑動沖突。
7、MeasureSpec
可以了解為測量标準。在測量過程中,系統會将View的LayoutParams根據父容器所施加的規則轉換成想要的MeasureSpec,然後在根據這個measureSpec來測量View的寬和高。
MeasureSpec分SpecMode和SpecSize兩類,前者是測量模式,後者是測量大小。SpecMode分為三類:
UNSPECIFIED 父容器不對View有任何限制;
EXACTLY 父容器已經檢測出View所需要的精确大小,這個時候View的最終大小就是SpecSize所指定的值。它對應于LayoutParams中的match_parent和具體的數值這兩種模式;
AT_MOST 父容器指定了一個可用大小即SpecSize,View的大小不能大于這個值。他它對應于LayoutParams中的wrap_content。
需要注意一點是:MeasureSpec不是唯一由LayoutParams決定的,LayoutParams需要和父容器一起才能決定View的MeasureSpec,進而進一步決定View的寬和高。
8、View的自定義過程
View的自定義過程分為measure、layout、draw三大流程,即測量、布局、繪制,其中measure決定View的測量寬高,layout決定View的最終寬高和四個頂點的位置,draw則将View繪制到螢幕上。