View源码绘制流程

本文基于Android API 28

绘制三大方法

onMeasure

onMeasure(int, int)

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {         
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

在onMeasure方法用来测量view和他的内容,来决定view的宽高,该方法被measure(int, int)调用,并且需要被子类重写去测量view的内容的精确、有效的宽高;

重写该方法时,必须调用setMeasureDimension(int, int)方法去保存该view测量的宽和高;如果不这样做,measure(int, int)方法会抛出IllegalStateException异常,调用父类的onMeasure(int, int)方法可以避免这样;

基类实现了测量默认的背景尺寸,除非MeasureSpec允许更大的尺寸,子类应该重写onMeasure(int, int)方法去提供更好的测量他们的内容;

如果重写该方法,子类有责任确保实测宽高至少是view的最小宽高;

该方法在View类里面直接调用setMeasureDimension方法去保存默认的view的尺寸,参数直接调用getDefaultSize方法获取默认宽高;

getDefaultSize(int, int);

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;
}

这个方法返回默认的尺寸,如果MeasureSpec没有添加约束,则使用提供的尺寸,如果MeasureSpec允许,则将尺寸放大;即当MeasureSpec的Mode为UNSPECIFIED时,没有约束,使用默认的大小尺寸,为AT_MOST和EXACTLY时,需要计算出尺寸的大小;

getSuggestedMinimumHeight() /Width()

protected int getSuggestedMinimumHeight() {
        return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}

protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

这两个方法返回view建议的最小宽高以及背景的最小宽高的最大值;调用这两个方法时,需要注意返回的宽高应该在父View的要求范围内;

setMeasuredDimension(int, int)

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);
}

该方法必须由onMeasure(int, int)方法调用来保存实测的view宽高,否则将抛出异常;这个方法里面涉及到要判断View及其父View是否为ViewGroup及是否有光学效果,来决定是否要处理关于这些效果产生的尺寸影响;

isLayoutModeOptical(Object)

public static boolean isLayoutModeOptical(Object o) {
        return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical();
}

该方法返回该View是否为ViewGroup,并且是否由光效效果,例如阴影、亮光等,这些也将影响View的尺寸测量;

getOpticalInsets()

public Insets getOpticalInsets() {
    if (mLayoutInsets == null) {
            mLayoutInsets = computeOpticalInsets();
    }
    return mLayoutInsets;
}

该方法返回当前View的插图属性,如果为空,则去计算;

setOpticalInsets(Insets)

public void setOpticalInsets(Insets insets) {
    mLayoutInsets = insets;
}

为该View设置Insets,该方法不请求layout,如果手动调用该方法设置了Insets,则需要调用requestLayout()方法;

computeOpticalInsets()

Insets computeOpticalInsets() {
    return (mBackground == null) ? Insets.NONE : mBackground.getOpticalInsets();
}

如果当前View没有背景图片,则返回Insets.NONE,否则去计算背景图片的光学效果范围;

setMeasuredDimensionRaw(int, int)

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;
        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

该方法中最终保存View的尺寸,并设置标志位为PFLAG_MEASURED_DIMENSION_SET;

MeasureSpec

MeasureSpec类封装了从父View传递到子View的布局要求;包含size和mode;

private static final int MODE_SHIFT = 30;
private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

三种Mode

·UNSPECIFIED(00….)

public static final int UNSPECIFIED = 0 << MODE_SHIFT;

父布局对子View没有任何限制,子View可以是任何大小;

·EXACTLY(01….)

public static final int EXACTLY     = 1 << MODE_SHIFT;

父布局给了子View一个准确的大小约束,不管子View想要多大,都会受到该限制;

·AT_MOST(10….)

public static final int AT_MOST     = 2 << MODE_SHIFT;

子View可是达到他想要的任何指定大小;

makeMeasureSpec(int, int)

public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size, @MeasureSpecMode int mode) {
    if (sUseBrokenMakeMeasureSpec) {
        return size + mode;
    } else {
        return (size & ~MODE_MASK) | (mode & MODE_MASK);
    }
}

该方法提供了Size和Mode的算法;

