9102年底,我對Android view的13條認識

每次到每年的年末,都會花幾天時間把今年對每一個知識點總結一下。算是對本身經驗的累積,以彌補本身的不足。把知識點彙總一下,看看自身的不足和錯誤,以便2020年再接再礪
今天先總結一下關於Android View 總結

順手留下GitHub連接,須要獲取相關面試等內容的能夠本身去找
https://github.com/xiangjiana/Android-MS

(VX:mm14525201314)android

一丶 View 的繪製流程?
  • 參考答案

View 的工做流程主要是指 measure、layout、draw 這三大流程,即測量、佈局和繪製,其中 measure 肯定 View 的測量寬/ / 高,layout 肯定 View 的最終寬/ / 高和 四個頂點的位置,而 draw 則將 View繪製到屏幕上git

View 的繪製過程遵循以下幾步github

  • 繪製背景 background.draw(canvas)
  • 繪製本身(onDraw)
  • 繪製 children(dispatchDraw)
  • 繪製裝飾(onDrawScollBars)

二丶View的事件分發制

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

View 事件傳遞分發機制?
參考回答:canvas

  • View 事件分發本質就是對 MotionEvent 事件分發的過程。即當一個 MotionEvent 發生後,系統將這個點擊事件傳遞到一個具體的 View 上
  • 點擊事件的傳遞順序: Activity( Window)→ViewGroup→ View
  • 事件分發過程由三個方法共同完成佈局

    • dispatchTouchEvent:用來進行事件的分發。若是事件可以傳遞給當前 View,那麼此方法必定會被調用,返回結果受當前 View 的 onTouchEvent 和下級View 的 dispatchTouchEvent 方法的影響,表示是否消耗當前事件
    • onInterceptTouchEvent:在上述方法內部調用,對事件進行攔截。該方法只在 ViewGroup 中有,View(不包含 ViewGroup)是沒有的。一旦攔截,則執行 ViewGrouponTouchEvent,在 ViewGroup 中處理事件,而不接着分發給 View。且只調用一次,返回結果表示是否攔截當前事件
    • onTouchEvent: 在 dispatchTouchEvent 方法中調用,用來處理點擊事件,返回結果表示是否消耗當前事件
三丶 View的加載流程

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

四丶 自定義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 帶有滑動嵌套情形時,處理好滑動衝突spa

五丶View 的 measure layout 和 draw

在上邊的分析中咱們知道,View 繪製流程的入口在 ViewRootImplperformTraversals 方法,在方法中首先調用 performMeasure 方法,傳入一個childWidthMeasureSpecchildHeightMeasureSpec 參數,這兩個參數表明的是DecorViewMeasureSpec 值,這個 MeasureSpec 值由窗口的尺寸和 DecorViewLayoutParams 決定,最終調用 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 有沒有設置背景,若是沒有設置背景,就返回設置的 minWidthminHeight,這兩個值若是沒有設置默認就是 0,若是 view 設置了背景,就取 minWidthminHeight和背景這個 drawable 固有寬或者高中的最大值返回
2.當 MeasureSpec 的 mode 是 AT_MOST 和 EXACTLY,此時 view 的寬高都返回從 MeasureSpec 中獲取到的 size 值,這個值的肯定見上邊的分析。所以若是要經過繼承 view 實現自定義 view,必定要重寫 onMeasure 方法對 wrap_conten 屬性作處理,不然,他的 match_parentwrap_content 屬性效果就是同樣的

layout:
layout 方法的做用是用來肯定 view 自己的位置,onLayout 方法用來肯定全部子元素的位置,當 ViewGroup 的位置肯定以後,它在 onLayout 中會遍歷全部的子元素並調用其 layout 方法,在子元素的 layout 方法中 onLayout 方法又會被調用。layout 方法的流程是,首先經過 setFrame 方法肯定 view 四個頂點的位置,而後view 在父容器中的位置也就肯定了,接着會調用onLayout 方法,肯定子元素的位置,onLayout 是個空方法,須要繼承者去實現。

getMeasuredHeightgetHeight方法有什麼區別?
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 是什麼?包含幾種事件?什麼條件下會產生?

參考回答:
MotionEvent 是手指接觸屏幕後所產生的一系列事件。典型的事件類型有以下:

ACTION_DOWN:手指剛接觸屏幕
ACTION_MOVE:手指在屏幕上移動
ACTION_UP:手指從屏幕上鬆開的一瞬間
ACTION_CANCELL:手指保持按下操做,並從當前控件轉移到外層控件時觸發

