您好,登錄后才能下訂單哦!
每次到每一年的年底,都會花幾天時間把今年對每個知識點總結一下。算是對自己經驗的累積,以彌補自己的不足。把知識點匯總一下,看看自身的不足和錯誤,以便2020年再接再厲
(順手留下GitHub鏈接,需要獲取相關面試等內容的可以自己去找)
https://github.com/xiangjiana/Android-MS
(VX:mm14525201314)
參考答案
View 的工作流程主要是指 measure、layout、draw 這三大流程,即測量、布局和繪制,其中 measure 確定 View 的測量寬/ / 高,layout 確定 View 的最終寬/ / 高和 四個頂點的位置,而 draw 則將 View繪制到屏幕上
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 事件傳遞分發機制?
參考回答:
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 流程
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 帶有滑動嵌套情形時,處理好滑動沖突
在上邊的分析中我們知道,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
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。