Android UI绘制分析(一)-解析布局资源,完成布局

2019-07-08  本文已影响0人  张小凡凡

本文源码基于 Android sdk 26, 为了逻辑清晰,省略了无关代码,不排除后期重新加上相关代码

当用户在activity中 调用setContentView 方法时,将自己的布局id 设置到activity中。而activity通过内部持有的window对象 来设置。

   /**
     * Set the activity content from a layout resource.  The resource will be  inflated, 
     * adding all top-level views to the activity.

     * @param layoutResID Resource ID to be inflated.
     */
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

getWindow()方法 返回 内部持有的window对象,其具体实例是PhoneWindow。

 mWindow = new PhoneWindow(this, window, activityConfigCallback);
phoneWindow 实现setContentView操作

显然,我们需要看看phoneWindow中setContentView做了什么。

    @Override
    public void setContentView(int layoutResID) {

        if (mContentParent == null) {
            installDecor();
        } 
        mLayoutInflater.inflate(layoutResID, mContentParent);
        mContentParent.requestApplyInsets();
    }

mContentParent 实际上就是一个ViewGroup, setContentView方法实际上就把设置的布局资源解析成View后,再添加到 mContentParent中,完成整个布局。so,这个mContentParent从何而来,

1. 根据Activity主题配置,创建DecorView

phoneWindow的setContentView 方法中,首先调用installDecor(),初始化DecorView , 和mContentParent。

private void installDecor() {
        if (mDecor == null) {
            //创建 DecorView
            mDecor = generateDecor(-1);
        } 
        if (mContentParent == null) {
           //解析mContetnParent
            mContentParent = generateLayout(mDecor);
        }
}

而DecorView 本质上就是一个FrameLayout。

public class DecorView extends FrameLayout

我们先看如何初始化DecorView。

    protected DecorView generateDecor(int featureId) {
        return new DecorView(context, featureId, this, getAttributes());
    }

嗯,很简单。 我们再看如何初始化 mContentView

protected ViewGroup generateLayout(DecorView decor) {
        int layoutResource;
        int features = getLocalFeatures();
        //根据activity主题中设置的参数 获取不同的系统内置的布局资源id
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;

        }  else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;

        } else {
            layoutResource = R.layout.screen_simple;
        }
        //将获取到布局资源id 添加到mDecor中
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

        //系统内置全部布局资源中 都会一个id为ID_ANDROID_CONTENT 的 ViewGroup,获取到并赋值给contentParent
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }
        return contentParent;
    }

2. 解析用户设置的view,并添加到DecorView中

在上面方法中,第一步,会根据当前activity的主题信息 加载不同的布局资源,并添加到DecorView中。
第二步,从布局资源中获取 id为 ID_ANDROID_CONTENT 的view 并赋值给contentParent 返回,这个view便是上文中 mContentParent,在之后,会将用户自己的布局 添加到mContentParent中,从而达到界面显示的目的。

    /**
     *DecorView 直接解析根据主题配置 获取到的系统布局id,并添加到自己身上
     */
    void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        final View root = inflater.inflate(layoutResource, null);
        addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }

所以最终一个Activity界面显示的布局应该是这样的:

activity界面显示布局视图.png
总结

所以一个View是如何被添加到屏幕窗口上的呢。

  1. 创建顶层布局容器DecorView
  2. 在顶层布局中加载基础布局ViewGroup
  3. 将ContentView添加到基础布局中的FrameLayout中
上一篇下一篇

猜你喜欢

热点阅读