1.Android 花費5年 自定義view面試題都在這 (5分鐘入門到牛逼)面試+源碼+demo 深圳一線大廠都在學(xué)

目錄

1.View繪制流程?

2.View? Window ViewRootImp之間的關(guān)系?

3.控件的寬高和哪些因素有關(guān)系?

4.Android的wrap_content是如何計算的?

5.為什么你的自定義View wrap_content不起作用?

6.說下Measurepec這個類

7.invalidate()和postInvalidate()和requestlayout的使用與區(qū)別

8.自定義View執(zhí)行invalidate()方法,為什么有時候不會回調(diào)onDraw()

9.如何獲取 View 寬高?

10.一個view的寬和高是由什么決定!

11.getWidth() ( getHeight())與 getMeasuredWidth() (getMeasuredHeight())獲取的寬 (高)有什么區(qū)別?

12.Android消息機制原理——為什么不能在子線程更新UI?

13.如何自定義View?

14.自定義View為什么有3個構(gòu)造函數(shù)

15.自定義view效率高于xml定義嗎?說明理由

16.自定義view的生命周期如何?

17.如何優(yōu)化自定義View?

18.veiw狀態(tài)的保持



自定義view十八連問

1.View繪制流程?

setContentView開始

View------Window-----ViewRootImp的setView()----ViewRootImp的RequestLayout()---performTraversals()3個方法

源碼如下:

WindowManagerImpl實現(xiàn)類:

@Override

public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {

applyDefaultToken(params)

;

mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);

}

ViewRootImpl在windowManager傳入的!

mViews.add(view);

mRoots.add(root);

mParams.add(wparams);

// do this last because it fires off messages to start doing things

try {

root.setView(view

, wparams, panelParentView);

} catch (RuntimeException e) {

// BadTokenException or InvalidDisplayException, clean up.

if (index >= 0) {

removeViewLocked(index

, true);

}

throw e;

}

