Android View底层到底是怎么绘制的详解手机开发
Android绘制链图:
网上很多讲Android view的绘制流程往往只讲到了Measure Layout Draw。
但是,这只是一个大体的流程,而我们需要探讨的是Android在我们调用setcontentView()之后,系统给我们干了什么事情,这个完整的逻辑是什么样的,却很少有人讲,还是先看下系统代码吧。
public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); }
而最终调用了initWindowDecorActionBar这个方法,我们看下这个方法里面都实现了什么
span span private void initWindowDecorActionBar() { Window window = getWindow(); // Initializing the window decor can change window feature flags. // Make sure that we have the correct set before performing the test below. window.getDecorView(); if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) { return; } mActionBar = new WindowDecorActionBar(this); mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp); mWindow.setDefaultIcon(mActivityInfo.getIconResource()); mWindow.setDefaultLogo(mActivityInfo.getLogoResource()); }
根据人家给我们的注释,这段代码是创建一个actionbar,初始化这个view和actionbar。这里面有一段很重要的代码:
window.getDecorView();
正式这段代码告知系统可以从view的根节点开始绘制了,
通过DecorView方法,decorview调用了performTraversals方法,我们来看下performTraversals源码:
span private void performTraversals() { final View host = mView; ... host.measure(childWidthMeasureSpec, childHeightMeasureSpec); ... host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); ... draw(fullRedrawNeeded); }
调用然后系统再调用Measure Layout Draw实现了View的绘制。
我们看一下完整的绘制流程,直接上一张图,或许更能说明这个意思:
到这里,系统会调用我们之前的比较熟悉的几个方法:Measure Layout Draw
Measure
Measure过程是计算视图大小,View中视图measure过程相关的方法主要有三个
public final void measure(int widthMeasureSpec, int heightMeasureSpec) protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
measure调用onMeasure,onMeasure测量完成后setMeasureDimension,setMeasureDimension是final类型,view的子类不需要重写。
measure 源码:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { if ((mPrivateFlags FORCE_LAYOUT) == FORCE_LAYOUT || widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec) { // first clears the measured dimension flag mPrivateFlags = ~MEASURED_DIMENSION_SET; if (ViewDebug.TRACE_HIERARCHY) { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE); // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec); // flag not set, setMeasuredDimension() was not invoked, we raise // an exception to warn the developer if ((mPrivateFlags MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) { throw new IllegalStateException("onMeasure() did not set the" + " measured dimension by calling" + " setMeasuredDimension()"); mPrivateFlags |= LAYOUT_REQUIRED; mOldWidthMeasureSpec = widthMeasureSpec; mOldHeightMeasureSpec = heightMeasureSpec; }
我们看一下OnMearsure方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
这个方法主要是实现setMeasuredDimension,这个方法是测量view的大小:
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { boolean optical = isLayoutModeOptical(this); if (optical != isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets(); int opticalWidth = insets.left + insets.right; int opticalHeight = insets.top + insets.bottom; measuredWidth += optical ? opticalWidth : -opticalWidth; measuredHeight += optical ? opticalHeight : -opticalHeight; setMeasuredDimensionRaw(measuredWidth, measuredHeight); }
而对于这个measuredWidth和measuredHeight参数,系统却调了一个getDefaultSize();
public static int getDefaultSize(int size, int measureSpec) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: result = size; break; case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = specSize; break; return result; }
widthMeasureSpec和heightMeasureSpec决定了Mode和Size的值,
widthMeasureSpec和heightMeasureSpec来自父视图,这两个值都是由父视图经过计算后传递给子视图的,说明父视图会在一定程度上决定子视图的大小。但是最外层的根视图,它的widthMeasureSpec和heightMeasureSpec又是从哪里得到的呢?这就需要去分析ViewRoot中的源码了.
关于视图的measure过程可以阅读以下LinearLayout源码。
Layout measure过程确定视图的大小,而layout过程确定视图的位置。
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; }
函数中参数l、t、r、b是指view的左、上、右、底的位置,通过这几个参数来确定view在Windows的位置。
在layout函数中,重载了一个空函数
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { }
这个需要子类去实现的。
比如Linearlayout:
protected void onLayout(boolean changed, int l, int t, int r, int b) { if (mOrientation == VERTICAL) { layoutVertical(); } else { layoutHorizontal(); }
具体实现请自行看源码。
而在最后无论是layoutVertical还是layoutHorizontal都会掉一个setChildFrame方法来控制显示位置。
private void setChildFrame(View child, int left, int top, int width, int height) { child.layout(left, top, left + width, top + height); }
从上面看出,layout也是一个自上而下的过程,先设置父视图位置,在循环子视图,父视图位置一定程度上决定了子视图位置。
draw过程调用顺序在measure()和layout()之后,同样的,performTraversals()发起的draw过程最终会调用到mView的draw()函数,对于activity来说就是调用的PhoneWindow.DecorView。
* 1. Draw the background * 2. If necessary, save the canvas layers to prepare for fading * 3. Draw views content * 4. Draw children * 5. If necessary, draw the fading edges and restore layers * 6. Draw decorations (scrollbars for instance)
根据view源码的注释,
1,绘制背景
2,保存画布图层
3,调用了onDraw方法,子类中实现onDraw方法
4,使用的dispatchDraw方法
View或ViewGroup的子类不用再重载ViewGroup中该方法,因为它已经有了默认而且标准的view系统流程。dispatchDraw()内部for循环调用drawChild()分别绘制每一个子视图,而drawChild()内部又会调用draw()函数完成子视图的内部绘制工作。
有兴趣的可以看看onDraw的源码。
5885.html
app程序应用开发手机开发无线开发移动端开发相关文章
- android开机动画多长时间_Android开机动画及黑屏[通俗易懂]
- android toast位置_android studio toast不显示
- android scaleanimation动画,Android 动画之ScaleAnimation应用详解「建议收藏」
- Java反射改变Android属性详解手机开发
- Android常见输入inputType类型详解手机开发
- 华为手机Android Studio的Logcat无任何内容输出的解决方案详解手机开发
- Android Glide加载图片时转换为圆形、圆角、毛玻璃等图片效果详解手机开发
- Android Bitmap 图片处理工具类详解手机开发
- android广播监听短信并显示内容详解手机开发
- Android P可以正式支持通话录音详解手机开发
- [android] 切换按钮-自定义控件详解手机开发
- [android] 轮播图-滑动图片标题焦点详解手机开发
- [android] 多媒体播放api简介详解手机开发
- [android] 利用广播实现ip拨号详解手机开发
- [android] 采用layoutInflater打气筒创建一个view对象详解手机开发
- [android] sql语句实现数据库的增删改查详解手机开发
- [android] 采用断点调试的方式观察pull解析的流程详解手机开发
- android 的android httpClient详解手机开发
- 再谈Android客户端进程保活详解手机开发
- android classloader双亲委托模式详解手机开发
- android守护进程详解手机开发
- Android启动过程分析详解手机开发
- Android开发艺术探索(一)——Activity的生命周期和启动模式详解手机开发
- 实现android apk反编译后代码混淆详解手机开发