3.2019Android View總結

1.View的滑動方式

a.layout(left,top,right,bottom):通過修改View四個方向的屬性值來修改View的座標,從而滑動View
b.offsetLeftAndRight() offsetTopAndBottom():指定偏移量滑動view
c.LayoutParams,改變佈局參數:layoutParams中保存了view的佈局參數,可以通過修改佈局參數的方式滑動view
d.通過動畫來移動view:注意安卓的平移動畫不能改變view的位置參數,屬性動畫可以
e.scrollTo/scrollBy:注意移動的是view的內容,scrollBy(50,50)你會看到屏幕上的內容向屏幕的左上角移動了,這是參考對象不同導致的,你可以看作是它移動的是手機屏幕,手機屏幕向右下角移動,那麼屏幕上的內容就像左上角移動了
f.scroller:scroller需要配置computeScroll方法實現view的滑動,scroller本身並不會滑動view,它的作用可以看作一個插值器,它會計算當前時間點view應該滑動到的距離,然後view不斷的重繪,不斷的調用computeScroll方法,這個方法是個空方法,所以我們重寫這個方法,在這個方法中不斷的從scroller中獲取當前view的位置,調用scrollTo方法實現滑動的效果

View的事件分發機制

點擊事件產生後,首先傳遞給Activity的dispatchTouchEvent方法,通過PhoneWindow傳遞給DecorView,然後再傳遞給根ViewGroup,進入ViewGroup的dispatchTouchEvent方法,執行onInterceptTouchEvent方法判斷是否攔截,再不攔截的情況下,此時會遍歷ViewGroup的子元素,進入子View的dispatchToucnEvent方法,如果子view設置了onTouchListener,就執行onTouch方法,並根據onTouch的返回值爲true還是false來決定是否執行onTouchEvent方法,如果是false則繼續執行onTouchEvent,在onTouchEvent的Action Up事件中判斷,如果設置了onClickListener ,就執行onClick方法。

View的加載流程

View隨着Activity的創建而加載,startActivity啓動一個Activity時,在ActivityThread的handleLaunchActivity方法中會執行Activity的onCreate方法,這個時候會調用setContentView加載佈局創建出DecorView並將我們的layout加載到DecorView中,當執行到handleResumeActivity時,Activity的onResume方法被調用,然後WindowManager會將DecorView設置給ViewRootImpl,這樣,DecorView就被加載到Window中了,此時界面還沒有顯示出來,還需要經過View的measure,layout和draw方法,才能完成View的工作流程。我們需要知道View的繪製是由ViewRoot來負責的,每一個DecorView都有一個與之關聯的ViewRoot,這種關聯關係是由WindowManager維護的,將DecorView和ViewRoot關聯之後,ViewRootImpl的requestLayout會被調用以完成初步佈局,通過scheduleTraversals方法向主線程發送消息請求遍歷,最終調用ViewRootImpl的performTraversals方法,這個方法會執行View的measure layout 和draw流程

View的measure layout 和 draw流程

在上邊的分析中我們知道,View繪製流程的入口在ViewRootImpl的performTraversals方法,在方法中首先調用performMeasure方法,傳入一個childWidthMeasureSpec和childHeightMeasureSpec參數,這兩個參數代表的是DecorView的MeasureSpec值,這個MeasureSpec值由窗口的尺寸和DecorView的LayoutParams決定,最終調用View的measure方法進入測量流程

measure:

View的measure過程由ViewGroup傳遞而來,在調用View.measure方法之前,會首先根據View自身的LayoutParams和父佈局的MeasureSpec確定子view的MeasureSpec,然後將view寬高對應的measureSpec傳遞到measure方法中,那麼子view的MeasureSpec獲取規則是怎樣的?分幾種情況進行說明
1.父佈局是EXACTLY模式:
a.子view寬或高是個確定值,那麼子view的size就是這個確定值,mode是EXACTLY(是不是說子view寬高可以超過父view?見下一個)
b.子view寬或高設置爲match_parent,那麼子view的size就是佔滿父容器剩餘空間,模式就是EXACTLY
c.子view寬或高設置爲wrap_content,那麼子view的size就是佔滿父容器剩餘空間,不能超過父容器大小,模式就是AT_MOST

2.父佈局是AT_MOST模式:
a.子view寬或高是個確定值,那麼子view的size就是這個確定值,mode是EXACTLY
b.子view寬或高設置爲match_parent,那麼子view的size就是佔滿父容器剩餘空間,不能超過父容器大小,模式就是AT_MOST
c.子view寬或高設置爲wrap_content,那麼子view的size就是佔滿父容器剩餘空間,不能超過父容器大小,模式就是AT_MOST

