淺談Android之Activity 視窗顯示流程介紹(二)
7.3 Activity Décorview佈局(layout)
Measure確定Décor View以及child views的大小,layout則是確定child view在其parent view中的顯示區域,只有layout結束,view的left,right,top,bottom值才會被設定,getWidth和getHeight兩個函式才會返回view最終的寬高值
對FrameLayout來說,由於child view的大小確定了,那麼再確定它們在parentview中的顯示區域其實通過parent view的padding值,child view的margin值以及寬高就可以計算出
當然RelativeLayout和LinearLayout會相對複雜點,因為childviews之間會存在佈局關聯,本文只對FrameLayout的實現做簡單介紹,至於RelativeLayout和LinearLayout大家可自行看程式碼分析
在ViewRootImpl中呼叫performMeasure完成對Décor View的measure後,接著呼叫
PerformLayout觸發佈局操作,其內部主要呼叫:
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); |
Host變數儲存的就是Décor View,layout的四個變數依次對應left,top,right,bottom
FrameLayout和ViewGroup都沒有對layout做處理,接著直接看View中的預設實現:
public void layout(int l, int t, int r, int b) { if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) { onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; ListenerInfo li = mListenerInfo; if (li != null && li.mOnLayoutChangeListeners != null) { ArrayList<OnLayoutChangeListener> listenersCopy = (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone(); int numListeners = listenersCopy.size(); for (int i = 0; i < numListeners; ++i) { listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB); } } } mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; } |
這個函式先呼叫setFrame儲存位置資料,然後呼叫onLayout進行佈局操作,最後看這個view是否有設定layoutchangelistener,如果有,呼叫回撥通知佈局已經發生改變
先看setFrame:
//View.java protected boolean setFrame(int left, int top, int right, int bottom) { boolean changed = false; if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) { changed = true; int drawn = mPrivateFlags & PFLAG_DRAWN; int oldWidth = mRight - mLeft; int oldHeight = mBottom - mTop; int newWidth = right - left; int newHeight = bottom - top; boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight); // Invalidate our old position invalidate(sizeChanged); mLeft = left; mTop = top; mRight = right; mBottom = bottom; mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom); mPrivateFlags |= PFLAG_HAS_BOUNDS; if (sizeChanged) { sizeChange(newWidth, newHeight, oldWidth, oldHeight); } if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) { mPrivateFlags |= PFLAG_DRAWN; invalidate(sizeChanged); invalidateParentCaches(); } // Reset drawn bit to original value (invalidate turns it off) mPrivateFlags |= drawn; mBackgroundSizeChanged = true; notifySubtreeAccessibilityStateChangedIfNeeded(); } return changed; } |
如果left,right,top,bottom值都沒變,那就說明佈局沒變,返回changed為false,如果變了,則將對應的值都儲存到mLeft,mRight,mTop, mBottom,然後呼叫invalidate通知view進行重繪
接著看onLayout的實現:
//FrameLayout.java protected void onLayout(boolean changed, int left, int top, int right, int bottom) { layoutChildren(left, top, right, bottom, false /* no force left gravity */); } |
直接呼叫layoutChildren:
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) { final int count = getChildCount(); final int parentLeft = getPaddingLeftWithForeground(); final int parentRight = right - left - getPaddingRightWithForeground(); final int parentTop = getPaddingTopWithForeground(); final int parentBottom = bottom - top - getPaddingBottomWithForeground(); mForegroundBoundsChanged = true; for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final int width = child.getMeasuredWidth(); final int height = child.getMeasuredHeight(); int childLeft; int childTop; int gravity = lp.gravity; if (gravity == -1) { gravity = DEFAULT_CHILD_GRAVITY; } final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL: childLeft = parentLeft + (parentRight - parentLeft - width) / 2 + lp.leftMargin - lp.rightMargin; break; case Gravity.RIGHT: if (!forceLeftGravity) { childLeft = parentRight - width - lp.rightMargin; break; } case Gravity.LEFT: default: childLeft = parentLeft + lp.leftMargin; } switch (verticalGravity) { case Gravity.TOP: childTop = parentTop + lp.topMargin; break; case Gravity.CENTER_VERTICAL: childTop = parentTop + (parentBottom - parentTop - height) / 2 + lp.topMargin - lp.bottomMargin; break; case Gravity.BOTTOM: childTop = parentBottom - height - lp.bottomMargin; break; default: childTop = parentTop + lp.topMargin; } child.layout(childLeft, childTop, childLeft + width, childTop + height); } } } |
邏輯很簡單,通過拿到child view的measure width和measure height,然後根據gravity和margin值計算出child view在parent view中的left,right,top,bottom值,最後呼叫
child.layout
如果child view是ViewGroup,則重走本節流程,依次反覆執行,直到全部child view都layout完成為止
7.4 Activity Décorview繪製(draw)
Layout結束後,每一個child view在其parent view的位置都已經固定,接下去就可以開始繪製child view的圖形資料了
在ViewRootImpl中呼叫performLayout完成佈局後,接著呼叫performDraw,其最終會呼叫drawSoftware,該函式會通過mSurface拿到Canvas,接著呼叫mView.draw()並傳入canvas開始Décor view的繪製
由於Decor view的所有child views是共用Decor view這一塊top canvas的,對每一個view來說,它們在繪製其child view或者content data時,最簡單的當然是使用相對座標,也就是將其左上角設定為座標原點,這樣就可以讓view只需專注於child view或者content data的繪製,而無需關心座標原點的調整以及恢復
如果把Décor view作為世界地圖,那top canvas肯定擁有初始的單位矩陣, childview或者content data就是世界中的顯示元素,元素顯示位置和方式的調整,都是通過矩陣(matrix)的配置來實現的
後續View繪製程式碼的分析,為了簡化邏輯以便於理解,我們假定系統不支援硬體加速,也就是mAttachInfo.mHardwareAccelerated為false
Draw(Canvas canvas)是View繪製的入口函式,它跟measure和layout一樣,基於view tree做完整的遞迴呼叫,它會按順序做如下事情
1) 繪製view的background
2) 呼叫onDraw繪製view的content
3) 呼叫dispatchDraw繪製view的children
4) 繪製scrollbar等
其中1, 2, 4都是基於傳入的canvas做介面繪製,那canvas的座標切換肯定是由第3步
dispatchDraw來完成了
dispatchDraw的預設實現是空的,也就是啥也沒做,只有ViewGroup重新實現了改方法,也就是說,這個方法,只會ViewGroup有效
接著我們看其在ViewGroup中的實現:
//ViewGroup.java protected void dispatchDraw(Canvas canvas) { boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode); final int childrenCount = mChildrenCount; final View[] children = mChildren; int flags = mGroupFlags; …… int clipSaveCount = 0; final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK; if (clipToPadding) { clipSaveCount = canvas.save(); canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop, mScrollX + mRight - mLeft - mPaddingRight, mScrollY + mBottom - mTop - mPaddingBottom); } // We will draw our child's animation, let's reset the flag mPrivateFlags &= ~PFLAG_DRAW_ANIMATION; mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED; …… for (int i = 0; i < childrenCount; i++) { int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex); if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { more |= drawChild(canvas, child, drawingTime); } } …… } |
由於mGroupFlags在initViewGroup時被設定了FLAG_CLIP_TO_PADDING,所以這裡
clipToPadding肯定為true,接著呼叫canvas .clipRect根據padding和scroll值來對canvas的操作區域進行裁剪,接著遍歷所有childview,依次呼叫drawChild
接著看drawChild的實現:
//ViewGroup.java protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); } |
直接呼叫child.draw函式,這個函式也在View.java預設被實現,跟上頭提過的draw(canvas)函式不同的是,它有三個引數,除了canvas外,還傳入了parent viewgroup和drawing time:
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) { boolean usingRenderNodeProperties = mAttachInfo != null && mAttachInfo.mHardwareAccelerated; boolean more = false; final boolean childHasIdentityMatrix = hasIdentityMatrix(); final int flags = parent.mGroupFlags; …… final boolean hasNoCache = cache == null || hasDisplayList; final boolean offsetForScroll = cache == null && !hasDisplayList && layerType != LAYER_TYPE_HARDWARE; int restoreTo = -1; if (!usingRenderNodeProperties || transformToApply != null) { restoreTo = canvas.save(); } if (offsetForScroll) { canvas.translate(mLeft - sx, mTop - sy); } else { if (!usingRenderNodeProperties) { canvas.translate(mLeft, mTop); } if (scalingRequired) { if (usingRenderNodeProperties) { // TODO: Might not need this if we put everything inside the DL restoreTo = canvas.save(); } // mAttachInfo cannot be null, otherwise scalingRequired == false final float scale = 1.0f / mAttachInfo.mApplicationScale; canvas.scale(scale, scale); } } …… if (!usingRenderNodeProperties) { // apply clips directly, since RenderNode won't do it for this draw if ((flags & ViewGroup.FLAG_CLIP_CHILDREN) == ViewGroup.FLAG_CLIP_CHILDREN && cache == null) { if (offsetForScroll) { canvas.clipRect(sx, sy, sx + (mRight - mLeft), sy + (mBottom - mTop)); } else { if (!scalingRequired || cache == null) { canvas.clipRect(0, 0, mRight - mLeft, mBottom - mTop); } else { canvas.clipRect(0, 0, cache.getWidth(), cache.getHeight()); } } } if (mClipBounds != null) { // clip bounds ignore scroll canvas.clipRect(mClipBounds); } } …… if (!layerRendered) { if (!hasDisplayList) { // Fast path for layouts with no backgrounds if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { mPrivateFlags &= ~PFLAG_DIRTY_MASK; dispatchDraw(canvas); } else { draw(canvas); } } else { mPrivateFlags &= ~PFLAG_DIRTY_MASK; ((HardwareCanvas) canvas).drawRenderNode(renderNode, null, flags); } } } else if (cache != null) { …… } if (restoreTo >= 0) { canvas.restoreToCount(restoreTo); } …… mRecreateDisplayList = false; return more; } |
drawingTime和parent viewgroup的作用在下一節再介紹
由於在layout的時候,view在其parent view中的top-left座標已經確定,所以這裡的座標切換相對來說就非常簡單,首先呼叫canvas.save()備份canvas當前的matrix/clip資料,接著呼叫canvas.translate(mLeft,mTop)進行原點切換,最後呼叫draw(canvas)讓view在當前canvas上進行內容繪製,繪製成功後,呼叫canvas.restoreToCount(restoreTo)恢復之前canvas備份的matrix/clip資料
7.5 ViewAnimation原理介紹
通過上一節我們知道,View在parent view中的位置或者展示方式是通過matrix來完成的,
那如果我在view每一次繪製的時候,按照一定的規律更改matrix的值,這樣就可以達到view在parent view的一個序列圖形顯示效果,也就是說一個針對view的動畫效果就出來了,這個好像是廢話,動態檢視不都是這麼來的麼^_^
Android提供了TranslateAnimation等類用來封裝對每一幀對應matrix的計算
接下去通過程式碼做下簡單介紹,為view設定並開始播放animation:
TranslateAnimation tAnim = new TranslateAnimation(0, 400, 0, 0); tAnim.setDuration(2000); view.startAnimation(tAnim); |
建立TranslateAnimation,然後呼叫startAnimation開發播放
//View.java public void startAnimation(Animation animation) { animation.setStartTime(Animation.START_ON_FIRST_FRAME); setAnimation(animation); invalidateParentCaches(); invalidate(true); } |
先通過animation.setStartTime(Animation.START_ON_FIRST_FRAME)設定animation的啟動時間為Animation.START_ON_FIRST_FRAME,也就是說,animation的第一幀被繪製的時間,即為start time
然後呼叫setAnimation(animation)將該animation設定為view的mCurrentAnimation中
最後呼叫invalidate(true)強制view重畫。
View重畫後,上一節說過,帶有parent和drawingTime的draw函式會被呼叫:
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) { …… Transformation transformToApply = null; boolean concatMatrix = false; …… final Animation a = getAnimation(); if (a != null) { more = drawAnimation(parent, drawingTime, a, scalingRequired); concatMatrix = a.willChangeTransformationMatrix(); if (concatMatrix) { mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM; } transformToApply = parent.getChildTransformation(); } else { …… } concatMatrix |= !childHasIdentityMatrix; …… float alpha = usingRenderNodeProperties ? 1 : (getAlpha() * getTransitionAlpha()); if (transformToApply != null || alpha < 1 || !hasIdentityMatrix() || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) == PFLAG3_VIEW_IS_ANIMATING_ALPHA) { if (transformToApply != null || !childHasIdentityMatrix) { int transX = 0; int transY = 0; if (offsetForScroll) { transX = -sx; transY = -sy; } if (transformToApply != null) { if (concatMatrix) { if (usingRenderNodeProperties) { renderNode.setAnimationMatrix(transformToApply.getMatrix()); } else { // Undo the scroll translation, apply the transformation matrix, // then redo the scroll translate to get the correct result. canvas.translate(-transX, -transY); canvas.concat(transformToApply.getMatrix()); canvas.translate(transX, transY); } parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION; } …… } …… if (a != null && !more) { if (!hardwareAccelerated && !a.getFillAfter()) { onSetAlpha(255); } parent.finishAnimatingView(this, a); } …… return more; } |
drawingTime,其實就是指這次draw操作的觸發時間,也就是ViewRootImpl呼叫
drawSoftware的時間:
//ViewRootImpl.java private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) { …… attachInfo.mDrawingTime = SystemClock.uptimeMillis(); …… return true; } |
至於parent ViewGroup,這邊看到,其內部會有一個變數mChildTransformation儲存當前某一child view的animation對應的transformation片段資料
在draw函式中,先通過呼叫drawAnimation並傳入parent,animation和drawingTime來來得到當前的時間下對應的transformation片段資料,並儲存到parent ViewGroup的
mChildTransformation中,然後drawAnimation內部還會判斷在當前transformation片段資料執行完後,是否還存在animation片段資料?如果有,則會呼叫invalidate觸發view的下一次繪製,這樣才能保持animation不會中斷
在draw函式,接著呼叫parent.getChildTransformation()獲取當前的transformation資料,然後獲取其Matrix並設定到canvas中
最後判斷more是否為false,如果是false,說明animation已經結束,呼叫
parent.finishAnimatingView(this,a)執行animation結束相關掃尾程式碼
由於android View都是在主執行緒的完成繪製的,而主執行緒的負載又不是均衡的,所以會導致View animation的幀率無法得到保障,當然,對於短時的動畫來說影響不大,但是對於長時間並且比較複雜的動畫,建議還是使用Surface來繪製,然後開一條執行緒來按照你想要的幀率來觸發重繪
相關推薦
淺談Android之Activity 視窗顯示流程介紹(二)
7.3 Activity Décorview佈局(layout) Measure確定Décor View以及child views的大小,layout則是確定child view在其parent view中的顯示區域,只有layout結束,view的left,right,t
淺談Android之Activity 視窗顯示流程介紹(一)
7 Activity 視窗顯示流程介紹 Activity 視窗顯示,其實就是Décor View繪製並顯示的過程,但是在繪製之前,Décor View需要先做下面兩件事情: 1) 確定Décor View的大小 2) 對Décor View進行佈局操作,也就是確定Déc
淺談Android之Activity Decor View建立流程介紹
6 Activity DecorView建立流程介紹 上頭已經完整的介紹了Activity的啟動流程,Activity是如何繫結Window,Window的décor view是如何通過ViewRootImpl與WMS建立關聯的,也就是說,整個框架已經有了,唯一缺的就是Ac
淺談Android之Activity觸控事件傳輸機制介紹
8 Activity觸控事件傳輸機制介紹 當我們觸控式螢幕幕的時候,程式會收到對應的觸控事件,這個事件是在app端去讀取的嗎?肯定不是,如果app能讀取,那會亂套的,所以app不會有這個許可權,系統按鍵的讀取以及分發都是通過WindowManagerService來完成
淺談Android之App視窗檢視管理
5 App視窗檢視管理 WindowManagerGlobal負責管理App所有要新增到WMS的視窗,介面即為上頭的addView 首先,對於App本地視窗來說,其最核心的資料無非就兩個,一個是Window Parameters,另一個就是視窗的DécorView,一個負
HBase原始碼分析之HRegion上compact流程分析(二)
2016年03月03日 21:38:04 辰辰爸的技術部落格 閱讀數:2767 版權宣告:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/lipeng_bigdata/article/details/50791205
【CSS】淺談css中格式化上下文BFC、IFC(二)
Inline Formatting Context Inline Formatting Context的縮寫就是IFC。中文名叫,行內格式化上下文。行內框參與IFC。 什麼是行框? 在IFC中,每個框都是一個接一個地水平排列,起點是包含塊的頂部,水平方向
android之解析包時出現錯誤(二)
這次的原因不同,再記錄下public class DownloadTask { /** * @param path下載地址 * @param filePath儲存路徑 * @param progressDialog進度條 * @return * @t
Android OTA升級原理和流程分析(二)---update.zip差分包問題的解決
Android OTA升級原理和流程分析(二)—update.zip差分包問題的解決 在上一篇末尾提到的生成差分包時出現的問題,現已解決,由於最近比較忙,相隔的時間也比較長,所以單列一個篇幅提示大家。這個問題居然是原始碼中的問題,可能你已經制作成功了,不過我的
Android用surface直接顯示yuv資料(二)
上一篇文章主要是參照AwesomePlayer直接用SoftwareRenderer類來顯示yuv,為了能用到這個類,不惜依賴了libstagefright、libstagefright_color_conversion等動態靜態庫,從而造成程式具有很高的耦合度,也不便
上門洗車APP --- Androidclient開發 之 網絡框架封裝介紹(二)
glob imp success rgb sed error margin p s 再次 上門洗車APP --- Androidclient開發 之 網絡框架封裝介紹(二)前幾篇博文中給大家介紹了一下APP中的基本業務及開發本項目使用的網絡架構:上門洗車APP ---
淺談SpringMVC之架構與工作流程
MVC模式是在Java的Web應用開發中非常常用的模式。MVC全名是Model View Controller,是模型(model)-檢視(view)-控制器(controller)的縮寫,一種軟體設計典範,用一種業務邏輯、資料、介面顯示分離的方法組織程式碼,將
淺談Android之SurfaceFlinger相關介紹(三)
3.3 Surface Java層相關封裝 主要介紹三個類,對應如下: Java C++ SurfaceSession.java SurfaceComposeClient 對應JNI檔案為: android_view_surfacesession.cpp
Android apk動態載入機制的研究(二) 資源載入和activity生命週期管理
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!  
淺談HTTP中Get與Post的區別(轉)
Http定義了與伺服器互動的不同方法,最基本的方法有4種,分別是GET,POST,PUT,DELETE。URL全稱是資源描述符,我們可以這樣認為:一個URL地址,它用於描述一個網路上的資源,而HTTP中的GET,POST,PUT,DELETE就對應著對這個
Android9.0 Activity啟動流程分析(二)
文章目錄 1、ActivityThread的main函式 2. AMS的attachApplication函式 2.1 Part-I 2.2 Part-II 2.2.1 ApplicationThread的bindApp
淺談快取一致性原則和Java記憶體模型(JMM)
Java記憶體模型(JMM)是一個概念模型,底層是計算機的暫存器、快取記憶體、主記憶體和CPU等。 多處理器環境下,共享資料的互動硬體裝置之間的關係: JMM: 從以上兩張圖中,談一談以下幾個概念: 1.快取一致性協議(MESI): 由於每個處
淺談ArcGIS移動開發中的基本變數(1):MapView、Map、Layers、GraphicsOverlay
一、MapView 二、Map Map,程式設計中即ArcGISMap,可譯為地圖物件,主要用來承載地圖資料,ArcGISMap包含製圖資料圖層以及其它能夠定義地圖資訊的資料(例如basemaps底圖、popups彈出視窗、renderer渲染
Android使用Font Awesome顯示小圖示(一)
Android中傳統的顯示圖示的方式 在平常的開發中,如果我們需要在介面上顯示某個小圖示,比如搜尋按鈕,返回按鈕,這時我們需要美工給我們切對應的png圖片,並放進對應的drawable資料夾中,這樣隨著圖示的越來越多,APK體積也會越來越大。 什麼是Fo
Android OTA升級原理和流程分析(一)
這篇及以後的篇幅將通過分析update.zip包在具體Android系統升級的過程,來理解Android系統中Recovery模式服務的工作原理。我們先從update.zip包的製作開始,然後是Android系統的啟動模式分析,Recovery工作原理,如何從