美文网首页Android技术知识Android开发Android基础知识
Android视图相关之setContentView()源码分析

Android视图相关之setContentView()源码分析

作者: zl_adams | 来源:发表于2019-04-27 22:53 被阅读4次

日常使用Activity时都会用到setContentView(int layoutId)的方法,今天挖一下相关的源码,方便日后装逼
(学会这个好像最直接的作用就是可以装逼)

1、setContentView(int layoutId)

(基于Android8.1)

    @Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }
    
    -->
    @NonNull
    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }

    -->
    /**
    *  AppCompatDelegateImplV9是下面几个AppCompatDelegateImplX 类的父类
    *  setContentView在AppCompatDelegateImplV9中实现
    */
    private static AppCompatDelegate create(Context context, Window window,
            AppCompatCallback callback) {
        if (Build.VERSION.SDK_INT >= 24) {
            return new AppCompatDelegateImplN(context, window, callback);
        } else if (Build.VERSION.SDK_INT >= 23) {
            return new AppCompatDelegateImplV23(context, window, callback);
        } else if (Build.VERSION.SDK_INT >= 14) {
            return new AppCompatDelegateImplV14(context, window, callback);
        } else if (Build.VERSION.SDK_INT >= 11) {
            return new AppCompatDelegateImplV11(context, window, callback);
        } else {
            return new AppCompatDelegateImplV9(context, window, callback);
        }
    }

setContentView在AppCompatDelegateImplV9中实现,其他类最终都继承了AppCompatDelegateImplV9,调用AppCompatDelegateImplV9中的setContentView() 。

class AppCompatDelegateImplV9{

  private ViewGroup mSubDecor;
    @Override
    public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);//常见的inflate layout
        mOriginalWindowCallback.onContentChanged();//调用onContentChanged()的生命周期方法
    }

    -->
    private void ensureSubDecor() {
        if (!mSubDecorInstalled) {
            mSubDecor = createSubDecor();//初始化mSubDecor
            
            mSubDecorInstalled = true;//标记mSubDecor已经初始化

            ...
        }
    }
}

重点方法在createSubDecor(), 会关联到了PhoneWindow和DecorView。

2、createSubDecor()

    private ViewGroup createSubDecor() {
        //获取主题属性
        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);

        //根据activity设置的window属性,配置activity风格样式
        if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
            requestWindowFeature(Window.FEATURE_NO_TITLE);//无title
        } else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.
            requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);//设置actionbar
        }
        if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) {
             //ActionBar的Overlay模式,让ActionBar浮动且透明
            requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
        }
        if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) {
            requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY);
        }
        mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false);//悬浮
        a.recycle();

        // Now let's make sure that the Window has installed its decor by retrieving it
        mWindow.getDecorView();//给mWindow创建一个DecorView

        final LayoutInflater inflater = LayoutInflater.from(mContext);
        ViewGroup subDecor = null;//subDecor会作为结果返回出去

        //根据主题属性的标志来决定实现哪个layout,然后赋值给subDecor 
        if (!mWindowNoTitle) {
            if (mIsFloating) {
                // If we're floating, inflate the dialog title decor
                subDecor = (ViewGroup) inflater.inflate(
                        R.layout.abc_dialog_title_material, null);
            } else if (mHasActionBar) {
                // Now inflate the view using the themed context and set it as the content view
                subDecor = (ViewGroup) LayoutInflater.from(themedContext)
                        .inflate(R.layout.abc_screen_toolbar, null);
                ...
            }
        } else {
            if (mOverlayActionMode) {
                subDecor = (ViewGroup) inflater.inflate(
                        R.layout.abc_screen_simple_overlay_action_mode, null);
            } else {
                subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
            }
        }
        ...

        //获取内容contentView,上面的四个layout中都有id为action_bar_activity_content的子view
        final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
                R.id.action_bar_activity_content);

        // 获取PhoneWindow中的content布局对象,这里其实就是mWindow的mContentParent
        final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
        if (windowContentView != null) {
            // There might be Views already added to the Window's content view so we need to
            // migrate them to our content view
            //如果mWindow的contentview已经添加过一些view,把第一个子view放到我们contentView里面
            while (windowContentView.getChildCount() > 0) {
                final View child = windowContentView.getChildAt(0);
                windowContentView.removeViewAt(0);
                contentView.addView(child);
            }

            // Change our content FrameLayout to use the android.R.id.content id.
            // Useful for fragments.
            //把mWindow中的mContentParent的id修改成View.NO_ID,原来的id是android:id/content
            windowContentView.setId(View.NO_ID);
            contentView.setId(android.R.id.content);
        }

        // Now set the Window's content view with the decor
        mWindow.setContentView(subDecor);//把subDecor设置成mWindow的内容画面
        ...
        return subDecor;
    }