ViewRootImpl的setView方法:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {

然后調(diào)用ViewRootImpl類都requestLayout()方法

ViewRootImpl是繼承ViewParent

ublic final class ViewRootImpl implements ViewParent,

View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {

View系統(tǒng)的繪制流程會從ViewRootImpl的performTraversals()方法中開始,performTraversals()的意思是:執(zhí)行遍歷

Traversals:遍歷的意思

ViewRoot中包含了窗口的總?cè)萜鱀ecorView,ViewRoot中的performTraversal()方法會依次調(diào)用decorView的measure、layout、draw方法,從而完成view樹的繪制。

measure()方法,layout(),draw()三個方法主要存放了一些標(biāo)識符,來判斷每個View是否需要再重新測量,布局或者繪制

View樹的繪制是一個遞歸的過程,從ViewGroup一直向下遍歷,直到所有的子view都完成繪制

View的整個繪制流程可以分為以下三個階段:

measure: 判斷是否需要重新計算View的大小,需要的話則計算;每個View的控件的實際寬高都是由父視圖和本身視圖決定的

layout: 判斷是否需要重新計算View的位置,需要的話則計算;

draw: 判斷是否需要重新繪制View,需要的話則重繪制。

measure()、layout()、draw(),其內(nèi)部又分別包含了onMeasure()、onLayout()、onDraw()三個子方法。

? ?其他:

View的measure方法是final的,不允許重載,View子類只能重載onMeasure來完成自己的測量邏輯。

最頂層DecorView測量時的MeasureSpec是由ViewRootImpl中g(shù)etRootMeasureSpec方法確定的(LayoutParams寬高參數(shù)均為MATCH_PARENT,specMode是EXACTLY,specSize為物理屏幕大?。?。

ViewGroup類提供了measureChild,measureChild和measureChildWithMargins方法,簡化了父子View的尺寸計算。

只要是ViewGroup的子類就必須要求LayoutParams繼承子MarginLayoutParams,否則無法使用layout_margin參數(shù)。

使用View的getMeasuredWidth()和getMeasuredHeight()方法來獲取View測量的寬高,必須保證這兩個方法在onMeasure流程之后被調(diào)用才能返回有效值。

2.View? Window ViewRootImp之間的關(guān)系?

3.控件的寬高和哪些因素有關(guān)系?

測量邏輯:

? ? ?如果子視圖對于?Measure?得到的大小不滿意的時候,父視圖會介入并設(shè)置測量規(guī)則進行第二次

measure。比如,父視圖可以先根據(jù)未給定的?dimension?去測量每一個子視圖, 如果最終子視圖的未約束尺寸太大或者太小的時候,父視圖就會使用一個確切的 大小再次對子視圖進行?measure。

如果子視圖對于?Measure?得到的大小不滿意的時候,父視圖會介入并設(shè)置測量規(guī)則進行第二次measure。

比如,父視圖可以先根據(jù)未給定的?dimension?去測量每一個子視圖, 如果最終子視圖的未約束尺寸太大或者太小的時候,父視圖就會使用一個確切的 大小再次對子視圖進行?measure。

measure?過程傳遞尺寸的兩個類

1.ViewGroup.LayoutParams?(View?自身的布局參數(shù))?

2.MeasureSpecs?類(父視圖對子視圖的測量要求)

當(dāng)不需要繪制?Layer?的 時候第二步和第五步會跳過。因此在繪制的時候,能省的?layer?盡可省,可以

View的measure方法是final的,不允許重載,View子類只能重載onMeasure來完成自己的測量邏輯。

最頂層DecorView測量時的MeasureSpec是由ViewRootImpl中g(shù)etRootMeasureSpec方法確定的(LayoutParams寬高參數(shù)均為MATCH_PARENT,specMode是EXACTLY,specSize為物理屏幕大小)。

ViewGroup類提供了measureChild,measureChild和measureChildWithMargins方法,簡化了父子View的尺寸計算。

只要是ViewGroup的子類就必須要求LayoutParams繼承子MarginLayoutParams,否則無法使用layout_margin參數(shù)。

使用View的getMeasuredWidth()和getMeasuredHeight()方法來獲取View測量的寬高,必須保證這兩個方法在onMeasure流程之后被調(diào)用才能返回有效值。

4.請簡述一下ViewGroup的繪制流程?

Android的wrap_content是如何計算的?

? ? ? ?ViewGroup去會管理其子View,包括管理負責(zé)子View的顯示大小。當(dāng)ViewGroup的大小為wrap_content,ViewGroup就需要對子View進行遍歷,以便獲得所有子View的大小,從而來決定自己的大小。

? ? ?在其他模式下會通過具體的指定值來設(shè)置自身的大小。? ? ? ?VIewGroup在測量時會通過遍歷所有子View,從而調(diào)用子View的Measure方法來獲得每個子View的測量結(jié)果,前面所說的對View的測量,就是在這里進行的。當(dāng)子View測量完畢后,就需要將子View放到合適的位置,這個過程就是View的Layout過程。ViewGroup在執(zhí)行Layout過程時,同樣是遍歷來調(diào)用子View的Layout方法,并指定其具體顯示的位置,從而來決定其布局位置。

在自定義ViewGroup時,通常會去重寫onLayout()方法來控制其子View顯示位置的邏輯。同樣,如果需要支持wrap_content屬性,那么它必須要還要重寫onMeasure()方法,這點與View是相同的。

下面簡述一下ViewGroup繪制的邏輯:通常ViewGroup情況下不需要繪制,因為本身就沒什么可繪制的東西,如果不是指定了ViewGroup的背景顏色,那么ViewGroup的onDraw()方法都不會被調(diào)用。ViewGroup會使用dispatchDraw()方法來繪制其子View,其過程同樣是通過遍歷所有子View,并調(diào)用子View的繪制方法來完成繪制工作。

源碼分析如下:

protected void measureChildWithMargins(View child,

int

parentWidthMeasureSpec, int widthUsed,

int

parentHeightMeasureSpec, int heightUsed) {

final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

final int

childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,

mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin

+ widthUsed, lp.width);

final int

childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,

mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin

+ heightUsed, lp.height);

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

}

getChildMeasureSpec();

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {

int specMode = MeasureSpec.getMode(spec);

int

specSize = MeasureSpec.getSize(spec);

int

size = Math.max(0, specSize - padding);

int

resultSize = 0;

int

resultMode = 0;

switch

(specMode) {

// Parent has imposed an exact size on us

case MeasureSpec.EXACTLY:

if (childDimension >= 0) {

resultSize = childDimension

;

resultMode = MeasureSpec.EXACTLY;

} else if (childDimension == LayoutParams.MATCH_PARENT) {

// Child wants to be our size. So be it.

resultSize = size;

resultMode = MeasureSpec.EXACTLY;

} else if (childDimension == LayoutParams.WRAP_CONTENT) {

// Child wants to determine its own size. It can't be

// bigger than us.

resultSize = size;

resultMode = MeasureSpec.AT_MOST;

}

break;

// Parent has imposed a maximum size on us

case MeasureSpec.AT_MOST:

if (childDimension >= 0) {

// Child wants a specific size... so be it

resultSize = childDimension;

resultMode = MeasureSpec.EXACTLY;

} else if (childDimension == LayoutParams.MATCH_PARENT) {

// Child wants to be our size, but our size is not fixed.

// Constrain child to not be bigger than us.

resultSize = size;

resultMode = MeasureSpec.AT_MOST;

} else if (childDimension == LayoutParams.WRAP_CONTENT) {

// Child wants to determine its own size. It can't be

// bigger than us.

resultSize = size;

resultMode = MeasureSpec.AT_MOST;

}

break;

// Parent asked to see how big we want to be

case MeasureSpec.UNSPECIFIED:

if (childDimension >= 0) {

// Child wants a specific size... let him have it

resultSize = childDimension;

resultMode = MeasureSpec.EXACTLY;

} else if (childDimension == LayoutParams.MATCH_PARENT) {

// Child wants to be our size... find out how big it should

// be

resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;

resultMode = MeasureSpec.UNSPECIFIED;

} else if (childDimension == LayoutParams.WRAP_CONTENT) {

// Child wants to determine its own size.... find out how

// big it should be

resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;

resultMode = MeasureSpec.UNSPECIFIED;

}

break;

}

//noinspection ResourceType

return MeasureSpec.makeMeasureSpec(resultSize, resultMode);

}

