Android中的View全解析(一)
来源:互联网 发布:手机淘宝主页 编辑:程序博客网 时间:2024/05/22 03:06
View的内容大致分为一下四项:
View的绘制原理,
View的自定义属性,
View的生命周期,
View的事件分发机制
首先聊一聊View的绘制。大家应该都知道View的绘制经历了三个步骤:Measure,Layout,Draw,这也是View类中的三个方法,但它们并不真正的做工作,只是对工作的结果进行审查。在这个三个方法中,分别调用了onMeasure,onLayout,onDraw三个方法来做真正的测量,布局与绘制的工作。所以Measure,Layout,Draw三个方法只是作为监督者,其中Measure方法使用了final修饰符进行了修饰,我们无法对其进行重写与继承。我们一般通过重写onMeasure,onLayout,onDraw三个真正做工作的方法来对View进行自定义。
每一个View都存在于一个窗口(Window)里,每一个新窗口都会创建一个ViewRootImpl,作为这个控件树的根部,负责整个窗口中的所有View。View的测量,布局,绘制,沿着控件树,完成每个父View及子View的测量,布局,绘制的工作。
ViewRootImpl调用其performTraversals方法来对整个控件树进行遍历,在performTraversals方法中调用performMeasure方法,该方法调用view.Measure方法,对控件进测量。measure()方法接收两个参数,widthMeasureSpec和heightMeasureSpec,这两个值分别用于确定视图的宽度和高度的规格和大小。在测量之前,Measure方法会先判断是否需要测量,依据为:MeasureSpec是否发生了变化,强制布局位是否被设置;两个条件满足一个,便会调用onMeasure方法进行重新测量。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }以上是View中的onMeasure方法,内部调用了setMeasuredDimension方法来确定View的高,宽的测量值。setMeasuredDimension方法被调用之后,我们就可以使用getMeasuredWidth和getMeasuredHeight方法来获得View的高,宽的测量值。
一个ViewGroup内部包含多个View。ViewGroup中定义了一个measureChildren()方法来去测量子视图的大小,如下所示:
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) { final int size = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < size; ++i) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } }measureChildren方法中,一次对每一个child遍历,如果child满足一定条件,就会对其调用measureChild方法。
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }在measureChild方法中,最终child作为View类,调用了内部的measure方法,进行测量。measure方法的最后,View会设置强制布局标志位,保证下面的layout可以顺利进行。
performTraversals()方法继续执行,调用View的layout()方法来执行布局过程,如下所示:
public void layout(int l, int t, int r, int b) { int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; boolean changed = setFrame(l, t, r, b); if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) { if (ViewDebug.TRACE_HIERARCHY) { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT); } onLayout(changed, l, t, r, b); mPrivateFlags &= ~LAYOUT_REQUIRED; if (mOnLayoutChangeListeners != null) { ArrayList<OnLayoutChangeListener> listenersCopy = (ArrayList<OnLayoutChangeListener>) 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 &= ~FORCE_LAYOUT; }
在layout()方法中,首先会调用setFrame()方法来判断视图的大小和位置是否发生过变化。第7行,layout方法会进行判断,如果View的大小和位置都未发生变化,而且强制布局位也未被设置,便不会调用onLayout方法进行重新布局。若两个条件满足其一,就会在接下来的第11行调用onLayout方法。
View中的onLayout()方法是一个空方法,因为onLayout()过程是为了确定视图在布局中所在的位置,而这个操作应该是由布局来完成的,即父视图决定子视图的显示位置。而ViewGroup中的onLayout()方法是一个抽象方法,这就意味着所有ViewGroup的子类都必须重写这个方法。正如LinearLayout、RelativeLayout等布局,都是重写了这个方法,然后在内部按照各自的规则对子视图进行布局的。
接下来继续执行Draw方法,并在其中调用onDraw方法,一次绘制背景,内容,子View以及滚动条。每个视图都是有滚动条的,TextView,Button等,我们都可以通过设置来让滚动条显示出来。
这也就是为什么调用requestLayout方法会调用onMeasure,onLayout,onDraw三个方法, 因为requestLayout方法内部重新设置了强制布局位。
@CallSuper public void requestLayout() { if (mMeasureCache != null) mMeasureCache.clear(); if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) { // Only trigger request-during-layout logic if this is the view requesting it, // not the views in its parent hierarchy ViewRootImpl viewRoot = getViewRootImpl(); if (viewRoot != null && viewRoot.isInLayout()) { if (!viewRoot.requestLayoutDuringLayout(this)) { return; } } mAttachInfo.mViewRequestingLayout = this; } mPrivateFlags |= PFLAG_FORCE_LAYOUT; mPrivateFlags |= PFLAG_INVALIDATED; if (mParent != null && !mParent.isLayoutRequested()) { mParent.requestLayout(); } if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) { mAttachInfo.mViewRequestingLayout = null; } }
第15,16,18行,requestLayout不仅重置了自己的强制布局位,还会设置自己父亲的强制标志为位,如此不断传递,从而引起了整个控件树的测量,布局,绘制。
invalidate()方法和postinvalidate方法设置了脏标志位,只会调用onDraw方法进行绘制,而不会进行侧量和布局的工作。
mPrivateFlags |= PFLAG_DIRTY;
- Android中的View全解析(一)
- Android中的View全解析(二)
- Android中的View全解析(三)
- Android中的view全解析(四)
- Android View 全解析(一) -- 窗口管理系统
- Android Service全解析(一)
- Android CardView全解析(一)
- android中的动画全解析
- Android View 源码解析(一)
- Android数据存储全解析(一)
- Android架构(一)MVP全解析
- Android架构(一)MVP全解析
- Android动画全解析(一)
- Android架构(一)MVP全解析
- Android架构(一)MVP全解析
- Android事件分发、View事件Listener全解析
- Android事件分发、View事件Listener全解析
- Android中的广播使用全解析
- Spring学习笔记-IOC高级特性2-Spring表达式
- 全栈学习计划
- 2017.4.15笔记
- [WebView]WebView中的H5叕定位不了
- Codeforces Round #409 (Div. 2) D. Volatile Kite 计算几何、凸多边形、线段类
- Android中的View全解析(一)
- 机械节能产品生产企业网站织梦模板
- VL01N时【对于直到所选日期的交货没有到期的计划行】解决方案
- Oracle 11.2.0.3 linux x86_64 使用sqlplus 搭建 dataguard
- 转:如何解决win10与Ubuntu16.04时间不同步的问题
- 十四、自定义Jackson ObjectMapper把Long型转化为String类型
- effective javascript(四)——避免对于混合类型使用==运算符
- 坚持#第166天~心中有一个辛德勒不够好的信念,要珍惜缘分
- Spring配置FreeMarker