getMode(int)、getSize(int)

这两个方法分别获取View的MeasureSpec的Mode和Size;

onLayout

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}

该方法是一个空实现,需要子View自己去实现是否需要为他们的子View重新计算大小和位置;

onDraw

protected void onDraw(Canvas canvas) {
}

该方法也是一个空实现,子View自己定义绘制内容;

View创建

加载xml布局

LayoutInflater.from(context).inflate(R.layout.activity_base, null);

以上代码用来加载定义好的xml文件,加载布局;

LayoutInflater#from(context)

public static LayoutInflater from(Context context) {
    LayoutInflater LayoutInflater =
        (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    if (LayoutInflater == null) {
        throw new AssertionError("LayoutInflater not found.");
    }
    return LayoutInflater;
}

在代码中,通过Context的getSystemService方法去获取到LayoutInflater的实例;

LayoutInflater#inflate()

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
    return inflate(resource, root, root != null);
}

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    if (DEBUG) {
        (TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                + Integer.toHexString(resource) + ")");
    }

    final XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}

这里inflate通过内容调用到另一个重载的inflate方法,这里获取到了Resource资源,并且获取到XmlResourceParser对象实例,用来解析Xml布局,然后最终调用到了inflate(XmlPullParser, ViewGroup, boolean)方法;

LayoutInflater#inflate(XmlPullParser, ViewGroup, boolean)

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        ... ...
        final String name = parser.getName();
            ... ...
            if (TAG_MERGE.equals(name)) {
            ... ...
            rInflate(parser, root, inflaterContext, attrs, false);
        } else {
                    // Temp is the root view that was found in the xml
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                    ViewGroup.LayoutParams params = null;
                    if (root != null) {
                        ... ....
                            // Create layout params that match root, if supplied
                            params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                                // Set the layout params for temp if we are not
                                // attaching. (If we are, we use addView, below)
                                temp.setLayoutParams(params);
                            }
                    }
                    ... ...
                    // Inflate all children under temp against its context.
                    rInflateChildren(parser, temp, attrs, true);
                    ... ...
                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    if (root != null && attachToRoot) {
                            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) {
                            result = temp;
                    }
            }
            ... ...
        return result;
    }
}

这里省略掉部分代码,使用Xml解析器去解析Xml,获取Xml标签,先忽略Xml开始和结束等标签,直接看创建View的部分,这里判断是否为merge标签,进入else分支,这里的核心代码是调用createViewFromTag(root, name, inflaterContext, attrs)方法去创建View,后面是判断是否有父布局,获取父布局参数等信息,将创建好的View添加到父布局;

LayoutInflater#createViewFromTag(View, String, Context, AttributeSet, boolean)

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) {
    ... ...
    try {
        View view;
        if (mFactory2 != null) {
            view = mFactory2.onCreateView(parent, name, context, attrs);
        } else if (mFactory != null) {
            view = mFactory.onCreateView(name, context, attrs);
        } else {
            view = null;
        }
        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('.')) {
                    view = onCreateView(parent, name, attrs);
                        } else {
                    view = createView(name, null, attrs);
                        }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
            }
            return view;
    ... ...
}

在该方法中,首先先判断了三个factory,这两个factory在通过LayoutInflater.from().inflate()方法在加载布局的时候都为空,这个Factory是在创建View的时候可以去手动设置View的创建规则,实现自己定义View的创建,例如在AppComAppCompatActivity中就会初始化一个Factory对象去创建属于AppCompat风格的View,下面分析;

所以方法进入到下面if处,这里判断name中是否包含“.”,即是在判断是否为自定义View,其实两个方法最终调用的都是同一个方法,只是在系统的View时,会为其加上android.view的包名;

protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
    return createView(name, "android.view.", attrs);
}

LayoutInflater#creatView(String, String, AttrbuteSet)

