View的创建,measure(),layout(),invalidate()源码分析

View源码分析

简述

  • 主要分析从XML资源文件中生成View对象过程;
  • 以及View的构造函数,measure(),layout()方法分析;
  • invalidate()请求刷新重绘视图过程分析;
  • View自身touch事件处理onTouchEvent()方法分析

View对象的生成

一般View对象的生成有两种,从XML文件中通过LayoutInflater(子类PhoneLayoutInflater)的inflate()经反射生成对象,一种是直接new View(Context)直接通过构造函数生成;

在Activity.onCreate()中的setContentView(@LayoutRes int layoutResID),最终调用的是其window(PhoneWindow)的setContentView(int layoutResID):

@Override
public void setContentView(int layoutResID) {
    // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
    // decor, when theme attributes and the like are crystalized. Do not check the feature
    // before this happens.
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        // 填充资源文件中视图到activity的window的顶层视图内容部分
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
}

PhoneWindow是在Activity.attach()中生成的,且传入的Context是当前Activity对象本身,并由window的final Context mContext成员变量引用;

在为window初始化顶层视图DecorView后,会调用LayoutInflater的inflate(@LayoutRes int resource, @Nullable ViewGroup root)方法,下面具体看下该过程:

// root参数为由资源文件生成的view的父view
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
    return inflate(resource, root, root != null);
}

// attachToRoot参数表示是否将生成的view 添加add到root父view中
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    if (DEBUG) {
        Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                + Integer.toHexString(resource) + ")");
    }
    // 从资源文件中解析出"layout"即与布局相关的Parser对象
    final XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}

// 主要方法
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
        final Context inflaterContext = mContext;
        // 转换为AttributeSet属性集合
        final AttributeSet attrs = Xml.asAttributeSet(parser);
        Context lastContext = (Context) mConstructorArgs[0];
        mConstructorArgs[0] = inflaterContext;
        View result = root;

        try {
            // Look for the root node.
            int type;
            while ((type = parser.next()) != XmlPullParser.START_TAG &&
                    type != XmlPullParser.END_DOCUMENT) {
                // Empty
            }

            if (type != XmlPullParser.START_TAG) {
                throw new InflateException(parser.getPositionDescription()
                        + ": No start tag found!");
            }
            
            final String name = parser.getName();
            
            if (DEBUG) {
                System.out.println("**************************");
                System.out.println("Creating root view: "
                        + name);
                System.out.println("**************************");
            }
            // 首先看首节点是否属于"merge"标签,如果inflate时候没有给予父view,则会抛出异常
            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, inflaterContext, attrs, false);
            } else {
                // Temp is the root view that was found in the xml
                // 真正生成view的地方
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                ViewGroup.LayoutParams params = null;

                if (root != null) {
                    if (DEBUG) {
                        System.out.println("Creating params from root: " +
                                root);
                    }
                    // Create layout params that match root, if supplied
                    // 传入给予的父view不为空,则根据属性集合在父view中为当前生成的view生成一个LayoutParams布局参数对象
                    params = root.generateLayoutParams(attrs);
                    if (!attachToRoot) {
                        // Set the layout params for temp if we are not
                        // attaching. (If we are, we use addView, below)
                        // 如果传入父view不为空,一般attachToRoot==true,就不会在这里直接为生成的view赋值LayoutParams
                        temp.setLayoutParams(params);
                    }
                }

                if (DEBUG) {
                    System.out.println("-----> start inflating children");
                }

                // Inflate all children under temp against its context.
                // 这里去为当前生成的view填充其节点下面的子view
                rInflateChildren(parser, temp, attrs, true);

                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) {
                    // 这里将由xml生成的view树addview到传入的父view中
                    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) {
                    // 如果调用inflate()没有传入root父view,或者attachToRoot==false,则当前方法返回的对象是反射生成的view本身,否则返回的其实是父view即传入的root参数
                    result = temp;
                }
            }

        } catch (XmlPullParserException e) {
            InflateException ex = new InflateException(e.getMessage());
            ex.initCause(e);
            throw ex;
        } catch (Exception e) {
            InflateException ex = new InflateException(
                    parser.getPositionDescription()
                            + ": " + e.getMessage());
            ex.initCause(e);
            throw ex;
        } finally {
            // Don't retain static reference on context.
            mConstructorArgs[0] = lastContext;
            mConstructorArgs[1] = null;
        }

        Trace.traceEnd(Trace.TRACE_TAG_VIEW);

        return result;
    }
}