case MeasureSpec.EXACTLY:

if (childDimension >= 0) {

resultSize = childDimension

;

resultMode = MeasureSpec.EXACTLY;

} else if (childDimension == LayoutParams.MATCH_PARENT) {

// Child wants to be our size. So be it.

resultSize = size;

resultMode = MeasureSpec.EXACTLY;

} else if (childDimension == LayoutParams.WRAP_CONTENT) {

// Child wants to determine its own size. It can't be

// bigger than us.

resultSize = size;

resultMode = MeasureSpec.AT_MOST;

}

break;

// Parent has imposed a maximum size on us

case MeasureSpec.AT_MOST:

if (childDimension >= 0) {

// Child wants a specific size... so be it

resultSize = childDimension;

resultMode = MeasureSpec.EXACTLY;

} else if (childDimension == LayoutParams.MATCH_PARENT) {

// Child wants to be our size, but our size is not fixed.

// Constrain child to not be bigger than us.

resultSize = size;

resultMode = MeasureSpec.AT_MOST;

} else if (childDimension == LayoutParams.WRAP_CONTENT) {

// Child wants to determine its own size. It can't be

// bigger than us.

resultSize = size;

resultMode = MeasureSpec.AT_MOST;

}

break;

EXACTLY:(確切的數(shù)據(jù)),

如果當(dāng)前控件的寬高是確切值就用這個值,否則由父元素決定。

如果子控件是match_parenter。那么就把父元素的大小給子控件。

如果子控件是wrap_content。父元素的大小給子控件

如果父類是Wrap_content

如果父view不限制,就按自己的背景大小或者最小值來顯示,如果父view有限制,就按父view給的尺寸來顯示。

按照這個邏輯,如果要自己寫一個自定義View,大小可以在布局中確定的話,一般不用再重新onMeasure 再做什么工作了。

但是如果自己的自定義View在布局中使用WRAP_CONTENT,并且內(nèi)容大小并不確定的話,還是要根據(jù)自己的顯示邏輯做一些工作的。

比如,自己寫一個顯示圖片的控件,布局中使用WRAP_CONTENT,那么根據(jù)以上的邏輯梳理,父view很可能就扔給你一個尺寸模式:大小是父view本身的大小,模式是MeasureSpec.AT_MOST;

這樣的話即使你布局里寫的是WRAP_CONTENT,你也會使用父view建議給你的尺寸,占滿父view全部的空間了,即使你的圖片并沒有那么大~是不是會很奇怪?

所以,一般情況下,展示內(nèi)容尺寸不確定的自定義View,onMeasure可以作如下類似的邏輯:

5.為什么你的自定義View wrap_content不起作用?

舉一個實例:

extend?view ,不寫onMeasure? ,會怎么顯示

如果自定義View沒有重寫onMeasure函數(shù),就看viewGroup里面的源碼

?extent viewGroup?不寫onMeasure? ?不寫onMeasure,不可以顯示

模式是由父布局和自己決定的。

比如:父親是wrapcontent,就算子布局是match_parent,這個時候測量模式還是at_most

? ? ? ? ? 父親是match_parent,子布局是match_parent,這個時候測量模式還是exactly

其他結(jié)論

1.繼承view,子布局即使在xml中有寬高,不寫onMeasure,可以顯示

2.View測量的時候,默認是EXACTLY模式,你不重寫OnMeasure方法,即使設(shè)置wrap_content屬性,他也是填充父容器。(不是viewgroup)

3.繼承ViewGroup,子布局即使在xml中有寬高,不寫onMeasure,不可以顯示? ,必須重寫ononMeasure

4.繼承LinearyLayout,子布局即使在xml中有寬高,不寫onMeasure,可以顯示