解析:
1、首先配置window的属性样式(看2.1的代码片段);
2、然后调用了mWindow.getDecorView() ,即在window类中新建一个DecorView(FrameLayout),根据设置好的window属性样式inflate对应的layout布局生成一个View,添加成为DecorView的子View。
3、根据属性样式的标志加载对应的布局,设置为subDecor(ViewGroup)的布局;
4、执行mWindow.setContentView(subDecor)。

(2.1) requestWindowFeature(int featureId)

    @Override
    public boolean requestWindowFeature(int featureId) {
        ...
        switch (featureId) {
            case FEATURE_SUPPORT_ACTION_BAR:
                throwFeatureRequestIfSubDecorInstalled();
                mHasActionBar = true;// 改变属性的标记
                return true;
        ...
        }
        return mWindow.requestFeature(featureId);//设置window的属性
    }
  
    // 如果在setContentView之后再去设置requestWindowFeature,会抛出Exception。
    private void throwFeatureRequestIfSubDecorInstalled() {
        if (mSubDecorInstalled) {
            throw new AndroidRuntimeException(
                    "Window feature must be requested before adding content");
        }
    }

(2.2) 看看subDecor设置的layout布局

R.layout.abc_dialog_title_material(在include布局中包含了id为action_bar_activity_content的ContentFrameLayout)

<android.support.v7.widget.FitWindowsLinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_height="match_parent"
        android:layout_width="match_parent"
        android:orientation="vertical"
        android:fitsSystemWindows="true">
    <TextView
            android:id="@+id/title"
            style="?android:attr/windowTitleStyle"
            android:singleLine="true"
            android:ellipsize="end"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textAlignment="viewStart"
            android:paddingLeft="?attr/dialogPreferredPadding"
            android:paddingRight="?attr/dialogPreferredPadding"
            android:paddingTop="@dimen/abc_dialog_padding_top_material"/>
    <include
            layout="@layout/abc_screen_content_include"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"/>
</android.support.v7.widget.FitWindowsLinearLayout>

R.layout.abc_screen_simple(在include布局中包含了id为action_bar_activity_content的ContentFrameLayout)

<android.support.v7.widget.FitWindowsLinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/action_bar_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:fitsSystemWindows="true">

    <android.support.v7.widget.ViewStubCompat
        android:id="@+id/action_mode_bar_stub"
        android:inflatedId="@+id/action_mode_bar"
        android:layout="@layout/abc_action_mode_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <include layout="@layout/abc_screen_content_include" />

</android.support.v7.widget.FitWindowsLinearLayout>

3、mWindow.setContentView(View view)

    @Override
    public void setContentView(View view) {
        setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        // 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();//如果mWindow中的mContentParent 为null,执行installDecor()会初始化mContentParent
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            view.setLayoutParams(params);
            final Scene newScene = new Scene(mContentParent, view);
            transitionTo(newScene);
        } else {
            //把subDecor添加到了window中
            mContentParent.addView(view, params);
        }
        ...
    }

解析:mWindow.setContentView(subDecor)就是把mSubDecor添加到mWindow的mContentParent布局中。

4、再回到setContentView

    public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();//清空contentParent布局中的子控件
        LayoutInflater.from(mContext).inflate(resId, contentParent);//把我们设置的layout添加到contentParent中
        mOriginalWindowCallback.onContentChanged();//调用onContentChanged()的生命周期方法
    }

从mSubDecor中获取contentParent的布局,把我们设置的layout添加到contentParent中。

总结:
1、Activity中有一个window(PhoneWindow), 配置主题属性就是经过这个window;
2、Activity视图层级中的根布局是DecorView,这是个FrameLayout。
3、我们设置的布局到DecorView之间还存在三个或者四个布局。

Activity视图层级.png

结论验证

写个demo,布局页面如下

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>
preview.png

在来看一下视图层级


视图层级分析.JPG

相关文章

网友评论

    本文标题:Android视图相关之setContentView()源码分析

    本文链接:https://www.haomeiwen.com/subject/ubuowqtx.html