看下createViewFromTag()的实现

private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
    return createViewFromTag(parent, name, context, attrs, false);
}

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
        boolean ignoreThemeAttr) {
    if (name.equals("view")) {
        name = attrs.getAttributeValue(null, "class");
    }

    // Apply a theme wrapper, if allowed and one is specified.
    if (!ignoreThemeAttr) {
        final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
        final int themeResId = ta.getResourceId(0, 0);
        if (themeResId != 0) {
            context = new ContextThemeWrapper(context, themeResId);
        }
        ta.recycle();
    }

    if (name.equals(TAG_1995)) {
        // Let's party like it's 1995!
        return new BlinkLayout(context, attrs);
    }

    try {
        View view;
        // 一般情况这里的mFactory2==null,mFactory==null
        if (mFactory2 != null) {
            view = mFactory2.onCreateView(parent, name, context, attrs);
        } else if (mFactory != null) {
            view = mFactory.onCreateView(name, context, attrs);
        } else {
            view = null;
        }
        // 在acitivity.attach()中为其window的LayoutInflater mLayoutInflater设置了mPrivateFactory = acitivity本身.
        // 这里主要用于在xml中直接使用Fragment标签,会去acitivity中生成该Fragment对象并返回其Fragment.onCreatView()中返回的该fragment封装的view
        if (view == null && mPrivateFactory != null) {
            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
        }

        if (view == null) {
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {
                if (-1 == name.indexOf('.')) {
                    // 通过反射生成Android系统view
                    view = onCreateView(parent, name, attrs);
                } else {
                    // 通过反射生成我们自定义的view
                    view = createView(name, null, attrs);
                }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        }

        return view;
    } catch (InflateException e) {
        throw e;

    } catch (ClassNotFoundException e) {
        final InflateException ie = new InflateException(attrs.getPositionDescription()
                + ": Error inflating class " + name);
        ie.initCause(e);
        throw ie;

    } catch (Exception e) {
        final InflateException ie = new InflateException(attrs.getPositionDescription()
                + ": Error inflating class " + name);
        ie.initCause(e);
        throw ie;
    }
}

看下为当前节点view填充子节点view所调用的rInflateChildren()方法过程:

final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
        boolean finishInflate) throws XmlPullParserException, IOException {
    rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}

void rInflate(XmlPullParser parser, View parent, Context context,
        AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

    final int depth = parser.getDepth();
    int type;

    while (((type = parser.next()) != XmlPullParser.END_TAG ||
            parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

        if (type != XmlPullParser.START_TAG) {
            continue;
        }

        final String name = parser.getName();
        
        if (TAG_REQUEST_FOCUS.equals(name)) {
            parseRequestFocus(parser, parent);
        } else if (TAG_TAG.equals(name)) {
            // 解析"tag"标签节点
            parseViewTag(parser, parent, attrs);
        } else if (TAG_INCLUDE.equals(name)) {
            // 解析"include"标签节点
            if (parser.getDepth() == 0) {
                throw new InflateException("<include /> cannot be the root element");
            }
            parseInclude(parser, context, parent, attrs);
        } else if (TAG_MERGE.equals(name)) {
            // 由此可见"merge"节点只能是首节点
            throw new InflateException("<merge /> must be the root element");
        } else {
            // 这里同之前一样,先生成该节点view对象,然后循环填充该节点view的子view,最后会将该节点addview到该节点view的上一层级节点view即传入父节点view中.
            final View view = createViewFromTag(parent, name, context, attrs);
            final ViewGroup viewGroup = (ViewGroup) parent;
            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
            rInflateChildren(parser, view, attrs, true);
            viewGroup.addView(view, params);
        }
    }
    // 通知该父节点View其内部所有子view已经由xml打入填充完毕
    if (finishInflate) {
        parent.onFinishInflate();
    }
}
  • 可以看出,在调用Acitivity.setContentView()方法完成后,已经将视图树建立完毕,即将资源文件各个view节点分别通过反射生成对象然后addview到父view中,且将xml资源文件的根view填充依附到window的顶层视图容器DecorView的内容部分mContentParent中.这一过程中,所有view都在构造函数中做了初始化,且都设置了由父view容器调用generateLayoutParams()生成对应的LayoutParams对象.
  • 手动调用LayoutInflater.inflate()资源文件的时候,最大的区别就在inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) 中的root参数和attachToRoot参数设置,如果root为null则返回无设置LayoutParams()的xml根节点view对象,若root!=null,但是attachToRoot==false则返回是设置LayoutParams()的xml根节点view对象,若root!=null同时attachToRoot==true则将xml根view生成LayoutParams()且通过父view即root依附addview到root上