3.父佈局是UNSPECIFIED模式:
a.子view寬或高是個確定值,那麼子view的size就是這個確定值,mode是EXACTLY
b.子view寬或高設置爲match_parent,那麼子view的size就是0,模式就是UNSPECIFIED
c.子view寬或高設置爲wrap_content,那麼子view的size就是0,模式就是UNSPECIFIED

獲取到寬高的MeasureSpec後,傳入view的measure方法中來確定view的寬高,這個時候還要分情況

1.當MeasureSpec的mode是UNSPECIFIED,此時view的寬或者高要看view有沒有設置背景,如果沒有設置背景,就返回設置的minWidth或minHeight,這兩個值如果沒有設置默認就是0,如果view設置了背景,就取minWidth或minHeight和背景這個drawable固有寬或者高中的最大值返回
2.當MeasureSpec的mode是AT_MOST和EXACTLY,此時view的寬高都返回從MeasureSpec中獲取到的size值,這個值的確定見上邊的分析。因此如果要通過繼承view實現自定義view,一定要重寫onMeasure方法對wrap_conten屬性做處理,否則,他的match_parent和wrap_content屬性效果就是一樣的

layout:

layout方法的作用是用來確定view本身的位置,onLayout方法用來確定所有子元素的位置,當ViewGroup的位置確定之後,它在onLayout中會遍歷所有的子元素並調用其layout方法,在子元素的layout方法中onLayout方法又會被調用。layout方法的流程是,首先通過setFrame方法確定view四個頂點的位置,然後view在父容器中的位置也就確定了,接着會調用onLayout方法,確定子元素的位置,onLayout是個空方法,需要繼承者去實現。

getMeasuredHeight和getHeight方法有什麼區別?getMeasuredHeight(測量高度)形成於view的measure過程,getHeight(最終高度)形成於layout過程,在有些情況下,view需要measure多次才能確定測量寬高,在前幾次的測量過程中,得出的測量寬高有可能和最終寬高不一致,但是最終來說,還是會相同,有一種情況會導致兩者值不一樣,如下,此代碼會導致view的最終寬高比測量寬高大100px

public void layout(int l,int t,int r, int b){
    super.layout(l,t,r+100,b+100);
}

draw:

View的繪製過程遵循如下幾步:
a.繪製背景 background.draw(canvas)
b.繪製自己(onDraw)
c.繪製children(dispatchDraw)
d.繪製裝飾(onDrawScrollBars)

View繪製過程的傳遞是通過dispatchDraw來實現的,它會遍歷所有的子元素的draw方法,如此draw事件就一層一層的傳遞下去了

ps:view有一個特殊的方法setWillNotDraw,如果一個view不需要繪製內容,即不需要重寫onDraw方法繪製,可以開啓這個標記,系統會進行相應的優化。默認情況下,View沒有開啓這個標記,默認認爲需要實現onDraw方法繪製,當我們繼承ViewGroup實現自定義控件,並且明確知道不需要具備繪製功能時,可以開啓這個標記,如果我們重寫了onDraw,那麼要顯示的關閉這個標記

子view寬高可以超過父view?能

1.android:clipChildren = "false" 這個屬性要設置在父 view 上。代表其中的子View 可以超出屏幕。
2.子view 要有具體的大小,一定要比父view 大 才能超出。比如 父view 高度 100px 子view 設置高度150px。子view 比父view大,這樣超出的屬性纔有意義。(高度可以在代碼中動態賦值,但不能用wrap_content / match_partent)。
3.對父佈局還有要求,要求使用linearLayout(反正我用RelativeLayout 是不行)。你如果必須用其他佈局可以在需要超出的view上面套一個linearLayout 外面再套其他的佈局。
4.最外面的佈局如果設置的padding 不能超出

自定義view需要注意的幾點

1.讓view支持wrap_content屬性,在onMeasure方法中針對AT_MOST模式做專門處理,否則wrap_content會和match_parent效果一樣(繼承ViewGroup也同樣要在onMeasure中做這個判斷處理)

if(widthMeasureSpec == MeasureSpec.AT_MOST && heightMeasureSpec == MeasureSpec.AT_MOST){
    setMeasuredDimension(200,200); // wrap_content情況下要設置一個默認值,200只是舉個例子,最終的值需要計算得到剛好包裹內容的寬高值
}else if(widthMeasureSpec == MeasureSpec.AT_MOST){
    setMeasuredDimension(200,heightMeasureSpec );
}else if(heightMeasureSpec == MeasureSpec.AT_MOST){
    setMeasuredDimension(heightMeasureSpec ,200);
}

2.讓view支持padding(onDraw的時候,寬高減去padding值,margin由父佈局控制,不需要view考慮),自定義ViewGroup需要考慮自身的padding和子view的margin造成的影響
3.在view中儘量不要使用handler,使用view本身的post方法
4.在onDetachedFromWindow中及時停止線程或動畫
5.view帶有滑動嵌套情形時,處理好滑動衝突

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章