目錄
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系列)