View的构造函数

view的构造函数有5个,除了无参数的构造函数一般最后都调用如下2个:

public View(Context context) {
    mContext = context;
    mResources = context != null ? context.getResources() : null;
    mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED;
    // Set some flags defaults
    mPrivateFlags2 =
            (LAYOUT_DIRECTION_DEFAULT << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) |
            (TEXT_DIRECTION_DEFAULT << PFLAG2_TEXT_DIRECTION_MASK_SHIFT) |
            (PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT) |
            (TEXT_ALIGNMENT_DEFAULT << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT) |
            (PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT) |
            (IMPORTANT_FOR_ACCESSIBILITY_DEFAULT << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT);
    mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS);
    mUserPaddingStart = UNDEFINED_PADDING;
    mUserPaddingEnd = UNDEFINED_PADDING;
    mRenderNode = RenderNode.create(getClass().getName(), this);

    if (!sCompatibilityDone && context != null) {
        final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;

        // Older apps may need this compatibility hack for measurement.
        sUseBrokenMakeMeasureSpec = targetSdkVersion <= JELLY_BEAN_MR1;

        // Older apps expect onMeasure() to always be called on a layout pass, regardless
        // of whether a layout was requested on that View.
        sIgnoreMeasureCache = targetSdkVersion < KITKAT;

        Canvas.sCompatibilityRestore = targetSdkVersion < M;

        // In M and newer, our widgets can pass a "hint" value in the size
        // for UNSPECIFIED MeasureSpecs. This lets child views of scrolling containers
        // know what the expected parent size is going to be, so e.g. list items can size
        // themselves at 1/3 the size of their container. It breaks older apps though,
        // specifically apps that use some popular open source libraries.
        sUseZeroUnspecifiedMeasureSpec = targetSdkVersion < M;

        sCompatibilityDone = true;
    }
}



public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    this(context);

    final TypedArray a = context.obtainStyledAttributes(
            attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);

    if (mDebugViewAttributes) {
        saveAttributeData(attrs, a);
    }

    Drawable background = null;

    int leftPadding = -1;
    int topPadding = -1;
    int rightPadding = -1;
    int bottomPadding = -1;
    int startPadding = UNDEFINED_PADDING;
    int endPadding = UNDEFINED_PADDING;

    int padding = -1;

    int viewFlagValues = 0;
    int viewFlagMasks = 0;

    boolean setScrollContainer = false;

    int x = 0;
    int y = 0;

    float tx = 0;
    float ty = 0;
    float tz = 0;
    float elevation = 0;
    float rotation = 0;
    float rotationX = 0;
    float rotationY = 0;
    float sx = 1f;
    float sy = 1f;
    boolean transformSet = false;

    int scrollbarStyle = SCROLLBARS_INSIDE_OVERLAY;
    int overScrollMode = mOverScrollMode;
    boolean initializeScrollbars = false;
    boolean initializeScrollIndicators = false;

    boolean startPaddingDefined = false;
    boolean endPaddingDefined = false;
    boolean leftPaddingDefined = false;
    boolean rightPaddingDefined = false;

    final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;

    final int N = a.getIndexCount();
    for (int i = 0; i < N; i++) {
        int attr = a.getIndex(i);
        switch (attr) {
        ....

        }

    }
    ....
    setOverScrollMode(overScrollMode);
    if (background != null) {
        setBackground(background);
    }
    internalSetPadding(
            mUserPaddingLeftInitial,
            topPadding >= 0 ? topPadding : mPaddingTop,
            mUserPaddingRightInitial,
            bottomPadding >= 0 ? bottomPadding : mPaddingBottom);
    if (x != 0 || y != 0) {
        scrollTo(x, y);
    }

    if (transformSet) {
        setTranslationX(tx);
        setTranslationY(ty);
        setTranslationZ(tz);
        setElevation(elevation);
        setRotation(rotation);
        setRotationX(rotationX);
        setRotationY(rotationY);
        setScaleX(sx);
        setScaleY(sy);
    }
    ....

}

