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()方法来执行布局过程,如下所示:

host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);
layout()方法接收四个参数,分别代表着左、上、右、下的坐标,layout方法的坐标是相对于当前视图的父视图而言的。可以看到,这里还把刚才测量出的宽度和高度传到了layout()方法中。那么我们来看下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;

0 0
原创粉丝点击