溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

9102年末,我對Android view的13條認(rèn)識

發(fā)布時(shí)間:2020-06-03 16:44:33 來源:網(wǎng)絡(luò) 閱讀:264 作者:Android丶VG 欄目:移動(dòng)開發(fā)

每次到每一年的年底,都會(huì)花幾天時(shí)間把今年對每個(gè)知識點(diǎn)總結(jié)一下。算是對自己經(jīng)驗(yàn)的累積,以彌補(bǔ)自己的不足。把知識點(diǎn)匯總一下,看看自身的不足和錯(cuò)誤,以便2020年再接再厲

今天先總結(jié)一下關(guān)于Android View 總結(jié)

順手留下GitHub鏈接,需要獲取相關(guān)面試等內(nèi)容的可以自己去找
https://github.com/xiangjiana/Android-MS
9102年末,我對Android view的13條認(rèn)識
(VX:mm14525201314)

一丶 View 的繪制流程?
  • 參考答案
    View 的工作流程主要是指 measure、layout、draw 這三大流程,即測量、布局和繪制,其中 measure 確定 View 的測量寬/ / 高,layout 確定 View 的最終寬/ / 高和 四個(gè)頂點(diǎn)的位置,而 draw 則將 View繪制到屏幕上

    View 的繪制過程遵循如下幾步

  • 繪制背景 background.draw(canvas)
  • 繪制自己(onDraw)
  • 繪制 children(dispatchDraw)
  • 繪制裝飾(onDrawScollBars)
    9102年末,我對Android view的13條認(rèn)識
二丶View的事件分發(fā)制

點(diǎn)擊事件產(chǎn)生后,首先傳遞給 Activity 的 dispatchTouchEvent 方法,通過PhoneWindow 傳遞給 DecorView,然后再傳遞給根 ViewGroup,進(jìn)入 ViewGroupdispatchTouchEvent 方法,執(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ī)制?
參考回答:

  • View 事件分發(fā)本質(zhì)就是對 MotionEvent 事件分發(fā)的過程。即當(dāng)一個(gè) MotionEvent 發(fā)生后,系統(tǒng)將這個(gè)點(diǎn)擊事件傳遞到一個(gè)具體的 View 上
  • 點(diǎn)擊事件的傳遞順序: Activity( Window)→ViewGroup→ View
  • 事件分發(fā)過程由三個(gè)方法共同完成
    • 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í)行 ViewGrouponTouchEvent,在 ViewGroup 中處理事件,而不接著分發(fā)給 View。且只調(diào)用一次,返回結(jié)果表示是否攔截當(dāng)前事件
    • onTouchEvent: 在 dispatchTouchEvent 方法中調(diào)用,用來處理點(diǎn)擊事件,返回結(jié)果表示是否消耗當(dāng)前事件
三丶 View的加載流程

View 隨著 Activity 的創(chuàng)建而加載,startActivity 啟動(dòng)一個(gè) Activity 時(shí),在
ActivityThreadhandleLaunchActivity 方法中會(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ù)的,將DecorViewViewRoot 關(guān)聯(lián)之后,ViewRootImplrequestLayout會(huì)被調(diào)用以完成初步布局,通過scheduleTraversals方法向主線程發(fā)送消息請求遍歷,最終調(diào)用ViewRootImplperformTraversals方法,這個(gè)方法會(huì)執(zhí)行 View 的 measure layout 和 draw 流程

四丶 自定義view 需要注意的幾點(diǎn)

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 的 measure layout 和 draw

在上邊的分析中我們知道,View 繪制流程的入口在 ViewRootImplperformTraversals 方法,在方法中首先調(diào)用 performMeasure 方法,傳入一個(gè)childWidthMeasureSpecchildHeightMeasureSpec 參數(shù),這兩個(gè)參數(shù)代表的是DecorViewMeasureSpec 值,這個(gè) MeasureSpec 值由窗口的尺寸和 DecorViewLayoutParams 決定,最終調(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)行說明

1.父布局是 EXACTLY 模式:

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

2.父布局是 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

3.父布局是 UNSPECIFIED 模式:

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è)置的 minWidthminHeight,這兩個(gè)值如果沒有設(shè)置默認(rèn)就是 0,如果 view 設(shè)置了背景,就取 minWidthminHeight和背景這個(gè) drawable 固有寬或者高中的最大值返回
2.當(dāng) MeasureSpec 的 mode 是 AT_MOST 和 EXACTLY,此時(shí) view 的寬高都返回從MeasureSpec 中獲取到的 size 值,這個(gè)值的確定見上邊的分析。因此如果要通過繼承 view 實(shí)現(xiàn)自定義 view,一定要重寫 onMeasure 方法對 wrap_conten 屬性做處理,否則,他的 match_parentwrap_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)。