可以看出,view的构造函数设置了初始化了一些基本属性,比如Context,mRenderNode等,主要还是解析并获取了对应属性集合中一些参数并赋值给成员变量,比如mBackground,mPaddingTop,mPaddingLeft,mPaddingRight,mPaddingBottom等等,为mRenderNode赋值设置translationX,translationY,rotationX等相关显示参数等;

measure()测量方法

view的measure()是由父view分发调用(最开始是从ViewRootImpl的performMeasure()测量DecroView开始的)的,传入的参数widthMeasureSpec和heightMeasureSpec,是由32位int值来表示,高2位代表该view的测量模式,剩余30位代表了实际了父view根据自身尺寸情况和子view布局参数生成的一个期望值.

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    // 通常没有设置layoutMode,这里为false
    boolean optical = isLayoutModeOptical(this);
    if (optical != isLayoutModeOptical(mParent)) {
        Insets insets = getOpticalInsets();
        int oWidth  = insets.left + insets.right;
        int oHeight = insets.top  + insets.bottom;
        widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
        heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
    }

    // Suppress sign extension for the low bytes
    // 生成mMeasureCache Long-long稀疏数组中保存测量值的key
    long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
    if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
    // 这里看flag的PFLAG_FORCE_LAYOUT位是否被强制置位,或者测量参数与上次是否有不一样
    if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
            widthMeasureSpec != mOldWidthMeasureSpec ||
            heightMeasureSpec != mOldHeightMeasureSpec) {

        // first clears the measured dimension flag
        // 清除flag的PFLAG_MEASURED_DIMENSION_SET位,表明view当前没有设置尺寸
        mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

        resolveRtlPropertiesIfNeeded();
        // 查询保存在缓存中是否有当前测量值的key,有的话同时flag的PFLAG_FORCE_LAYOUT位没有强制设置的话则不需要重新onMeasure()测量
        int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
                mMeasureCache.indexOfKey(key);
        if (cacheIndex < 0 || sIgnoreMeasureCache) {
            // measure ourselves, this should set the measured dimension flag back
            // 这里去具体设置该view的测量宽mMeasuredWidth和高mMeasuredHeight
            onMeasure(widthMeasureSpec, heightMeasureSpec);
            // 清除flag3的PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT位
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        } else {
            long value = mMeasureCache.valueAt(cacheIndex);
            // Casting a long to int drops the high 32 bits, no mask needed
            setMeasuredDimensionRaw((int) (value >> 32), (int) value);
            // 如果是直接拿缓存没有走onMeasure()则在这里置该位,在layout()中再去onMeasure()
            mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        // flag not set, setMeasuredDimension() was not invoked, we raise
        // an exception to warn the developer
        // 这里检查一下,防止子类重新没有真正为该view赋值设置测量宽高
        if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
            throw new IllegalStateException("View with id " + getId() + ": "
                    + getClass().getName() + "#onMeasure() did not set the"
                    + " measured dimension by calling"
                    + " setMeasuredDimension()");
        }
        // 置flag的PFLAG_LAYOUT_REQUIRED位表明需要走layout()
        mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
    }
    // 保存当前由父view下发的测量值
    mOldWidthMeasureSpec = widthMeasureSpec;
    mOldHeightMeasureSpec = heightMeasureSpec;

    mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
            (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}

// View默认的onMeasure()
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

// 这里可以看出,view默认的测量中layoutParams宽高设置为match_parent和wrap_content效果一样,均为父view计算给出的size
// 所以自定义view一般要重写onMeasure()区分设置layoutParams为wrap_content情况
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;
}


protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
    // 一般情况没有设置layoutmode时候,为false
    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);
}

// 这里真正为该view赋值mMeasuredWidth和mMeasuredHeight,同时标记flag的PFLAG_MEASURED_DIMENSION_SET位,表明已经设置好测量宽高
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
    mMeasuredWidth = measuredWidth;
    mMeasuredHeight = measuredHeight;
    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

view的measure方法是final修饰的,通常在派生类中重新onMeasure()方法可以根据layoutParams的情况(match_parent或是wrap_content)重新设置赋值测量的宽和高.对于自定义ViewGroup,还要在onMeasure()中去遍历测量其子view.

layout()布局方法

View的layout()方法也是由父view分发调用的,最开始是从ViewRootImpl的performLayout()开始的

