您好,登錄后才能下訂單哦!
每次到每一年的年底,都會(huì)花幾天時(shí)間把今年對每個(gè)知識點(diǎn)總結(jié)一下。算是對自己經(jīng)驗(yàn)的累積,以彌補(bǔ)自己的不足。把知識點(diǎn)匯總一下,看看自身的不足和錯(cuò)誤,以便2020年再接再厲
(順手留下GitHub鏈接,需要獲取相關(guān)面試等內(nèi)容的可以自己去找)
https://github.com/xiangjiana/Android-MS
(VX:mm14525201314)
參考答案
View 的工作流程主要是指 measure、layout、draw 這三大流程,即測量、布局和繪制,其中 measure 確定 View 的測量寬/ / 高,layout 確定 View 的最終寬/ / 高和 四個(gè)頂點(diǎn)的位置,而 draw 則將 View繪制到屏幕上
View 的繪制過程遵循如下幾步:
點(diǎn)擊事件產(chǎn)生后,首先傳遞給 Activity 的 dispatchTouchEvent
方法,通過PhoneWindow
傳遞給 DecorView
,然后再傳遞給根 ViewGroup
,進(jìn)入 ViewGroup
的dispatchTouchEvent
方法,執(zhí)行 onInterceptTouchEvent
方法判斷是否攔截,再不攔截的情況下,此時(shí)會(huì)遍歷 ViewGroup
的子元素,進(jìn)入子 View 的dispatchToucnEvent
方法,如果子 view 設(shè)置了 onTouchListener
,就執(zhí)行 onTouch
方法,并根據(jù) onTouch
的返回值為 true 還是 false 來決定是否執(zhí)行 onTouchEvent
方法,如果是 false 則繼續(xù)執(zhí)行 onTouchEvent
。在onTouchEvent
的 Action Up 事件中判斷,如果設(shè)置了 onClickListener
,就執(zhí)行 onClick
方法。
View 事件傳遞分發(fā)機(jī)制?
參考回答:
MotionEvent
事件分發(fā)的過程。即當(dāng)一個(gè) MotionEvent 發(fā)生后,系統(tǒng)將這個(gè)點(diǎn)擊事件傳遞到一個(gè)具體的 View 上dispatchTouchEvent
:用來進(jìn)行事件的分發(fā)。如果事件能夠傳遞給當(dāng)前 View,那么此方法一定會(huì)被調(diào)用,返回結(jié)果受當(dāng)前 View 的 onTouchEvent
和下級View 的 dispatchTouchEvent
方法的影響,表示是否消耗當(dāng)前事件onInterceptTouchEvent
:在上述方法內(nèi)部調(diào)用,對事件進(jìn)行攔截。該方法只在 ViewGroup
中有,View(不包含 ViewGroup
)是沒有的。一旦攔截,則執(zhí)行 ViewGroup
的 onTouchEvent
,在 ViewGroup
中處理事件,而不接著分發(fā)給 View。且只調(diào)用一次,返回結(jié)果表示是否攔截當(dāng)前事件onTouchEvent
: 在 dispatchTouchEvent
方法中調(diào)用,用來處理點(diǎn)擊事件,返回結(jié)果表示是否消耗當(dāng)前事件View 隨著 Activity 的創(chuàng)建而加載,startActivity 啟動(dòng)一個(gè) Activity 時(shí),在ActivityThread
的 handleLaunchActivity
方法中會(huì)執(zhí)行 Activity 的 onCreate
方法,這個(gè)時(shí)候會(huì)調(diào)用 setContentView
加載布局創(chuàng)建出 DecorView
并將我們的 layout加載到 DecorView
中,當(dāng)執(zhí)行到 handleResumeActivity
時(shí),Activity 的 onResume
方法被調(diào)用,然后 WindowManager
會(huì)將 DecorView
設(shè)置給 ViewRootImpl
,這樣,DecorView
就被加載到Window中了,此時(shí)界面還沒有顯示出來,還需要經(jīng)過 View的 measure,layout 和 draw 方法,才能完成 View 的工作流程。我們需要知道 View的繪制是由ViewRoot
來負(fù)責(zé)的,每一個(gè)DecorView
都有一個(gè)與之關(guān)聯(lián)的ViewRoot
,這種關(guān)聯(lián)關(guān)系是由WindowManager
維護(hù)的,將DecorView
和 ViewRoot
關(guān)聯(lián)之后,ViewRootImpl
的requestLayout
會(huì)被調(diào)用以完成初步布局,通過scheduleTraversals
方法向主線程發(fā)送消息請求遍歷,最終調(diào)用ViewRootImpl
的 performTraversals
方法,這個(gè)方法會(huì)執(zhí)行 View 的 measure layout 和 draw 流程
1.讓 view 支持 wrap_content 屬性,在 onMeasure
方法中針對 AT_MOST 模式做專門處理,否則 wrap_content
會(huì)和 match_parent
效果一樣(繼承 ViewGroup
也同樣要在 onMeasure
中做這個(gè)判斷處理)
if (widthMeasureSpec == MeasureSpec.AT_MOST && heightMeasureSpec == MeasureSpec.AT_MOST) {
setMeasuredDimension( 200 , 200 );
// wrap_content
情況下要設(shè)置一個(gè)默認(rèn)值,200 只是舉個(gè)例子,最終的值需要計(jì)算得到剛好包裹內(nèi)容的寬高值
} else if (widthMeasureSpec == MeasureSpec.AT_MOST) {
setMeasuredDimension( 200 ,heightMeasureSpec );
} else if (heightMeasureSpec == MeasureSpec.AT_MOST) {
setMeasuredDimension(heightMeasureSpec , 200 );
}
2.讓 view 支持 padding(onDraw 的時(shí)候,寬高減去 padding 值,margin 由父布局控制,不需要 view 考慮),自定義 ViewGroup
需要考慮自身的 padding 和子 view的 margin 造成的影響
3.在 view 中盡量不要使用 handler,使用 view 本身的 post 方法
4.在 onDetachedFromWindow 中及時(shí)停止線程或動(dòng)畫
5.view 帶有滑動(dòng)嵌套情形時(shí),處理好滑動(dòng)沖突
在上邊的分析中我們知道,View 繪制流程的入口在 ViewRootImpl
的performTraversals
方法,在方法中首先調(diào)用 performMeasure
方法,傳入一個(gè)childWidthMeasureSpec
和 childHeightMeasureSpec
參數(shù),這兩個(gè)參數(shù)代表的是DecorView
的 MeasureSpec
值,這個(gè) MeasureSpec
值由窗口的尺寸和 DecorView
的 LayoutParams
決定,最終調(diào)用 View 的 measure 方法進(jìn)入測量流程
measure :
View 的 measure 過程由 ViewGroup
傳遞而來,在調(diào)用 View.measure 方法之前,會(huì)首先根據(jù) View 自身的 LayoutParams
和父布局的MeasureSpec
確定子 view 的MeasureSpec
,然后將 view 寬高對應(yīng)的 measureSpec
傳遞到 measure 方法中,那么子 view 的 MeasureSpec
獲取規(guī)則是怎樣的?分幾種情況進(jìn)行說明
a. 子 view 寬或高是個(gè)確定值,那么子 view 的 size 就是這個(gè)確定值,mode是 EXACTLY(是不是說子 view 寬高可以超過父 view?見下一個(gè))
b. 子 view 寬或高設(shè)置為 match_parent,那么子 view 的 size 就是占滿父容器剩余空間,模式就是 EXACTLY
c. 子 view 寬或高設(shè)置為 wrap_content,那么子 view 的 size 就是占滿父容器剩余空間,不能超過父容器大小,模式就是 AT_MOST
a. 子 view 寬或高是個(gè)確定值,那么子 view 的 size 就是這個(gè)確定值,mode 是EXACTLY
b. 子 view 寬或高設(shè)置為 match_parent,那么子 view 的 size 就是占滿父容器剩余空間,不能超過父容器大小,模式就是 AT_MOST
c. 子 view 寬或高設(shè)置為 wrap_content,那么子 view 的 size 就是占滿父容器剩余空間,不能超過父容器大小,模式就是 AT_MOST
a. 子 view 寬或高是個(gè)確定值,那么子 view 的 size 就是這個(gè)確定值,mode 是EXACTLY
b. 子 view 寬或高設(shè)置為 match_parent,那么子 view 的 size 就是 0,模式就是UNSPECIFIED
c. 子 view 寬或高設(shè)置為 wrap_content,那么子 view 的 size 就是 0,模式就是UNSPECIFIED
獲取到寬高的 MeasureSpec
后,傳入 view 的 measure 方法中來確定 view 的寬高,這個(gè)時(shí)候還要分情況
1.當(dāng)
MeasureSpec
的 mode 是 UNSPECIFIED,此時(shí) view 的寬或者高要看 view 有沒有設(shè)置背景,如果沒有設(shè)置背景,就返回設(shè)置的minWidth
或minHeight
,這兩個(gè)值如果沒有設(shè)置默認(rèn)就是 0,如果 view 設(shè)置了背景,就取minWidth
或minHeight
和背景這個(gè)drawable
固有寬或者高中的最大值返回
2.當(dāng)MeasureSpec
的 mode 是 AT_MOST 和 EXACTLY,此時(shí) view 的寬高都返回從MeasureSpec
中獲取到的 size 值,這個(gè)值的確定見上邊的分析。因此如果要通過繼承 view 實(shí)現(xiàn)自定義 view,一定要重寫onMeasure
方法對wrap_conten
屬性做處理,否則,他的match_parent
和wrap_content
屬性效果就是一樣的
layout:
layout 方法的作用是用來確定 view 本身的位置,onLayout
方法用來確定所有子元素的位置,當(dāng) ViewGroup
的位置確定之后,它在 onLayout
中會(huì)遍歷所有的子元素并調(diào)用其 layout 方法,在子元素的 layout 方法中 onLayout
方法又會(huì)被調(diào)用。layout 方法的流程是,首先通過 setFrame
方法確定 view 四個(gè)頂點(diǎn)的位置,然后view 在父容器中的位置也就確定了,接著會(huì)調(diào)用onLayout
方法,確定子元素的位置,onLayout
是個(gè)空方法,需要繼承者去實(shí)現(xiàn)。
getMeasuredHeight
和getHeight
方法有什么區(qū)別?getMeasuredHeight
(測量高度)形成于 view 的 measure 過程,getHeight
(最終高度)形成于 layout 過程,在有些情況下,view 需要 measure 多次才能確定測量寬高,在前幾次的測量過程中,得出的測量寬高有可能和最終寬高不一致,但是最終來說,還是會(huì)相同,有一種情況會(huì)導(dǎo)致兩者值不一樣,如下,此代碼會(huì)導(dǎo)致 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
來實(shí)現(xiàn)的,它會(huì)遍歷所有的子元素的draw 方法,如此 draw 事件就一層一層的傳遞下去了
ps: view 有一個(gè)特殊的方法 setWillNotDraw
,如果一個(gè) view 不需要繪制內(nèi)容,即不需要重寫 onDraw
方法繪制,可以開啟這個(gè)標(biāo)記,系統(tǒng)會(huì)進(jìn)行相應(yīng)的優(yōu)化。默認(rèn)情況下,View 沒有開啟這個(gè)標(biāo)記,默認(rèn)認(rèn)為需要實(shí)現(xiàn) onDraw
方法繪制,當(dāng)我們繼承 ViewGroup
實(shí)現(xiàn)自定義控件,并且明確知道不需要具備繪制功能時(shí),可以開啟這個(gè)標(biāo)記,如果我們重寫了 onDraw
,那么要顯示的關(guān)閉這個(gè)標(biāo)記
子 view 寬高可以超過父 view?能
1. android:clipChildren = "false" 這個(gè)屬性要設(shè)置在父 view 上。代表其中的子View 可以超出屏幕。
2. 子 view 要有具體的大小,一定要比父 view 大 才能超出。比如 父 view 高度100px 子 view 設(shè)置高度 150px。子 view 比父 view 大,這樣超出的屬性才有意義。(高度可以在代碼中動(dòng)態(tài)賦值,但不能用 wrap_content / match_partent)。
3. 對父布局還有要求,要求使用 linearLayout(反正我用 RelativeLayout 是不行)。你如果必須用其他布局可以在需要超出的 view 上面套一個(gè)linearLayout 外面再套其他的布局。
4. 最外面的布局如果設(shè)置的 padding 不能超出
參考回答:
MotionEvent 是手指接觸屏幕后所產(chǎn)生的一系列事件。典型的事件類型有如下:
ACTION_DOWN:手指剛接觸屏幕
ACTION_MOVE:手指在屏幕上移動(dòng)
ACTION_UP:手指從屏幕上松開的一瞬間
ACTION_CANCELL:手指保持按下操作,并從當(dāng)前控件轉(zhuǎn)移到外層控件時(shí)觸發(fā)
正常情況下,一次手指觸摸屏幕的行為會(huì)觸發(fā)一系列點(diǎn)擊
事件,考慮如下幾種情況:
點(diǎn)擊屏幕后松開,事件序列:DOWN→UP
點(diǎn)擊屏幕滑動(dòng)一會(huì)再松開,事件序列為DOWN→MOVE→.....→MOVE→UP
參考回答:
常見開發(fā)中事件沖突的有 ScrollView
與 RecyclerView
的滑動(dòng)沖突、RecyclerView
內(nèi)嵌同時(shí)滑動(dòng)同一方向
滑動(dòng)沖突的處理規(guī)則:
滑動(dòng)沖突的實(shí)現(xiàn)方法:
onInterceptTouchEvent
方法,在內(nèi)部做出相應(yīng)的攔截。requestDisallowInterceptTouchEvent
方法參考回答:
MotionEvent.ACTION_UP
事件觸發(fā)時(shí)調(diào)用startScroll()
方法,該方法并沒有進(jìn)行實(shí)際的滑動(dòng)操作,而是記錄滑動(dòng)相關(guān)量(滑動(dòng)距離、滑動(dòng)時(shí)間)invalidate/postInvalidate()
方法,請求 View重繪,導(dǎo)致 View.draw 方法被執(zhí)行computeScroll
方法,而 computeScroll
又會(huì)去向 Scroller
獲取當(dāng)前的scrollX
和 scrollY
;然后通過 scrollTo
方法實(shí)現(xiàn)滑動(dòng);接著又調(diào)用 postInvalidate
方法來進(jìn)行第二次重繪,和之前流程一樣,如此反復(fù)導(dǎo)致 View 不斷進(jìn)行小幅度的滑動(dòng),而多次的小幅度滑動(dòng)就組成了彈性滑動(dòng),直到整個(gè)滑動(dòng)過成結(jié)束invalidate()
與 postInvalidate()
都用于刷新 View,主要區(qū)別是 invalidate()
在主線程中調(diào)用,若在子線程中使用需要配合 handler;而 postInvalidate()
可在子線程中直接調(diào)用。
SurfaceView
可在子線程進(jìn)行頁面的刷新SurfaceView
適用于被動(dòng)更新,如頻繁刷新,這是因?yàn)槿绻褂?View 頻繁刷新會(huì)阻塞主線程,導(dǎo)致界面卡頓SurfaceView
在底層已實(shí)現(xiàn)雙緩沖機(jī)制,而 View 沒有,因此 SurfaceView
更適用于需要頻繁刷新、刷新時(shí)數(shù)據(jù)處理量很大的頁面(如視頻播放界面)scollBy
內(nèi)部調(diào)用了 scrollTo
,它是基于當(dāng)前位置的相對滑動(dòng);而 scrollTo
是絕對滑動(dòng),因此如果使用相同輸入?yún)?shù)多次調(diào)用 scrollTo
方法由于 View 的初始位置是不變的,所以只會(huì)出現(xiàn)一次 View 滾動(dòng)的效果Scroller
有過度滑動(dòng)的效果a. layout(left,top,right,bottom)
:通過修改 View 四個(gè)方向的屬性值來修改 View 的坐標(biāo),從而滑動(dòng) View
b. offsetLeftAndRight() offsetTopAndBottom()
:指定偏移量滑動(dòng) view
c. LayoutParams
,改變布局參數(shù):layoutParams
中保存了 view 的布局參數(shù),可以通過修改布局參數(shù)的方式滑動(dòng) view
d. 通過動(dòng)畫來移動(dòng) view:注意安卓的平移動(dòng)畫不能改變 view 的位置參數(shù),屬性動(dòng)畫可以
e. scrollTo/scrollBy
:注意移動(dòng)的是 view 的內(nèi)容,scrollBy(50,50)
你會(huì)看到屏幕上的內(nèi)容向屏幕的左上角移動(dòng)了,這是參考對象不同導(dǎo)致的,你可以看作是它移動(dòng)的是手機(jī)屏幕,手機(jī)屏幕向右下角移動(dòng),那么屏幕上的內(nèi)容就像左上角移動(dòng)了
f. scroller:scroller
需要配置 computeScroll
方法實(shí)現(xiàn) view 的滑動(dòng),scroller
本身并不會(huì)滑動(dòng) view,它的作用可以看作一個(gè)插值器,它會(huì)計(jì)算當(dāng)前時(shí)間點(diǎn) view 應(yīng)該滑動(dòng)到的距離,然后 view 不斷的重繪,不斷的調(diào)用 computeScroll
方法,這個(gè)方法是個(gè)空方法,所以我們重寫這個(gè)方法,在這個(gè)方法中不斷的從 scroller
中獲取當(dāng)前 view 的位置,調(diào)用 scrollTo
方法實(shí)現(xiàn)滑動(dòng)的效果
(順手留下GitHub鏈接,需要獲取相關(guān)面試等內(nèi)容的可以自己去找)
https://github.com/xiangjiana/Android-MS
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。