Demo地址:https://github.com/pengcaihua123456/shennandadao/tree/master

View的測量大小與 wrap_content

? ? ? ? ? 先說結(jié)論:默認情況下,當(dāng)父布局為 wrap_content 或者 match_parent 時,無論子 view(view 或者 viewgroup) 是wrap_content 還是 match_parent,最終的效果都是 match_parent。也就是 子 view 會占據(jù)父布局中剩下的所有空間。

實戰(zhàn)二:

有這樣一個需求?一個父類寬高,然后一個imageview要完全填充父類?一個surfaceview可以完成填充父類。

1.父類為wrap_content,子類也未wrap_content

那么子控件的寬高是多少?

當(dāng)ViewGroup的大小為wrap_content,ViewGroup就需要對子View進行遍歷,以便獲得所有子View的大小,從而來決定自己的大小。

發(fā)現(xiàn):子view會填充父控件。

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:layout_gravity="center_horizontal"

android:orientation="vertical"

android:layout_marginRight="@dimen/dp_16"

android:layout_marginLeft="@dimen/dp_16"

android:layout_marginTop="@dimen/dp_16"

android:layout_marginBottom="@dimen/dp_16"

tools:background="@color/color_eeeeee">

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:background="@color/black">

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:background="@color/red">

2.父類為wrap_content,子類為match_parent

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:background="@color/black">

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:background="@color/red">

那么子控件的寬高是多少?

和上面一樣,填充父布局

3.父類為50dp,子類為wrap_content

那么子控件的寬高是多少?

android:layout_width="@dimen/dp_60"

android:layout_height="@dimen/dp_60"

android:layout_gravity="center"

android:background="@color/black">

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_gravity="center"

android:background="@color/red">

和上面一樣,填充父布局

4.父類為wrap_content,子類為80dp

子控件說了算

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_gravity="center"

android:background="@color/green">

android:layout_width="@dimen/dp_50"

android:layout_height="@dimen/dp_50"

android:layout_gravity="center"

android:background="@color/red">

實戰(zhàn)3:控件的大小怎么調(diào)試都沒有用,外面少了一層父布局。首先View必須存在于一個布局中???、

1.progrossbar的高度問題

2.linearylayout的高度問題

實戰(zhàn)分析:

3.場景分析:通過一個viewPager自定義banner?? ? ?發(fā)現(xiàn) viewPager wrap的高度不生效

原理是:viewPager重寫了onMesure。固定了自己的高度。然后測量了子view

4.類似的情況,recyleView里面的item高度問題。同樣可以

?onmesure什么時候觸發(fā):

1.父類調(diào)用一次

2.onlayout的時候又去調(diào)用次

viewpager本來是用來切換整個屏幕的。不是用來做banner的。

所以高度要自己測量高度。

測量原理:樹的深度變量。

解決辦法:重寫onMesure方法

先度量子view然后再度量自己。

這樣是不正確的,要用到LayoutParams才行

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int height = 0;

for

(int i = 0; i < getChildCount(); i++) {

View child = getChildAt(i)

;

child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));

int

h = child.getMeasuredHeight();

if

(h > height) height = h;

}

heightMeasureSpec = MeasureSpec.makeMeasureSpec(height

, MeasureSpec.EXACTLY);

super

.onMeasure(widthMeasureSpec, heightMeasureSpec);

一個viewGroup。父類給他一個參考值,同時要先度量子view。才能確定

有點像view的時間分發(fā)。

通過源碼發(fā)現(xiàn):測量自己之前先測量自己都子view

ViewPager源碼:

setMeasuredDimension:測量自己

LayoutParams:子空間的參數(shù)測量

MeasureSpec.makeMeasureSpec(widthSize, widthMode);? 測量的計算模式

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

// For simple implementation, our internal size is always 0.

// We depend on the container to specify the layout size of

// our view.? We can't really know what it is since we will be

// adding and removing different arbitrary views and do not

// want the layout to change as this happens.

setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),

getDefaultSize(0, heightMeasureSpec));

final int

measuredWidth = getMeasuredWidth();

final int

maxGutterSize = measuredWidth / 10;

mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);

// Children are just made to fill our space.

int childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight();

int

childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();

/*

* Make sure all children have been properly measured. Decor views first.

* Right now we cheat and make this less complicated by assuming decor

* views won't intersect. We will pin to edges based on gravity.

*/

int size = getChildCount();

for