正常狀況下,一次手指觸摸屏幕的行爲會觸發一系列點擊
事件,考慮以下幾種狀況:

點擊屏幕後鬆開,事件序列:DOWN→UP
點擊屏幕滑動一會再鬆開,事件序列爲DOWN→MOVE→.....→MOVE→UP
七丶如何解決View 的事件衝突

參考回答:
常見開發中事件衝突的有 ScrollViewRecyclerView 的滑動衝突、RecyclerView 內嵌同時滑動同一方向

滑動衝突的處理規則:

  • 對於因爲外部滑動和內部滑動方向不一致致使的滑動衝突,能夠根據滑動的方向判斷誰來攔截事件。
  • 對於因爲外部滑動方向和內部滑動方向一致致使的滑動衝突,能夠根據業務需求,規定什麼時候讓外部View 攔截事件,什麼時候由內部 View 攔截事件。
  • 對於上面兩種狀況的嵌套,相對複雜,可一樣根據需求在業務上找到突破點。

滑動衝突的實現方法:

  • 外部攔截法: 指點擊事件都先通過父容器的攔截處理,若是父容器須要此事件就攔截,不然就不攔截。

具體方法:須要重寫父容器的onInterceptTouchEvent 方法,在內部作出相應的攔截。

  • 內部攔截法: 指父容器不攔截任何事件,而將全部的事件都傳遞給子容器,若是子容器須要此事件就直接消耗,不然就交由父容器進行處理。

具體方法:須要配合requestDisallowInterceptTouchEvent 方法

八丶Scroller 是怎麼實現View的彈性滑動

參考回答:

  • MotionEvent.ACTION_UP 事件觸發時調用startScroll()方法,該方法並無進行實際的滑動操做,而是記錄滑動相關量(滑動距離、滑動時間)
  • 接着調用 invalidate/postInvalidate()方法,請求 View重繪,致使 View.draw 方法被執行
  • 當 View 重繪後會在 draw 方法中調用 computeScroll 方法,而 computeScroll 又會去向 Scroller 獲取當前的scrollXscrollY;而後經過 scrollTo 方法實現滑動;接着又調用 postInvalidate 方法來進行第二次重繪,和以前流程同樣,如此反覆致使 View 不斷進行小幅度的滑動,而屢次的小幅度滑動就組成了彈性滑動,直到整個滑動過成結束

九丶 invalidate()和 postInvalidate() 的區別 ?

invalidate()postInvalidate()都用於刷新 View,主要區別是 invalidate()在主線程中調用,若在子線程中使用須要配合 handler;而 postInvalidate()可在子線程中直接調用。

十丶 SurfaceView 和 View 的區別?
  • View 須要在 UI 線程對畫面進行刷新,而 SurfaceView 可在子線程進行頁面的刷新
  • View 適用於主動更新的狀況,而 SurfaceView 適用於被動更新,如頻繁刷新,這是由於若是使用 View 頻繁刷新會阻塞主線程,致使界面卡頓
  • SurfaceView 在底層已實現雙緩衝機制,而 View 沒有,所以 SurfaceView 更適用於須要頻繁刷新、刷新時數據處理量很大的頁面(如視頻播放界面)
十一丶 scrollTo()和 scollBy() 的區別?
  • scollBy 內部調用了 scrollTo,它是基於當前位置的相對滑動;而 scrollTo 是絕對滑動,所以若是使用相同輸入參數屢次調用 scrollTo 方法因爲 View 的初始位置是不變的,因此只會出現一次 View 滾動的效果
  • 二者都只能對 View 內容的滑動,而非使 View 自己滑動。可使用 Scroller 有過分滑動的效果
十二丶自定義View 如何考慮機型適配?
  • 合理使用 warp_content,match_parent
  • 儘量的是使用 RelativeLayout
  • 針對不一樣的機型,使用不一樣的佈局文件放在對應的目錄下,android 會自動匹配。
  • 儘可能使用點 9 圖片。
  • 使用與密度無關的像素單位 dp,sp
  • 引入 android 的百分比佈局。
  • 切圖的時候切大分辨率的圖,應用到佈局當中。在小分辨率的手機上也會有很好的顯示效果。
十三丶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 方法實現滑動的效果

順手留下GitHub連接,須要獲取相關面試等內容的能夠本身去找
https://github.com/xiangjiana/Android-MS

相關文章
相關標籤/搜索