getMeasuredHeightgetHeight方法有什么區(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 是什么?包含幾種事件?什么條件下會(huì)產(chǎn)生?

參考回答:
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

七丶如何解決View 的事件沖突

參考回答:
常見開發(fā)中事件沖突的有 ScrollViewRecyclerView 的滑動(dòng)沖突、RecyclerView 內(nèi)嵌同時(shí)滑動(dòng)同一方向

滑動(dòng)沖突的處理規(guī)則:

  • 對于由于外部滑動(dòng)和內(nèi)部滑動(dòng)方向不一致導(dǎo)致的滑動(dòng)沖突,可以根據(jù)滑動(dòng)的方向判斷誰來攔截事件。
  • 對于由于外部滑動(dòng)方向和內(nèi)部滑動(dòng)方向一致導(dǎo)致的滑動(dòng)沖突,可以根據(jù)業(yè)務(wù)需求,規(guī)定何時(shí)讓外部View 攔截事件,何時(shí)由內(nèi)部 View 攔截事件。
  • 對于上面兩種情況的嵌套,相對復(fù)雜,可同樣根據(jù)需求在業(yè)務(wù)上找到突破點(diǎn)。

滑動(dòng)沖突的實(shí)現(xiàn)方法:

  • 外部攔截法: 指點(diǎn)擊事件都先經(jīng)過父容器的攔截處理,如果父容器需要此事件就攔截,否則就不攔截。
    具體方法:需要重寫父容器的onInterceptTouchEvent 方法,在內(nèi)部做出相應(yīng)的攔截。
  • 內(nèi)部攔截法: 指父容器不攔截任何事件,而將所有的事件都傳遞給子容器,如果子容器需要此事件就直接消耗,否則就交由父容器進(jìn)行處理。
    具體方法:需要配合requestDisallowInterceptTouchEvent 方法
八丶Scroller 是怎么實(shí)現(xiàn)View的彈性滑動(dòng)

參考回答:

  • MotionEvent.ACTION_UP 事件觸發(fā)時(shí)調(diào)用startScroll()方法,該方法并沒有進(jìn)行實(shí)際的滑動(dòng)操作,而是記錄滑動(dòng)相關(guān)量(滑動(dòng)距離、滑動(dòng)時(shí)間)
  • 接著調(diào)用 invalidate/postInvalidate()方法,請求 View重繪,導(dǎo)致 View.draw 方法被執(zhí)行
  • 當(dāng) View 重繪后會(huì)在 draw 方法中調(diào)用 computeScroll 方法,而 computeScroll 又會(huì)去向 Scroller 獲取當(dāng)前的scrollXscrollY;然后通過 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é)束
    9102年末,我對Android view的13條認(rèn)識
    九丶 invalidate()和 postInvalidate() 的區(qū)別 ?

    invalidate()postInvalidate()都用于刷新 View,主要區(qū)別是 invalidate()在主線程中調(diào)用,若在子線程中使用需要配合 handler;而 postInvalidate()可在子線程中直接調(diào)用。

十丶 SurfaceView 和 View 的區(qū)別?
  • View 需要在 UI 線程對畫面進(jìn)行刷新,而 SurfaceView 可在子線程進(jìn)行頁面的刷新
  • View 適用于主動(dòng)更新的情況,而 SurfaceView 適用于被動(dòng)更新,如頻繁刷新,這是因?yàn)槿绻褂?View 頻繁刷新會(huì)阻塞主線程,導(dǎo)致界面卡頓
  • SurfaceView 在底層已實(shí)現(xiàn)雙緩沖機(jī)制,而 View 沒有,因此 SurfaceView 更適用于需要頻繁刷新、刷新時(shí)數(shù)據(jù)處理量很大的頁面(如視頻播放界面)
十一丶 scrollTo()和 scollBy() 的區(qū)別?
  • 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)的效果
  • 兩者都只能對 View 內(nèi)容的滑動(dòng),而非使 View 本身滑動(dòng)??梢允褂?Scroller 有過度滑動(dòng)的效果
十二丶自定義View 如何考慮機(jī)型適配?
  • 合理使用 warp_content,match_parent
  • 盡可能的是使用 RelativeLayout
  • 針對不同的機(jī)型,使用不同的布局文件放在對應(yīng)的目錄下,android 會(huì)自動(dòng)匹配。
  • 盡量使用點(diǎn) 9 圖片。
  • 使用與密度無關(guān)的像素單位 dp,sp
  • 引入 android 的百分比布局。
  • 切圖的時(shí)候切大分辨率的圖,應(yīng)用到布局當(dāng)中。在小分辨率的手機(jī)上也會(huì)有很好的顯示效果。
十三丶View 的滑動(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
9102年末,我對Android view的13條認(rèn)識

向AI問一下細(xì)節(jié)

免責(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)容。

AI