(int i = 0; i < size; ++i) {

final View child = getChildAt(i);

if

(child.getVisibility() != GONE) {

final LayoutParams lp = (LayoutParams) child.getLayoutParams();

if

(lp != null && lp.isDecor) {

final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;

final int

vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;

int

widthMode = MeasureSpec.AT_MOST;

int

heightMode = MeasureSpec.AT_MOST;

boolean

consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;

boolean

consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT;

if

(consumeVertical) {

widthMode = MeasureSpec.

EXACTLY;

} else if (consumeHorizontal) {

heightMode = MeasureSpec.

EXACTLY;

}

int widthSize = childWidthSize;

int

heightSize = childHeightSize;

if

(lp.width != LayoutParams.WRAP_CONTENT) {

widthMode = MeasureSpec.

EXACTLY;

if

(lp.width != LayoutParams.MATCH_PARENT) {

widthSize = lp.

width;

}

}

if (lp.height != LayoutParams.WRAP_CONTENT) {

heightMode = MeasureSpec.

EXACTLY;

if

(lp.height != LayoutParams.MATCH_PARENT) {

heightSize = lp.

height;

}

}

final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);

final int

heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);

child.measure(widthSpec, heightSpec);

MeasureSpec.makeMeasureSpec(widthSize, widthMode);

makeMeasuerMespect:度量計算:把參數(shù)轉(zhuǎn)成具體指

6.說下Measurepec這個類

? ? ? ?一個類,把模式和值封裝在一起,這個類在view中。是是一個類,不是一個常量

? ? ? 測量規(guī)格(MeasureSpec)?= 測量模式(mode)?+ 測量大?。╯ize)

? ? ? ?然后測量轉(zhuǎn)化為具體的數(shù)值。

MeasureSpec(View的內(nèi)部類)測量規(guī)格為int型,值由高2位規(guī)格模式specMode和低30位具體尺寸specSize組成。其中specMode只有三種值

MeasureSpec.EXACTLY //確定模式,父View希望子View的大小是確定的,由specSize決定;

MeasureSpec.AT_MOST //最多模式,父View希望子View的大小最多是specSize指定的值;

MeasureSpec.UNSPECIFIED //未指定模式,父View完全依據(jù)子View的設(shè)計值來決定;?

他們對應(yīng)的二進制值分別是:?UNSPECIFIED=00000000000000000000000000000000

EXACTLY =01000000000000000000000000000000?

AT_MOST =10000000000000000000000000000000?

由于最前面兩位代表模式,所以他們分別對應(yīng)十進制的0,1,2;?

1.EXACTLY

精確值模式,當(dāng)控件的layout_width和layout_height屬性指定為具體數(shù)值或match_parent時。

match_parent:屬于哪種?EXACTLY

2.AT_MOST

最大值模式,當(dāng)空間的寬高設(shè)置為wrap_content時。

wrap_content:屬于哪種?AT_MOST

3.UNSPECIFIED

未指定模式,View想多大就多大,通常在繪制自定義View時才會用。

決定因素:值由子View的布局參數(shù)LayoutParams父容器的MeasureSpec值共同決定。具體規(guī)則見下圖:

引申:直接繼承View的自定義View需要重寫onMeasure()并設(shè)置wrap_content時的自身大小,否則效果相當(dāng)于macth_pather:原因是因為:源代碼里面有? ? 結(jié)論:子View的MeasureSpec值根據(jù)子View的布局參數(shù)(LayoutParams)和父容器的MeasureSpec值計算得來的

7.invalidate()和postInvalidate()和requestlayout的使用與區(qū)別

?? invalidate 會先找到父類去走繪制流程,最終遍歷所有相關(guān)聯(lián)的 View ,觸發(fā)它們的?onDraw?方法進行繪制

? invalidate()得在UI線程中被調(diào)動,在工作者線程中可以通過Handler來通知UI線程進行界面更新。而postInvalidate()在工作者線程中被調(diào)用。

??requestLayout:有布局需要發(fā)生改變,需要調(diào)用requestlayout方法,如果只是刷新動畫,則只需要調(diào)用invalidate方法。

? requestLayout()方法會調(diào)用measure過程和layout過程,不會調(diào)用draw過程,也不會重新繪制任何View包括該調(diào)用者本身。

? ?onMeasure()和onLayout()要調(diào)用requestLayout()才能讓改變生效

? invalidate:View(非容器類)調(diào)用invalidate方法只會重繪自身,ViewGroup調(diào)用則會重繪整個View樹。

源碼分析:2個方法都會調(diào)用scheduleTraversals();

但是他們都有標(biāo)識位控制。

https://blog.csdn.net/a553181867/article/details/51583060

@Override

