view是怎么被展示在手机上的?
view是怎么被展示在手機上的?
?
我們先了解下window、windowManager等相關知識:
在activity的attach方法里(我不管誰調用了,只知道這個是開始就會做的事情),我們可以看到activity有一個window(PhoneWindow)成員變量,在這里初始化的。然后接著又給這個window設置了WindowManager(其實就是contextImpl中static塊中初始化好的windowManger,系統級的,只有一個),這個windowManage又是什么呢,他是個接口,繼承了ViewManager接口(它定義了addView,updateVIewlayout,removeView三個方法)。他的實現類其實就是一個WindowManagerImpl,就是說給windowManager塞了一個WindowManagerImpl的實例,這個WindowManagerImpl里有個單例成員,是windowManagerGlobal,它里面有一些list,存放了view,viewRootImpl,windowmanager.layoutparams,它保存了當前應用程序添加的所有的View對象,已經相對應的ViewRootImpl對象和添加時使用的WindowManager.LayoutParams屬性對象。
1.1??PhoneWindow
? ? PhoneWindow是Android中的最基本的窗口系統,每個Activity 均會創建一個PhoneWindow對象,是Activity和整個View系統交互的接口。
?
1.2??DecorView
? ? DecorView是當前Activity所有View的祖先,它并不會向用戶呈現任何東西,它主要有如下幾個功能,可能不全:
A. ?Dispatch ViewRoot分發來的key、touch、trackball等外部事件;
B. ?DecorView有一個直接的子View,我們稱之為System Layout,這個View是從系統的Layout.xml中解析出的,它包含當前UI的風格,如是否帶title、是否帶process bar等??梢苑Q這些屬性為Window decorations。
C. ?作為PhoneWindow與ViewRoot之間的橋梁,ViewRoot通過DecorView設置窗口屬性。
?
?
1.3??System Layout
? ? 目前android根據用戶需求預設了幾種UI 風格,通過PhoneWindow通過解析預置的layout.xml來獲得包含有不同Window decorations的layout,我們稱之為System Layout,我們將這個System Layout添加到DecorView中,目前android提供了8種System Layout,如下圖。
? ? 預設風格可以通過PhoneWindow方法requestFeature()來設置,需要注意的是這個方法需要在setContentView()方法調用之前調用。
?
1.4??Content Parent
? ? Content Parent這個ViewGroup對象才是真真正正的ContentView的parent,我們的ContentView終于找到了寄主,它其實對應的是System Layout中的id為”content”的一個FrameLayout。這個FrameLayout對象包括的才是我們的Activity的layout(每個System Layout都會有這么一個id為”content”的一個FrameLayout)。
?
?
?
我們先看看activity的onCreate里的setContentView方法,在這個方法里:
public void setContentView(View view) {getWindow().setContentView(view); //這里先獲得phoneWindow
initWindowDecorActionBar();
}
在phoneWindow的setContentView里:
好。我們開始看setContentView里面一個重要的部分就是installDecor();
這個類創建了DecorView,這是一個什么東西呢?其實自身就是一個frameLayout,就是如果我們看activity層級結構的時候
可以看到最上層是個decorView,然后是一個linearlayout(順帶說一下: 會根據feattures得到窗口修飾文件布局,這個其實就是圖上的linearLayout,并add到decorView中 還記得我們平時寫應用Activity時設置的theme或者feature嗎(全屏啥的,NoTitle等)?我們一般是不是通過XML的android:theme屬性或者java的requestFeature()方法來設置的呢?譬如: 通過java文件設置: requestWindowFeature(Window.FEATURE_NO_TITLE); 通過xml文件設置: android:theme="@android:style/Theme.NoTitleBar"
對的,其實我們平時requestWindowFeature()設置的值就是在這里通過getLocalFeature()獲取的;而android:theme屬性也是通過這里的getWindowStyle()獲取的。
),Linearlayout下面有一個id為content的frameLayout,和一個action_mode_bar_stub的ViewStub(性能優化之ViewStub,通過inflate后展示)。接著再看,setContentView里執行了LayoutInflater的inflate方法,參數是我們傳入的布局id,和上面獲得的id為content的framelayout(所以,merge其中的一個用法就找到了所用之處,如果我們的根布局是個framelayout或者只有一個元素,就一個使用這個標簽減少層級,至于我們為什么要減少層級,繼續往下看),如此我們就知道上面的布局圖為什么是那樣了。//從此開始了將xml展示到屏幕中的長征
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) //這個方法有三個參數//看第一個我們關心的重要的地方:這里先判斷了如果我們的xml布局的根標簽是<merge>的,那么就直接執行rInflate()否者按照另外一種方式處理,這種處理方式最終就是放到了rootView中,使用的是addView方法,所以我們在listView的getView中不能使用這樣的方式 if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, attrs, false, false);
} else { // Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, attrs, false); ...... // Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {// 如果root不是null并且這個方法的第三個參數是false,就會把root的layoutParams賦給temp
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
...... rInflate(parser, temp, attrs, true, true);//這個方法是做遞歸解析xml while()循環,添加到這個temp中去,利用addView。這里就是為什么我們要減少層級的一個原因,層級越多,就越耗時間循環啊!!!!!!教導了我們,盡可能使用相對布局
if (DEBUG) {
System.out.println("-----> done inflating children");
}
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {//如果第三個參數是true.則會把temp給add到root里去
root.addView(temp, params);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {//如果沒有null或者第三個參數是false,則返回temp(這里的如果root不是Null,則temp已經有layoutParams了)
result = temp;
} 從上面這段就能看出來,當我們使用listView的時候,在getView中 inflate(R.layout.xxxxx,null)的時候,直接返回了layout的根view,它的layoutParam為null,所以它的跟布局中的寬高會沒有效果
所以總結下,就是如果調用的inflate(R.layout.xxxxx,null),只是返回了xml的根布局view,inflate(R.layout_xxxxxxx,rootview),這個在listView中會報錯,rootView是listView,然而listView不支持addView,
在其他地方就是往rootView中addview(view)了,會直接展示出來。
inflate(R.layout_xxxxx,rootView,false),返回xml布局根布局view,并且擁有layoutPrarm,根寬高參數有效。
setContentView最后一個回調
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
到這里setContentView結束了,view都準備好啦~那么我們這個時候顯示到了屏幕上么?貌似木有吧,那又是怎么顯示的?
這里我們又需要學習下activityThread了,在這個類里,會執行一段handleResumeActivity方法,在這個方法里,有一句r.activity.makeVisible();
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();//這里的vm到底是什么,viewManager是個接口,實際上我們獲得的這個vm就是之前在attach里面得到的一個WindowManagerImpl實例
wm.addView(mDecor, getWindow().getAttributes());//再看看WindowManagerImpl的addView方法其實就是保存了view,viewRootImpl,params,最終還有句話!!!!root.setView(view, wparams, panelParentView);這里還是有點疑問,是不是到底是不是因為HandleResumeActivity方法導致最終顯示的?還有個說法是在phoneWindow中setContentView最后的mContentParent.addView(view, params);里調用的ViewGroup的addView里面有一句invalidate。然后開始執行的runnable方法開始了三部曲。 mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
} 以上代碼我們可以看到把mDecorView給set到ViewRootImpl中,這個setView方法中有句話:view.assignParent(this);他把DecorView的parent給置成了這個ViewRootImpl;
最終mDecor被設置為Visible;
說接下來的內容前,我們普及點知識:MeasureSpec,這里面全是static方法,他主要用來操作轉換size、mode、測量結果,這里我們可以知道他主要是通過一個32位的二進制來計算的,最高2位表示的mode
mode有三大類,UNSPECIFEID(不限制大小,view的寬高設置0或者沒有設置的時候),EXACITLY(精確的,view設置的match_parent),AT_MOST(根據自己尺寸有多大就是多大,view設置的wrap_parent),剩下的30位用來保存size
利用二進制的按位與和按位或來判斷與取值
?
接著看,在之前我們已經看到了在ViewRootImpl中調用setView,然后在這個方法里,調用了requestLayout方法,然后執行了一個runnable,最終我們能看到一句話
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
開始計算寬高啦,這個方法里面調用了mView.measure方法(這個方法是final的),這個mView其實就是我們之前setView傳過來的根布局decorView!,在measure里面會調用onMeasure方法,我們都是通過重寫這個方法重新設置寬高的這里必須要注意了,decorView調用measure方法,然后在這個方法內部調用了onMeasure方法,這個方法不是view的方法,而是decorView的父類frameLayout的onMeasure方法,記住這個就好辦了,不然都不能理解怎么一層一層計算子view的大小的呢!!!!!
view的onMeasure方法里只調用一個方法 setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); 繼續往里面看,一個view的最小寬高其實是由他的backgroud和他的minSize屬性來決定的。然后當父類要求的測量模式是UNSPECIFIED的時候,那么就用這個view的大小,否則都使用的是父類規格的大小,一般來說這個onMeasure方法由系統實現好了
我們直接調用就可以了。
我們看一下ScrollView的onMeasure方法 if (getChildCount() > 0) {
final View child = getChildAt(0);//只看第一個child,為什么只看第一個呢?因為scrollView里面只能有一層.....
......
}
再看看ListView,如果我們在ListView外面套一層ScrollView,那么將會只能顯示一行數據.....這是為什么呢?一句話,看看ListView源碼吧 if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED ||
heightMode == MeasureSpec.UNSPECIFIED)) {
final View child = obtainView(0, mIsScrap);
measureScrapChild(child, 0, widthMeasureSpec);
childWidth = child.getMeasuredWidth();
childHeight = child.getMeasuredHeight();
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(
((LayoutParams) child.getLayoutParams()).viewType)) {
mRecycler.addScrapView(child, 0);
}
}
if (widthMode == MeasureSpec.UNSPECIFIED) {
widthSize = mListPadding.left + mListPadding.right + childWidth +
getVerticalScrollbarWidth();
} else {
widthSize |= (childState&MEASURED_STATE_MASK);
}
if (heightMode == MeasureSpec.UNSPECIFIED) {
heightSize = mListPadding.top + mListPadding.bottom + childHeight +
getVerticalFadingEdgeLength() * 2;
}
if (heightMode == MeasureSpec.AT_MOST) {
// TODO: after first layout we should maybe start at the first visible position, not 0
heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
}
setMeasuredDimension(widthSize , heightSize);
可以看到如果父類指定的是UNSPECIFIED,那么就會先計算listView第一個view的高度,并在之后賦給listView自己作為高度。 ListView、ScrollView都不限制子視圖的高度,可以超過它本身。
所以我們曾經使用一個方法,重寫了ListView的onMeasure方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int mExpandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, //后30位是大小,現在就是00111111............
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, mExpandSpec);
}
這句話的意思就是,我自己指定用AT_MOST來替換掉了ScrollView的UNSPECIFIED,并且高度是最大。那么再看看源碼,這下它會用 heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);來計算新的高度, endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;
final AbsListView.RecycleBin recycleBin = mRecycler;
final boolean recyle = recycleOnMeasure();
final boolean[] isScrap = mIsScrap;
for (i = startPosition; i <= endPosition; ++i) {
child = obtainView(i, isScrap);
measureScrapChild(child, i, widthMeasureSpec);
..... // Recycle the view before we possibly return from the method
if (recyle && recycleBin.shouldRecycleViewType(//這里計算高度的時候也會使用到回收棧
((LayoutParams) child.getLayoutParams()).viewType)) {
recycleBin.addScrapView(child, -1);
}
接著會遍歷所有的child,計算最終高度。所以我們就可以展示了,但是卻丟失了listView利用回收棧的特性。(因為我們給他傳的高度是Integer.MAX_VALUE >> 2,最大,所以當在上面計算高度的時候發現夠放下所有的item,可以通過斷點調試看到這種情況下.mRecycler下面的mActiveView是ListView的總個數)
到這里,我們知道真正計算view高度的是在onMeasure中,那么這個方法的2個測量規格參數是怎么來的呢,是他們的父類ViewGroup的getChildMeasureSpec這個方法(以LinearLayout為例,入口onMeasure->measureVertical->measureChildBeforeLayout->measureChildWithMargins->getChildMeasureSpec).在這個方法里面我們可以總結出一個規律
| parent | child | childMeasure |
| EXACTLY | EXACTLY | EXACTLY |
| EXACTLY | MATCH_PARENT | EXACTLY |
| EXACTLY | WRAP_CONTENT | AT_MOST |
| AT_MOST | EXACTLY | EXACTLY |
| AT_MOST | MATCH_PARENT | AT_MOST |
| AT_MOST | WRAP_CONTENT | AT_MOST |
| UNSPECIFIED | EXACTLY | EXACTLY |
| UNSPECIFIED | MATCH_PARENT | UNSPECIFIED |
| UNSPECIFIED | WRAP_CONTENT | UNSPECIFIED |
?
為什么要說上面這個東西呢,我們再來看看ListView的onMeasure方法里面,會發現,當UNSPECIFIED顯示會有問題,當AT_MOST的時候,會自己循環計算child可以放多少個,算出高度。只有當EXACTLY的時候,這些統統跳過,直接高度計算完畢。我記得我以前看過,listView的高度能設置match就不要設置wrap,不然adapter里面getView會執行很多次,一直不知道為什么,現在知道了吧!!!!!!所以我們在使用listView的時候,第一不要嵌套在scrollView或者listview內部,第二盡量高度設置具體值或者是match,提高計算高度效率。performMeasure之后看看performLayout
可以看到host.layout();這個host就是mView,也就是之前setView的時候傳遞過來的decorView;我們會發現frameLayout中沒有重寫layout方法,所以直接看viewGroup,發現是final的,并且內部直接調用的spuer.layout。就是View的layout方法。再來看看這個方法 boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
其中有這么句,會發現怎么樣都會執行setFrame方法,這個方法為view設置了mLeft、mTop等屬性,并且會調用invalidate(這個我們之后再繼續看)??磥碓谶@個方法里面就已經把view的layout給框好了
再繼續看layout()方法,之后還會調用onLayout,這個方法在View里面是空的,然后ViewGroup是個抽象的。子類必須重寫,看看frameLayout怎么重寫的,就一句話。。。
layoutChildren();跟進去,很重要的。for循環了。依次執行child.layout()??吹竭@里,我覺得,我們重寫onLayout,其實不是框當前這個view的,而是為了循環遍歷子view去layout的。在onLayout之前,就已經計算好了位置。我們之前的measure方法測量的大小就是為layout服務的。view的getMeasureWidth這樣的方法獲得是onMeasure之后測量的大小。而view的getWidth是通過mLeft這樣的屬性計算出來的,也就是layout之后得到的大小,這2個方法得到的數據可能會不一樣。
performDraw
開始要畫了直接可以看到調用了draw,然后我們會在最后發現出現了分支
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
........//硬件加速開啟的時候
}else{
.....
drawSoftware() //軟件處理
} 看到這里,就又要引入新的知識了。硬件加速(GPU).
基于軟件的繪圖模式
?基于軟件的繪圖模式在重繪View時,需要如下兩個過程Invalidate the hierarchyDraw the hierarchy
需要進行重繪時,系統發出invalidate()信號,并在View視圖樹中進行傳遞,計算需要重新繪制的區域,但是這種繪圖方式有兩個不足:? ?當我們只需要重繪視圖樹中的一個View時,視圖樹中的View都將進行重繪,而且遍歷視圖樹也浪費大量時間。例如一個ViewA在另一個ViewB之上,即使B沒有發生變化,重繪A的時候,B也會重繪。
?這種方式隱藏了繪制中的bug,例如上面的例子中,由于ViewA、ViewB相互重疊,有需要重繪的the dirty region,那么如果B忘記了進行重繪的邏輯,那么A進行重繪的時候,就會將B重繪,也就是說使用錯誤的行為來得到了正確的現象。正是因為這個原因,開發者需要保證在View需要發生重繪時,調用正確的invalidate()方法。
? ?基于硬件的繪圖模式
基于硬件的繪圖方式同樣使用invalidate()信號來進行重繪,但其繪制和渲染的方式不同。Android內部維護一個display list用于記錄視圖樹的顯示狀態。當收到invalidate()信號時,系統只需要更改需要重繪的視圖的display list,而其他未發生改變的視圖只需要使用原來的display list即可,整個過程分為三 部分:Invalidate the hierarchyRecord and update display listsDraw the display lists使用這種方式,就可以避免軟件繪圖中第二點的bug。
?例如,假設有一個包含了一個Button對象的ListView對象的LinearLayout布局,那么LinearLayout布局的顯示列表如下:
?1. DrawDisplayList(ListView);
?2.DrawDisplayList(Button)
?假設現在要改變ListView對象的不透明度,那么在調用ListView對象的setAlpha(0.5f)方法時,顯示列表就包含了以下處理:
?1.SaveLayerAlpha(0.5);
?2.DrawDisplayList(ListView);
?3. Restore;
?4.DrawDisplayList(Button)
View LayersLAYER_TYPE_NONE:View對象用普通的方式來呈現,并且不是由屏幕外緩存來返回的。這種類型是默認的行為;LAYER_TYPE_HARDWARE:如果應用程序是硬件加速的,那么該View對象被呈現在硬件的一個硬件紋理中。如果沒有被硬件加速,那么這種層類型的行為與LAYER_TYPE_SOFTWARE相同。LAYER_TYPE_SOFTWARE: View對象會被呈現在軟件的一個位圖中。使用哪種層的類型,依賴以下目標:
性能:使用硬件層類型,把View呈現到一個硬件紋理中。一旦該View對象被呈現到一個層中,那么它的繪圖代碼直到調用該View對象的invalidate()方法時才會被執行。對于某些動畫,如alpha動畫,就能夠直接使用該層,這么做對于GPU來說是非常高效的。 視覺效果:使用硬件或軟件層類型和一個Paint對象,能夠把一 些特殊的視覺處理應用給一個View對象。例如,使用ColorMatrixColorFilter對象繪制一個黑白相間的View對象。 兼容性:使用軟件層類型會強制把一個View對象呈現在軟件中。如果View對象被硬件加速(例如,如果整個應用程序都被硬件加速)發生呈現問題,那么使用軟件層類型來解決硬件呈現管道的限制是一個容 易的方法。 硬件加速具體使用例子 :動畫,我們應用的引導頁,動畫復雜,多個動畫組成,非常的卡,這里就可以利用這個。 View.setLayerType(View.LAYER_TYPE_HARDWARE, null); ObjectAnimator animator = ObjectAnimator.ofFloat(view, "rotationY", 180); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { view.setLayerType(View.LAYER_TYPE_NONE, null); } }); animator.start();
實測效果很好,但是有一點要注意,動畫結束的時候一定要取消硬件加速,因為硬件加速是非常耗內存的。如果不取消就會導致oom(實測確實出現了)。
提示和技巧
切換到2D圖形硬加速能立即提升性能,但是你仍然需要按照以下建議來有效使用GPU:
??減少你應用中views的數量
系統畫的views越多,就越慢.這對軟件呈現管線來說也是一樣.減少views是優化你的UI的最簡單的辦法.
??避免過度繪制
不要在彼此的上面畫太多layers.移除那些完全被別的不透明view遮蓋的view們.如果你需要把多個layer混合畫到每個view的上面,應考慮把它們合并到一個layer中.對于當前硬件的一個好原則是畫的次數不要超過每幀屏幕上像素的2.5倍(透明像素按位圖計數).
??不要在繪制方法們中創建render對象
一個常見的錯誤就是在每次調用繪制方法時都創建新的Paint或Path.這強制垃圾收集器運行得更頻繁,并且導致高速緩存和硬件管道優化不起作用.
??不要太頻繁地修改shapes
比如混合shapes,paths,和circles時,是使用紋理遮罩呈現的.每次你創建或修改一個path,硬件管道都創建一個新的遮罩,這個代價是很昂貴的.
??不要太頻繁地修改bitmap
你每次改變一個bitmap的內容,你下次去畫它時它會作為一個GPU紋理重新上載.
??小心使用alpha(透明度)
當你用setAlpha(),AlphaAnimation,或ObjectAnimator把一個view設為透明,它將被呈現到一個離屏緩沖中,此時就需要雙倍的填充速率.當在一個very大的view上應用透明度時,應考慮把view的layer類型設置為LAYER_TYPE_HARDWARE.
好了現在我們繼續看,我們只關心drawSoftware這個方法,可以在這個方法里看到
......
final Rect dirty = mDirty;
......
canvas = mSurface.lockCanvas(dirty); //基于Surface,以后再學習........
...... mView.draw(canvas); ......//基于canvas,這個以后再學習,又可以畫各種各樣的東西
好了,這個mView,不用多說了,就是DecorView,一路追下去,看到FrameLayout的draw,發現它會畫一個mForeground,如果有的話,這個就是覆蓋在所有view之上的一個drawable..(發現新玩意.......也許以后可以用到,毛玻璃效果?),最后就到了View的draw,好吧。。。
在這里可以看到6個步驟!!!!
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/ 根據源碼注釋,2&5不是必須的,2&5是如果scroll 漸變效果開著的,就會去畫,跳過不看。看1,3,4,6
第一步
drawBackground(canvas);進去看看,發現如果mBackground == null 就return了,后面繁瑣的事情不做了,可以想象出為什么我們畫布局的時候,減少有交集的background,過渡渲染層會變少??吹竭@里大家應該清楚了,這個過渡渲染其實跟xml層級木有關系,跟這個background有關系,只要有覆蓋,下層視圖看不見,但是還是畫了,就是過渡渲染。 第三步
onDraw,我們會發現view這個方法是空方法,因為每個view長得不一樣,所以需要自己去畫,ViewGroup也沒有重寫這個方法。
第四步
dispatchDraw,viewgroup會重寫這個方法,會循環編譯childView,調用view.draw方法
第六步
onDrawScrollBars,畫滾動條,其實每個view都有滾動條,只是畫沒畫的問題 其實還有個第七步,不過這個只在4.3之上才有的,就是viewoverlay,這是一個在所有頁面之上的一個View,沒有點擊相應事件,可以做一些動畫 到這里三部曲結束。
但是我們還要來看看之前留的一個坑-invalidate和postInvalidate
其實postInvalidate就是通過ViewRootImpl發送了一個消息給ui線程handler,又調用了view.invalidate
看看view的invalidate方法 // Propagate the damage rectangle to the parent view.
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
p.invalidateChild(this, damage);
}
在這里能看出把刷新區域(自己view有關:0, 0, mRight - mLeft, mBottom - mTop,就是layout之后的4個點的值)放在Rect里面然后讓父類調用invalidateChild繼續處理,再隨便看一個ViewGroup do {
View view = null;
if (parent instanceof View) {
view = (View) parent;
}
if (drawAnimation) {
if (view != null) {
view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
} else if (parent instanceof ViewRootImpl) {
((ViewRootImpl) parent).mIsAnimating = true;
}
}
// If the parent is dirty opaque or not dirty, mark it dirty with the opaque
// flag coming from the child that initiated the invalidate
if (view != null) {
if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&
view.getSolidColor() == 0) {
opaqueFlag = PFLAG_DIRTY;
}
if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag;
}
}
parent = parent.invalidateChildInParent(location, dirty);
if (view != null) {
// Account for transform on current parent
Matrix m = view.getMatrix();
if (!m.isIdentity()) {
RectF boundingRect = attachInfo.mTmpTransformRect;
boundingRect.set(dirty);
m.mapRect(boundingRect);
dirty.set((int) (boundingRect.left - 0.5f),
(int) (boundingRect.top - 0.5f),
(int) (boundingRect.right + 0.5f),
(int) (boundingRect.bottom + 0.5f));
}
}
} while (parent != null); ViewGroup中做了循環,只要當前View有mParent,就一直循環下去,直到ViewRootImpl為止。
在這里循環過程中,parent = parent.invalidateChildInParent(location, dirty);方法中,有相關dirty.union/dirty.set方法,意思就是一層層傳遞當前view需要刷新的區域坐合并。最終到ViewRootImpl中看
if (!mWillDrawSoon && (intersected || mIsAnimating)) { scheduleTraversals();
}
會發現調用了scheduleTraversals,到這里,就發現又回到剛剛開始說的入口了,viewrootimpl的setView中的requestLayout方法里面也會調用scheduleTraversals。這樣就又開始了三部曲。
常見的引起invalidate方法操作的原因主要有:
-
- 直接調用invalidate方法.請求重新draw,但只會繪制調用者本身。
- 觸發setSelection方法。請求重新draw,但只會繪制調用者本身。
- 觸發setVisibility方法。 當View可視狀態在INVISIBLE轉換VISIBLE時會間接調用invalidate方法,繼而繪制該View。當View的可視狀態在INVISIBLE\VISIBLE 轉換為GONE狀態時會間接調用requestLayout和invalidate方法,同時由于View樹大小發生了變化,所以會請求measure過程以及draw過程,同樣只繪制需要“重新繪制”的視圖。
- 觸發setEnabled方法。請求重新draw,但不會重新繪制任何View包括該調用者本身。
- 觸發requestFocus方法。請求View樹的draw過程,只繪制“需要重繪”的View。
轉載于:https://www.cnblogs.com/liming-saki/p/5075446.html
總結
以上是生活随笔為你收集整理的view是怎么被展示在手机上的?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android签名机制之---签名验证过
- 下一篇: 关于年终奖励的扣税算法BUG