/**
 * @param l Left position, relative to parent 该view左边界距离父容器view左边界距离
 * @param t Top position, relative to parent 该view上边界距离父容器view上边界距离
 * @param r Right position, relative to parent 该view右边界距离父容器view左边界距离
 * @param b Bottom position, relative to parent 该view下边界距离父容器view上边界距离
 */
// 很明显,传入的参数分别是当前view相对父view的左边界,上边界,由边界,下边界的距离
public void layout(int l, int t, int r, int b) {
    // 这里如果在上面的measeure()中是直接从缓存中拿的没有走onMeasure()的话要去走一次onMeasure()
    if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
        onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
        mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    }
    // 首先记录上次该view四边与其父view左边界,上边界相应的距离,仅仅用于通知位置改变回调
    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;

    // ture 表示该view在父view中位置发生变化,即该view有一边与其父view左or上边界相应的距离发生改变
    // 真正改变位置的一般是在setFrame()中
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    // 如果该view位置改变,或者flag的PFLAG_LAYOUT_REQUIRED被强制置位,则去重新onLayout()布局子view
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        // 当前该view位置在其父view中位置变化了,则要重新去布局其子view,当然如果有子view的话
        // 一般想系统的LinearLayout,FrameLayout,RelativeLayout以及自定义viewGroup进要重新该方法
        // 去分配设置子view的相对该view的位置,View.java默认为空实现,ViewGroup一般要去重写该方法
        onLayout(changed, l, t, r, b);
        // 自己和子view都已经分配好位置后,清除flag的PFLAG_LAYOUT_REQUIRED位
        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);
            }
        }
    }
    // 清除flag的PFLAG_FORCE_LAYOUT位
    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}

// 为当前view指定其在父容器view中具体位置的是在该方法中
protected boolean setFrame(int left, int top, int right, int bottom) {
    boolean changed = false;

    if (DBG) {
        Log.d("View", this + " View.setFrame(" + left + "," + top + ","
                + right + "," + bottom + ")");
    }

    if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
        changed = true;

        // Remember our drawn bit
        // 首先记录当前的flag的PFLAG_DRAWN位
        int drawn = mPrivateFlags & PFLAG_DRAWN;

        // 每个view都是一个矩形,实际宽度为对应边界
        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
        // 该view的位置即将改变,先去刷新一遍该view的当前视图区域
        invalidate(sizeChanged);
        // 分配赋值新位置,同时更新RenderNode mRenderNode的参数
        mLeft = left;
        mTop = top;
        mRight = right;
        mBottom = bottom;
        mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
        // 置位flag的PFLAG_HAS_BOUNDS位,表示该view有边界束缚范围了
        mPrivateFlags |= PFLAG_HAS_BOUNDS;


        // view宽度or高度变化的方法通知
        if (sizeChanged) {
            sizeChange(newWidth, newHeight, oldWidth, oldHeight);
        }

        if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {
            // If we are visible, force the DRAWN bit to on so that
            // this invalidate will go through (at least to our parent).
            // This is because someone may have invalidated this view
            // before this call to setFrame came in, thereby clearing
            // the DRAWN bit.
            // 因为一个invalidate()过程开始进入后会清除flag的PFLAG_DRAWN位,直到当次UI绘制结束会重新置该位表示当次UI绘制完成
            // 所以这里为了保证一定走该方法去刷新视图,故先强制设置
            mPrivateFlags |= PFLAG_DRAWN;
            // 这里是真正去刷新位置变化后该view的视图了
            invalidate(sizeChanged);
            // parent display list may need to be recreated based on a change in the bounds
            // of any child
            invalidateParentCaches();
        }

        // Reset drawn bit to original value (invalidate turns it off)
        // 在invalidate()方法调用之后,恢复之前 的flag的PFLAG_DRAWN位
        mPrivateFlags |= drawn;
        // 该view位置变化的同时告知其背景size改变了
        mBackgroundSizeChanged = true;
        if (mForegroundInfo != null) {
            mForegroundInfo.mBoundsChanged = true;
        }

        notifySubtreeAccessibilityStateChangedIfNeeded();
    }
    return changed;
}

从上面看出,view的layout()主要为该view指定其在父容器view中位置,且会两次调用invalidate()方法去刷新UI,之后会走onLayout()方法,如果该view本身是一个容器ViewGroup子类的话,则需要在该方法中调用子view的layout()方法为其子view分配指定相对其左边界和上边界的相对位置