public final View createView(String name, String prefix, AttributeSet attrs)
    throws ClassNotFoundException, InflateException {
    Constructor<? extends View> constructor = sConstructorMap.get(name);
    ... ...
    Class<? extends View> clazz = null;
    try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
            if (constructor == null) {
            // Class not found in the cache, see if it's real, and try to add it
            clazz = mContext.getClassLoader().loadClass(prefix != null ? (prefix + name) : name).asSubclass(View.class);
            ... ...
            constructor = clazz.getConstructor(mConstructorSignature);
            constructor.setAccessible(true);
            sConstructorMap.put(name, constructor);
        } else {
            // If we have a filter, apply it to cached constructor
            if (mFilter != null) {
                // Have we seen this name before?
                Boolean allowedState = mFilterMap.get(name);
                if (allowedState == null) {
                    // New class -- remember whether it is allowed
                    clazz = mContext.getClassLoader().loadClass(prefix != null ? (prefix + name) : name).asSubclass(View.class);
                    ... ...
                } else if (allowedState.equals(Boolean.FALSE)) {
                    failNotAllowed(name, prefix, attrs);
                }
            }
        }
        final View view = constructor.newInstance(args);
        ... ...
        return view;
        ... ...
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

这是是通过反射拿到View类,通过反射去调用对应的View的构造方法去创建VIew,所以这里明白了通过Xml创建布局的时候系统是通过反射来创建的;走到这里就可以回到View的构造方法了;

通过setContentView创建

在Activity的onCreate()方法中,通过setContentView()方法将布局加载进去,Activity继承自AppCompatActivity;

AppCompatActivity#setContentView()

public void setContentView(@LayoutRes int layoutResID) {
    this.getDelegate().setContentView(layoutResID);
}

这里调用geDelegate()的setContentView方法;

public AppCompatDelegate getDelegate() {
    if (this.mDelegate == null) {
        this.mDelegate = AppCompatDelegate.create(this, this);
    }
    return this.mDelegate;
}

AppCompatDelegate类中的getDelegate()方法返回一个AppCompatDelegate的实现类AppCompatDelegateImpl,所以方法的具体实现由AppCompatDelegateImpl类完成;

AppCompatDelegateImpl#setContentView()

public void setContentView(int resId) {
    this.ensureSubDecor();
    ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);
    contentParent.removeAllViews();
    LayoutInflater.from(this.mContext).inflate(resId, contentParent);
    this.mOriginalWindowCallback.onContentChanged();
}

到这里又看到了LayoutInflater的inflate()方法,流程又回到了步骤2.1中;不过这里需要注意的是,在Xml创建的流程中,在createViewFromTag()方法中有一个对factory的判断,在Xml创建时为空,但是在这里就不为空了,这里在AppCompatActivity中对其做了处理,在Factory中去创建自己风格的View,因为AppCompatActivity和之前的Activity的风格是略有不一样;

Activity继承自AppCompatActivity,看onCreate()方法;

AppCompatActivity#onCreate()

protected void onCreate(@Nullable Bundle savedInstanceState) {
    delegate.installViewFactory();
    delegate.onCreate(savedInstanceState);
    ... ...
    super.onCreate(savedInstanceState);
}

这里初始化了一个Factory对象,具体实现还是在AppCompatDelegateImpl中;

AppCompatDelegateImpl#installViewFactory()

public void installViewFactory() {
    LayoutInflater layoutInflater = LayoutInflater.from(this.mContext);
    if (layoutInflater.getFactory() == null) {
        LayoutInflaterCompat.setFactory2(layoutInflater, this);
    } else if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
        Log.i("AppCompatDelegate", "The Activity's LayoutInflater already has a Factory installed so we can not install AppCompat's");
    }
}

在这里为LayoutInflater设置了一个Factory2对象;在创建的View的时候,判断factory不为空,就通过factory去创建View,最终如果是自定义View,则通过反射去创建View,如果是系统自身的View,则对应到AppCompatViewInflater的createView()方法中,匹配要创建的View,分别有每个View对应的实现类;

通过代码new

通过代码new的话,直接去调用对应的View的构造方法;

文章已同步至GitPress博客:https://gitpress.io/@yangshijie/

    原文作者:丿北纬91度灬
    原文地址: https://www.jianshu.com/p/6fcc787fd347
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