每次到每年的年末,都會花幾天時間把今年對每一個知識點總結一下。算是對本身經驗的累積,以彌補本身的不足。把知識點彙總一下,看看自身的不足和錯誤,以便2020年再接再礪
(順手留下GitHub連接,須要獲取相關面試等內容的能夠本身去找)
https://github.com/xiangjiana/Android-MS
(VX:mm14525201314)android
View 的工做流程主要是指 measure、layout、draw 這三大流程,即測量、佈局和繪製,其中 measure 肯定 View 的測量寬/ / 高,layout 肯定 View 的最終寬/ / 高和 四個頂點的位置,而 draw 則將 View繪製到屏幕上git
View 的繪製過程遵循以下幾步:github
點擊事件產生後,首先傳遞給 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 事件傳遞分發機制?
參考回答:canvas
MotionEvent
事件分發的過程。即當一個 MotionEvent 發生後,系統將這個點擊事件傳遞到一個具體的 View 上事件分發過程由三個方法共同完成佈局
dispatchTouchEvent
:用來進行事件的分發。若是事件可以傳遞給當前 View,那麼此方法必定會被調用,返回結果受當前 View 的 onTouchEvent
和下級View 的 dispatchTouchEvent
方法的影響,表示是否消耗當前事件onInterceptTouchEvent
:在上述方法內部調用,對事件進行攔截。該方法只在 ViewGroup
中有,View(不包含 ViewGroup
)是沒有的。一旦攔截,則執行 ViewGroup
的 onTouchEvent
,在 ViewGroup
中處理事件,而不接着分發給 View。且只調用一次,返回結果表示是否攔截當前事件onTouchEvent
: 在 dispatchTouchEvent
方法中調用,用來處理點擊事件,返回結果表示是否消耗當前事件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 流程post
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 帶有滑動嵌套情形時,處理好滑動衝突spa
在上邊的分析中咱們知道,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
獲取規則是怎樣的?分幾種狀況進行說明
a. 子 view 寬或高是個肯定值,那麼子 view 的 size 就是這個肯定值,mode是 EXACTLY(是否是說子 view 寬高能夠超過父 view?見下一個)
b. 子 view 寬或高設置爲 match_parent,那麼子 view 的 size 就是佔滿父容器剩餘空間,模式就是 EXACTLY
c. 子 view 寬或高設置爲 wrap_content,那麼子 view 的 size 就是佔滿父容器剩餘空間,不能超過父容器大小,模式就是 AT_MOST
a. 子 view 寬或高是個肯定值,那麼子 view 的 size 就是這個肯定值,mode 是EXACTLY
b. 子 view 寬或高設置爲 match_parent,那麼子 view 的 size 就是佔滿父容器剩餘空間,不能超過父容器大小,模式就是 AT_MOST
c. 子 view 寬或高設置爲 wrap_content,那麼子 view 的 size 就是佔滿父容器剩餘空間,不能超過父容器大小,模式就是 AT_MOST
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); { }
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 不能超出
參考回答:
MotionEvent 是手指接觸屏幕後所產生的一系列事件。典型的事件類型有以下:
ACTION_DOWN:手指剛接觸屏幕
ACTION_MOVE:手指在屏幕上移動
ACTION_UP:手指從屏幕上鬆開的一瞬間
ACTION_CANCELL:手指保持按下操做,並從當前控件轉移到外層控件時觸發
正常狀況下,一次手指觸摸屏幕的行爲會觸發一系列點擊
事件,考慮以下幾種狀況:
點擊屏幕後鬆開,事件序列:DOWN→UP
點擊屏幕滑動一會再鬆開,事件序列爲DOWN→MOVE→.....→MOVE→UP
參考回答:
常見開發中事件衝突的有 ScrollView
與 RecyclerView
的滑動衝突、RecyclerView
內嵌同時滑動同一方向
滑動衝突的處理規則:
滑動衝突的實現方法:
具體方法:須要重寫父容器的onInterceptTouchEvent
方法,在內部作出相應的攔截。
具體方法:須要配合requestDisallowInterceptTouchEvent
方法
參考回答:
MotionEvent.ACTION_UP
事件觸發時調用startScroll()
方法,該方法並無進行實際的滑動操做,而是記錄滑動相關量(滑動距離、滑動時間)invalidate/postInvalidate()
方法,請求 View重繪,致使 View.draw 方法被執行computeScroll
方法,而 computeScroll
又會去向 Scroller
獲取當前的scrollX
和 scrollY
;而後經過 scrollTo
方法實現滑動;接着又調用 postInvalidate
方法來進行第二次重繪,和以前流程同樣,如此反覆致使 View 不斷進行小幅度的滑動,而屢次的小幅度滑動就組成了彈性滑動,直到整個滑動過成結束
invalidate()
與 postInvalidate()
都用於刷新 View,主要區別是 invalidate()
在主線程中調用,若在子線程中使用須要配合 handler;而 postInvalidate()
可在子線程中直接調用。
SurfaceView
可在子線程進行頁面的刷新SurfaceView
適用於被動更新,如頻繁刷新,這是由於若是使用 View 頻繁刷新會阻塞主線程,致使界面卡頓SurfaceView
在底層已實現雙緩衝機制,而 View 沒有,所以 SurfaceView
更適用於須要頻繁刷新、刷新時數據處理量很大的頁面(如視頻播放界面)scollBy
內部調用了 scrollTo
,它是基於當前位置的相對滑動;而 scrollTo
是絕對滑動,所以若是使用相同輸入參數屢次調用 scrollTo
方法因爲 View 的初始位置是不變的,因此只會出現一次 View 滾動的效果Scroller
有過分滑動的效果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
方法實現滑動的效果
(順手留下GitHub連接,須要獲取相關面試等內容的能夠本身去找)
https://github.com/xiangjiana/Android-MS