invalidate()

这个方法通常用来刷新某个view的UI视图,主要是会调用onDraw()方法;

该方法内部十分复杂,下面简要分析其大概过程

/**
 * Invalidate the whole view. If the view is visible,
 * {@link #onDraw(android.graphics.Canvas)} will be called at some point in
 * the future.
 * <p>
 * This must be called from a UI thread. To call from a non-UI thread, call
 * {@link #postInvalidate()}.
 */
// 由注释可知,调用该方法必须在主线程且会在随后调用onDraw()方法
public void invalidate() {
    invalidate(true);
}

/**
 * This is where the invalidate() work actually happens. A full invalidate()
 * causes the drawing cache to be invalidated, but this function can be
 * called with invalidateCache set to false to skip that invalidation step
 * for cases that do not need it (for example, a component that remains at
 * the same dimensions with the same content).
 *
 * @param invalidateCache Whether the drawing cache for this view should be
 *            invalidated as well. This is usually true for a full
 *            invalidate, but may be set to false if the View's contents or
 *            dimensions have not changed.
 */
// invalidateCache为ture会使view的flag的置位PFLAG_INVALIDATED,清除PFLAG_DRAWING_CACHE_VALID位
void invalidate(boolean invalidateCache) {
    invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
        boolean fullInvalidate) {
    if (mGhostView != null) {
        mGhostView.invalidate(true);
        return;
    }
    // 一般如果该view不是VISIBLE状态且不处于动画状态则中断刷新UI
    if (skipInvalidate()) {
        return;
    }

    // 首先判断PFLAG_DRAWN位上传UI绘制是否完成,PFLAG_HAS_BOUNDS位是否已经指定该view四边界位置,在layout()的setFrame()方法调用中置位了
    if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
            || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
            || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
            || (fullInvalidate && isOpaque() != mLastIsOpaque)) {
        if (fullInvalidate) {
            mLastIsOpaque = isOpaque();
            // 这里清楚该位表明当次UI绘制已经开始没有完成
            mPrivateFlags &= ~PFLAG_DRAWN;
        }

        mPrivateFlags |= PFLAG_DIRTY;
        // PFLAG_INVALIDATED表示需要重新创建View的DisplayListCanvas
        // PFLAG_DRAWING_CACHE_VALID表示view的Bitmap drawing cache缓存有效可用,清除该位表明需要
        // 去创建该view的Bitmap drawing cache
        if (invalidateCache) {
            mPrivateFlags |= PFLAG_INVALIDATED;
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
        }

        // Propagate the damage rectangle to the parent view.
        // mAttachInfo这个成员变量是在绘制显示窗口顶层视图时候创建ViewRootImpl这个顶级ViewParent对象的时候生成的
        // 并在performTraversals()中调用host.dispatchAttachedToWindow(mAttachInfo, 0)方法沿着view树体系向下赋值引用
        final AttachInfo ai = mAttachInfo;
        // 每个view的容器父view都赋值引用到子View的mParent成员变量,最顶层的容器View即DecorView的mParent指向了顶级ViewParent即ViewRootImpl对象
        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);
        }

        // Damage the entire projection receiver, if necessary.
        if (mBackground != null && mBackground.isProjected()) {
            final View receiver = getProjectionReceiver();
            if (receiver != null) {
                receiver.damageInParent();
            }
        }

        // Damage the entire IsolatedZVolume receiving this view's shadow.
        if (isHardwareAccelerated() && getZ() != 0) {
            damageShadowReceiver();
        }
    }
}

由上面可以看出,view.invalidate()最后是把其自身边界尺寸传递到上一层父view中,让父view调用invalidateChild()方法,一般父容器view是ViewGroup这个实现了ViewParent的抽象类的派生类,最顶层的容器View即DecorView的mParent指向了顶级ViewParent即ViewRootImpl,下面看下ViewGroup的invalidateChild()方法:

public final void invalidateChild(View child, final Rect dirty) {

    ViewParent parent = this;
    ....
    final int[] location = attachInfo.mInvalidateChildLocation;
    location[CHILD_LEFT_INDEX] = child.mLeft;
    location[CHILD_TOP_INDEX] = child.mTop;

    ....

    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先就是当前this对象
            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);

}


/**
 * Don't call or override this method. It is used for the implementation of
 * the view hierarchy.
 *
 * This implementation returns null if this ViewGroup does not have a parent,
 * if this ViewGroup is already fully invalidated or if the dirty rectangle
 * does not intersect with this ViewGroup's bounds.
 */