public final void invalidateChild(View child, final Rect dirty) {

final AttachInfo attachInfo = mAttachInfo;

if

(attachInfo != null && attachInfo.mHardwareAccelerated) {

// HW accelerated fast path

onDescendantInvalidated(child, child);

return;

}

ViewParent parent =

this;

if

(attachInfo != null) {

// If the child is drawing an animation, we want to copy this flag onto

// ourselves and the parent to make sure the invalidate request goes

@Override

public ViewParent invalidateChildInParent(int[] location, Rect dirty) {

checkThread()

;

if

(DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);

if

(dirty == null) {

invalidate()

;

return null;

} else if (dirty.isEmpty() && !mIsAnimating) {

return null;

}

if (mCurScrollY != 0 || mTranslator != null) {

mTempRect.set(dirty);

dirty = mTempRect;

if

(mCurScrollY != 0) {

dirty.offset(

0, -mCurScrollY);

}

if (mTranslator != null) {

mTranslator.translateRectInAppWindowToScreen(dirty);

}

if (mAttachInfo.mScalingRequired) {

dirty.inset(-

1, -1);

}

}

invalidateRectOnScreen(dirty)

;

return null;

}

private void invalidateRectOnScreen(Rect dirty) {

final Rect localDirty = mDirty;

// Add the new dirty rect to the current one

localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);

// Intersect with the bounds of the window to skip

// updates that lie outside of the visible region

final float appScale = mAttachInfo.mApplicationScale;

final boolean

intersected = localDirty.intersect(0, 0,

(int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));

if

(!intersected) {

localDirty.setEmpty()

;

}

if (!mWillDrawSoon && (intersected || mIsAnimating)) {

scheduleTraversals()

;

}

}

8.自定義View執(zhí)行invalidate()方法,為什么有時候不會回調(diào)onDraw()

調(diào)用view.invalidate(),會觸發(fā)onDraw和computeScroll()。前提是該view被附加在當(dāng)前窗口上

自定義一個ViewGroup,重寫onDraw。onDraw可能不會被調(diào)用,原因是需要先設(shè)置一個背景(顏色或圖)。

表示這個group有東西需要繪制了,才會觸發(fā)draw,之后是onDraw。

因此,一般直接重寫dispatchDraw來繪制viewGroup,自定義一個ViewGroup,dispatchDraw會調(diào)用drawChild

9.

android中View的GONE和INVISIBLE的原理

1.visible:3個方法都執(zhí)行

2.INVISIBLE:執(zhí)行2個方法,不執(zhí)行onDraw方法

3.Gone: 一個方法都不會執(zhí)行

所以,獲取寬和高不一樣

Gone:得不到寬和高

INVISIBLE可以的都寬高

發(fā)現(xiàn):

viewRoot=View.inflate(context, R.layout.tab_main_group_run, this);

getViews();

setViewsOnClick();

init();

int

webViewHeightheight = webView.getHeight();

Log.d("peng", "onLoadFinish_height" + webViewHeightheight+"view height"+viewRoot.getHeight());

int webViewHeightheight = webView.getHeight();

9.如何獲取 View 寬高?

通過View.post?()。獲取寬和高

在 onResume 中handler.post 在 View.post 后面為什么執(zhí)行反而在前面;

通過上面第2點和點3點分析可以知道View.post的在后面performTraversals中被執(zhí)行,而handler.post在performTraversals之前就被執(zhí)行

View.post() 為什么能夠獲取到 View 的寬高 ?

里面發(fā)送了一個消息,僅僅保存起來。

測量后回調(diào)用dispatchAttachedToWindow

源碼分析:

可以看出 onResume() 方法在 addView() 方法前調(diào)用

重點關(guān)注:onResume() 方法所處的位置,前后都發(fā)生了什么?

從上面總結(jié)的流程看出,onResume() 方法是由 handleResumeActivity 觸發(fā)的,而界面繪制被觸發(fā)是因為 handleResumeActivity() 中調(diào)用了wm.addView(decor, l);

10.一個view的寬和高是由什么決定!

從源碼可以看出來,子View的測量模式是由自身LayoutParam和父View的MeasureSpec來決定的。

11.問題八:getWidth() ( getHeight())與 getMeasuredWidth() (getMeasuredHeight())獲取的寬 (高)有什么區(qū)別?

getWidth()?/?getHeight():獲得View最終的寬 / 高

getMeasuredWidth()?/?getMeasuredHeight():獲得?View測量的寬 / 高

// 獲得View測量的寬 / 高? public final int getMeasuredWidth() {?

? ? ? return mMeasuredWidth & MEASURED_SIZE_MASK;?

? ? ? // measure過程中返回的mMeasuredWidth? }?

? public final int getMeasuredHeight() {?

? ? ? return mMeasuredHeight & MEASURED_SIZE_MASK;?

? ? ? // measure過程中返回的mMeasuredHeight? }? // 獲得View最終的寬 / 高? public final int getWidth() {?

? ? ? return mRight - mLeft;?

? ? ? // View最終的寬 = 子View的右邊界 - 子view的左邊界。? }?

? public final int getHeight() {?

? ? ? return mBottom - mTop;?

? ? // View最終的高 = 子View的下邊界 - 子view的上邊界。? }?

他們的值大部分時間都是相同的,但意義確是根本不一樣的,

- 首先getMeasureWidth()方法在measure()過程結(jié)束后就可以獲取到了,而getWidth()方法要在layout()過程結(jié)束后才能獲取到。

- getMeasureWidth()方法中的值是通過setMeasuredDimension()方法來進行設(shè)置的,而getWidth()方法中的值則是通過layout(left,top,right,bottom)方法設(shè)置的。

12.如果不手動設(shè)置支持padding屬性,那么padding屬性在自定義View中是不會生效的?

protected void onDraw(Canvas canvas) {? ? ? ? super.onDraw(canvas);? ? ? ? // 獲取傳入的padding值? ? ? ? final int paddingLeft = getPaddingLeft();? ? ? ? final int paddingRight = getPaddingRight();? ? ? ? final int paddingTop = getPaddingTop();? ? ? ? final int paddingBottom = getPaddingBottom();

13.

?android 源碼分析padding替代margin======3大布局性能比較的時候

14.Android消息機制原理——為什么不能在子線程更新UI?竟然崩潰了,那問題來了,到底子線程能不能更新Ui呢?

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

最重要的方法來了,mThread線程是主線程,Thread.currentThread()是當(dāng)前線程,即我們運行的子線程

假如當(dāng)前更新UI不在主線程,就會導(dǎo)致CalledFromWrongThreadException異常

由此可見,每一次刷新View都會調(diào)用ViewRootImp的checkThread()方法去檢測是否在主線程

按理來說,這樣是可以的!但是google為什么要這樣去設(shè)計呢?

(1)如果在不同的線程去控制用一個控件,由于網(wǎng)絡(luò)延時或者大量耗時操作,會使UI繪制錯亂,出了問題也很難去排查到底是哪個線程更新時出了問題;

(2)主線程負責(zé)更新,子線程負責(zé)耗時操作,能夠大大提高響應(yīng)效率

(3)UI線程非安全線程,多線程進行并發(fā)訪問有可能會導(dǎo)致內(nèi)存溢出,降低硬件使用壽命;且非線程安全不能加Lock線程鎖,否則會阻塞其他線程對View的訪問,效率就會變得低下!

自定義View問題

1.如何自定義View?

自定義view套路:

1.自定義屬性(可配置)

2.onMesure? ? ,如果是繼承view,要寫。如果是繼承button,這種就不要寫了

3.ondraw()

4.onTouch()

自定義viewgroup

1.自定義屬性(可配置)很少寫

2.onMesuare()? ? for循環(huán)測量子view。根據(jù)子view計算自己的寬和高

3.onlayout()??

4.不會ondraw,如果要實現(xiàn)用DispatchDraw()

5.一般不繼承viewGroup.而是linearyLayout,或者viewPager();

自定義控件實現(xiàn)方式:1.自定義組合控件2.繼承已有控件3.繼承View(構(gòu)造函數(shù)里獲取自定義屬性)4.繼承ViewGroup

而一些自定義View,現(xiàn)在大廠中必備的技能,頻率非常非常之高,可能每個人對自定義View的理解也不盡相同,又說可能說有三種可能說有多種,其實在大廠中用的最多的那種叫做自定義組合View。

因為大廠里不建議你直接去畫一個View,即自己去繪制的這個控件,而更建議去使用原生的或者現(xiàn)成的優(yōu)質(zhì)View,即能去組合就去組合,所以這也體現(xiàn)了自定義組合View的重要。

自定義組合View因為可以把自己的邏輯封裝到一起,這樣可以即簡潔又高效。其實有一些部門可能會專門去畫一些View,封裝這些View以及框架等,或者說有一些專門的人就做一些純繪制View。

這樣會避免一些自己畫的可能兼容性和通用性不是很好,也可能還會隱藏其他的BUG,所以說大廠中很不建議自己就畫一個View(直接繼承View和ViewGroup),因此說自定義組合View成了一個大廠的基本的一個要求

2.自定義View為什么有3個構(gòu)造函數(shù)

第一個:new 出來

第二個:xml中,findviewById ? ? ?可以查看源碼:layoutFlate,里面通過反射實現(xiàn)的。(context ,attr)

第三個:主題用到

3.自定義view效率高于xml定義嗎?說明理由

自定義view效率高于xml定義:

1、少了解析xml。

2.、自定義View 減少了ViewGroup與View之間的測量,包括父量子,子量自身,子在父中位置擺放,當(dāng)子view變化時,父的某些屬性都會跟著變化。

計算一個view的嵌套層級

private int getParents(ViewParents view){

? ? if(view.getParents() == null)?

? ? ? ? return 0;

? ? } else {

? ? return (1 + getParents(view.getParents));

? ?}

4.自定義view的生命周期如何?

View生命周期相關(guān)方法:

onFinishInflate()?當(dāng)View中所有的子控件均被映射成xml后觸發(fā)?

onMeasure(?int?,??int?)?確定所有子元素的大小?

onLayout(?boolean?,??int?,??int?,??int?,??int?)?當(dāng)View分配所有的子元素的大小和位置時觸發(fā) ????

onSizeChanged(?int?,??int?,??int?,??int?)?當(dāng)view的大小發(fā)生變化時觸發(fā) ?

onDraw(Canvas)?view渲染內(nèi)容的細節(jié) ?

onKeyDown(?int?,?KeyEvent)?有按鍵按下后觸發(fā) ?

onKeyUp(?int?,?KeyEvent)?有按鍵按下后彈起時觸發(fā) ?

onTrackballEvent(MotionEvent)?軌跡球事件 ?

onTouchEvent(MotionEvent)?觸屏事件 ?

onFocusChanged(?boolean?,??int?,?Rect)?當(dāng)View獲取或失去焦點時觸發(fā) ??

onWindowFocusChanged(?boolean?)?當(dāng)窗口包含的view獲取或失去焦點時觸發(fā) ?

onAttachedToWindow()?當(dāng)view被附著到一個窗口時觸發(fā) ?

onDetachedFromWindow()?當(dāng)view離開附著的窗口時觸發(fā),Android123提示該方法和??onAttachedToWindow()?是相反的。 ?

onWindowVisibilityChanged(?int?)?當(dāng)窗口中包含的可見的view發(fā)生變化時觸發(fā)

綜上所述:View 的關(guān)鍵生命周期為 [改變可見性] --> 構(gòu)造View --> onFinishInflate --> onAttachedToWindow --> onMeasure --> onSizeChanged --> onLayout --> onDraw --> onDetackedFromWindow

onAttachedToWindow是在第一次onDraw前調(diào)用的。也就是我們寫的View在沒有繪制出來時調(diào)用的,但只會調(diào)用一次。

onDetachedFromWindow:銷毀資源(既銷毀view)之后調(diào)用。

5.

如何優(yōu)化自定義View?

6.veiw狀態(tài)的保持

view是先父view測量子view,等子view測量完,再測量自己

首先Activity?被意外終止時,Activity?會調(diào)用onSaveInstanceState?去保存數(shù)據(jù),然后Activity?會委托Window?去保存數(shù)據(jù),接著Window?在委托它上面的頂級容器去保存數(shù)據(jù)。頂級容器是一個ViewGroup,一般來說它很可能是DecorView。

最后頂層容器再去一一通知它的子元素來保存數(shù)據(jù),這樣整個數(shù)據(jù)保存過程就完成了??梢园l(fā)現(xiàn),這是一個典型的委托思想,上層委托下層,父容器去委托子元素去處理一件事情,這種思想在Android?中有很多應(yīng)用,比如:View?的繪制過程,事件分發(fā)等都是采用類似的思想。

既然View的狀態(tài)是基于它的ID存儲的 , 因此如果一個VIew沒有ID,那么將不會被保存到container中。沒有保存的支點(id),我們也無法恢復(fù)沒有ID的view的狀態(tài),因為不知道這個狀態(tài)是屬于哪個View的。

這里需要注意一個細節(jié):想要保存View的狀態(tài),需要在XML布局文件中提供一個唯一的ID(android:id),

如果沒有設(shè)置這個ID的話,View控件的onSaveInstanceState是不會被調(diào)用的。

要保存view的狀態(tài),至少有兩點需要滿足:

view要有id

要調(diào)用setSaveEnabled(true)

都用SparseArray來存儲的

7.自定義View?如何考慮機型適配 ?

o?合理使用warp_content,match_parent

o?盡可能的是使用RelativeLayout

o?針對不同的機型,使用不同的布局文件放在對應(yīng)的目錄下,android?會自動匹配。

o?盡量使用點9?圖片。

o?使用與密度無關(guān)的像素單位dp,sp

o?引入android?的百分比布局。

o?切圖的時候切大分辨率的圖,應(yīng)用到布局當(dāng)中。在小分辨率的手機上也會有很好的顯示效果。

自定義控件不錯的地方

http://www.itdecent.cn/p/705a6cb6bfee

手把手教你寫一個完整的自定義View(非常不錯,view系列)

http://www.itdecent.cn/p/e9d8420b1b9c

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容