// 联合设置统一当前需要重绘的UI视图矩形与该view本身的四边界
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
    if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||
            (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) {
        if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) !=
                    FLAG_OPTIMIZE_INVALIDATE) {
            dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
                    location[CHILD_TOP_INDEX] - mScrollY);
            if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) {
                dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
            }

            final int left = mLeft;
            final int top = mTop;

            if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
                if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) {
                    dirty.setEmpty();
                }
            }
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;

            location[CHILD_LEFT_INDEX] = left;
            location[CHILD_TOP_INDEX] = top;

            if (mLayerType != LAYER_TYPE_NONE) {
                mPrivateFlags |= PFLAG_INVALIDATED;
            }

            return mParent;

        } else {
            mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID;

            location[CHILD_LEFT_INDEX] = mLeft;
            location[CHILD_TOP_INDEX] = mTop;
            if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
                dirty.set(0, 0, mRight - mLeft, mBottom - mTop);
            } else {
                // in case the dirty rect extends outside the bounds of this container
                dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
            }

            if (mLayerType != LAYER_TYPE_NONE) {
                mPrivateFlags |= PFLAG_INVALIDATED;
            }

            return mParent;
        }
    }

    return null;
}

invalidateChild()里面的do..while()循环一直不端的调用invalidateChildInParent(),invalidateChildInParent()返回父ViewParent并联合设置统一当前需要重绘的UI矩形与view本身四边界,直到view树向上到达ViewRootImpl的invalidateChildInParent()方法返回null中断循环,看下顶层ViewParent实现类的ViewRootImpl的invalidateChildInParent()方法;

@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    // 检查方法调用的线程是否就是UI线程即APP主线程,不是则抛出异常,这也是为何invaldate()要在主线程调用的直接原因
    checkThread();
    if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);

    if (dirty == null) {
        invalidate();
        return null;
    } else if (dirty.isEmpty() && !mIsAnimating) {
        return null;
    }

    if (mCurScrollY != 0 || mTranslator != null) {
        mTempRect.set(dirty);
        dirty = mTempRect;
        if (mCurScrollY != 0) {
            dirty.offset(0, -mCurScrollY);
        }
        if (mTranslator != null) {
            mTranslator.translateRectInAppWindowToScreen(dirty);
        }
        if (mAttachInfo.mScalingRequired) {
            dirty.inset(-1, -1);
        }
    }

    invalidateRectOnScreen(dirty);

    return null;
}

// mThread即当前APP进程主线程,在ViewRootImpl构造函数中赋值,因为ViewRootImpl必然是在主线程创建的
void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

// 合并由view树体系向上传来的需要重绘的区域与成员变量mDirty区域,并保存在成员变量mDirty中
// 之后调用scheduleTraversals()向主线程Looper的MessageQueue消息队列发送一个Message去请求刷新视图了
private void invalidateRectOnScreen(Rect dirty) {
    final Rect localDirty = mDirty;
    if (!localDirty.isEmpty() && !localDirty.contains(dirty)) {
        mAttachInfo.mSetIgnoreDirtyState = true;
        mAttachInfo.mIgnoreDirtyState = true;
    }

    // Add the new dirty rect to the current one
    localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
    // Intersect with the bounds of the window to skip
    // updates that lie outside of the visible region
    final float appScale = mAttachInfo.mApplicationScale;
    final boolean intersected = localDirty.intersect(0, 0,
            (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
    if (!intersected) {
        localDirty.setEmpty();
    }
    if (!mWillDrawSoon && (intersected || mIsAnimating)) {
        scheduleTraversals();
    }
}

如果当前已经有一个UI刷新过程在进行中的时候,mWillDrawSoon为true,则不再去刷新视图了;

onTouchEvent()

不管是ViewGroup容器还是View本身,首先都是一个View,作为View都有自身的基本的touch事件处理逻辑,即View类中的onTouchEvent()方法;
如果是ViewGroup的话,一般是没有子view处理分发的事件或者自己拦截了事件强制分发到自身处理,然后调用其onTouchEvent()方法

PS:写到这里,不想在这篇文章分析view的touch事件处理了,今天中秋节,想想很有空,还是写一篇View和ViewGroup的事件分发,拦截,自身处理的源码分析文章.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容