AppCompatActivity和Activity的setContentView方法的区别

前几天大致看了下Activity的setContentView方法,现在我们看一下AppCompatActivity的该方法

进入该方法和Activity的setContentView方法有明显的差别,通过名字可以猜测getDelegate()是一个代理方法,它的作用是 代理了一些做兼容的类,因为AppCompatActivity是V7包里的,而v7包就是做兼容的;

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

我们进入 getDelegate(),看看他是一个什么东西,这个方法返回一个AppCompatDelegate

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

我们进入AppCompatDelegate.create(this, this)方法看看它是干什么用的,进入该方法我们发现我们进入AppCompatDelegate是一个抽象类,他有很多实现类:AppCompatDelegateImplV23,AppCompatDelegateImplV14 等

private static AppCompatDelegate create(Context context, Window window,
            AppCompatCallback callback) {
        final int sdk = Build.VERSION.SDK_INT;
        if (sdk >= 23) {
            return new AppCompatDelegateImplV23(context, window, callback);
        } else if (sdk >= 14) {
            return new AppCompatDelegateImplV14(context, window, callback);
        } else if (sdk >= 11) {
            return new AppCompatDelegateImplV11(context, window, callback);
        } else {
            return new AppCompatDelegateImplV7(context, window, callback);
        }
    }

然后我们发现这个类有setContentView方法;如下

public abstract void setContentView(@LayoutRes int resId);

然后我们随便找一个它的实现类(比如AppCompatDelegateImplV7)进去,就可以找到它实现类的setContentView方法,它有三个重载方法;如下

@Override
    public void setContentView(View v) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        contentParent.addView(v);
        mOriginalWindowCallback.onContentChanged();
    }

    @Override
    public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mOriginalWindowCallback.onContentChanged();
    }

    @Override
    public void setContentView(View v, ViewGroup.LayoutParams lp) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        contentParent.addView(v, lp);
        mOriginalWindowCallback.onContentChanged();
    }

我们就看第一个就好,首先我们看到这个方法ensureSubDecor(),既然叫subDecor,那我们猜想一下是不是和我们以前讲的Activity的DecorView有某种联系呢??

我们进入ensureSubDecor();继续探索 主要方法createSubDecor();

private void ensureSubDecor() {
        if (!mSubDecorInstalled) {
            mSubDecor = createSubDecor();
            // If a title was set before we installed the decor, propogate it now
            CharSequence title = getTitle();
            if (!TextUtils.isEmpty(title)) {
                onTitleChanged(title);
            }

            applyFixedSizeWindow();

            onSubDecorInstalled(mSubDecor);

            mSubDecorInstalled = true;

            // Invalidate if the panel menu hasn't been created before this.
            // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
            // being called in the middle of onCreate or similar.
            // A pending invalidation will typically be resolved before the posted message
            // would run normally in order to satisfy instance state restoration.
            PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
            if (!isDestroyed() && (st == null || st.menu == null)) {
                invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
            }
        }
    }

我们进入createSubDecor()方法,可以直观的看到 这个方法返回的是一个ViewGroup
,是不是和我们的 DecorView有点像,但是它找的都是一些AppCompat的属性,如:R.styleable.AppCompatTheme等等。做的都是兼容方面的工作。另外,当我们用了AppCompatActivity,但是在清单文件中没用AppCompatTheme相关的样式,就造成我们的xml文件报错。原因就是在createSubDecor() 的时候,加载的样式都AppCompatTheme相关的样式。相信大家或多或少都有这样的经历。

private ViewGroup createSubDecor() {
        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);

        if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
            a.recycle();
            throw new IllegalStateException(
                    "You need to use a Theme.AppCompat theme (or descendant) with this activity.");
        }
        //在PhoneWindow里是requestFeature而在这里是requestFeature
        if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
            requestWindowFeature(Window.FEATURE_NO_TITLE);
        } 
        。。。省略若干行
        // Now let's make sure that the Window has installed its decor by retrieving it
        //这里还是初始化Decor,其实还是调用了PhoneWindow的getDecorView()方法
        //所以这里的第一步其实也初始化DecorView,和Activiy加载DecorView一样
        mWindow.getDecorView();
        //之后是根据不同的情来加载subDecor
        final LayoutInflater inflater = LayoutInflater.from(mContext);
        ViewGroup subDecor = null;
        if (!mWindowNoTitle) {
            if (mIsFloating) {
                // If we're floating, inflate the dialog title decor
                subDecor = (ViewGroup) inflater.inflate(
                        R.layout.abc_dialog_title_material, null);

                // Floating windows can never have an action bar, reset the flags
                mHasActionBar = mOverlayActionBar = false;
            } else if (mHasActionBar) {
                /**
                 * This needs some explanation. As we can not use the android:theme attribute
                 * pre-L, we emulate it by manually creating a LayoutInflater using a
                 * ContextThemeWrapper pointing to actionBarTheme.
                 */
                TypedValue outValue = new TypedValue();
                mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true);

                Context themedContext;
                if (outValue.resourceId != 0) {
                    themedContext = new ContextThemeWrapper(mContext, outValue.resourceId);
                } else {
                    themedContext = mContext;
                }
                //加载subDecor的布局,xml为abc_screen_toolbar,大家可以点进去看一下,这里就不贴代码了,太多了,下面会分为好多情况来给subDecor加载不同的布局,我们下面分析一个
                // 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);

                mDecorContentParent = (DecorContentParent) subDecor
                        .findViewById(R.id.decor_content_parent);
                mDecorContentParent.setWindowCallback(getWindowCallback());

                /**
                 * Propagate features to DecorContentParent
                 */
                if (mOverlayActionBar) {
                    mDecorContentParent.initFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
                }
                if (mFeatureProgress) {
                    mDecorContentParent.initFeature(Window.FEATURE_PROGRESS);
                }
                if (mFeatureIndeterminateProgress) {
                    mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
                }
            }
        } 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);
            }

            if (Build.VERSION.SDK_INT >= 21) {
                // If we're running on L or above, we can rely on ViewCompat's
                // setOnApplyWindowInsetsListener
                ViewCompat.setOnApplyWindowInsetsListener(subDecor,
                        new OnApplyWindowInsetsListener() {
                            @Override
                            public WindowInsetsCompat onApplyWindowInsets(View v,
                                    WindowInsetsCompat insets) {
                                final int top = insets.getSystemWindowInsetTop();
                                final int newTop = updateStatusGuard(top);

                                if (top != newTop) {
                                    insets = insets.replaceSystemWindowInsets(
                                            insets.getSystemWindowInsetLeft(),
                                            newTop,
                                            insets.getSystemWindowInsetRight(),
                                            insets.getSystemWindowInsetBottom());
                                }

                                // Now apply the insets on our view
                                return ViewCompat.onApplyWindowInsets(v, insets);
                            }
                        });
            } else {
                // Else, we need to use our own FitWindowsViewGroup handling
                ((FitWindowsViewGroup) subDecor).setOnFitSystemWindowsListener(
                        new FitWindowsViewGroup.OnFitSystemWindowsListener() {
                            @Override
                            public void onFitSystemWindows(Rect insets) {
                                insets.top = updateStatusGuard(insets.top);
                            }
                        });
            }
        }

        if (subDecor == null) {
            throw new IllegalArgumentException(
                    "AppCompat does not support the current theme features: { "
                            + "windowActionBar: " + mHasActionBar
                            + ", windowActionBarOverlay: "+ mOverlayActionBar
                            + ", android:windowIsFloating: " + mIsFloating
                            + ", windowActionModeOverlay: " + mOverlayActionMode
                            + ", windowNoTitle: " + mWindowNoTitle
                            + " }");
        }

        if (mDecorContentParent == null) {
            mTitleView = (TextView) subDecor.findViewById(R.id.title);
        }

        // Make the decor optionally fit system windows, like the window's decor
        ViewUtils.makeOptionalFitsSystemWindows(subDecor);


        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
            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.
            windowContentView.setId(View.NO_ID);
            contentView.setId(android.R.id.content);

            // The decorContent may have a foreground drawable set (windowContentOverlay).
            // Remove this as we handle it ourselves
            if (windowContentView instanceof FrameLayout) {
                ((FrameLayout) windowContentView).setForeground(null);
            }
        }

        // Now set the Window's content view with the decor
        mWindow.setContentView(subDecor);

        contentView.setAttachListener(new ContentFrameLayout.OnAttachListener() {
            @Override
            public void onAttachedFromWindow() {}

            @Override
            public void onDetachedFromWindow() {
                dismissPopups();
            }
        });

        return subDecor;
    }

我们这里分析一个subDecor的一个布局文件的情况,我们来看一下subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);这个里面的abc_screen_simple代码如下

<?xml version="1.0" encoding="utf-8"?>
<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">
    //兼容的ViewStub
    <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的这个布局u其实就是一个framelayout,和DecorView的xml文件基本一致
    <include layout="@layout/abc_screen_content_include" />

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

接着createSubDecor() 方法往下讲
我们在这个方法里看到以下代码,发现 R.id.action_bar_activity_content这个Id就是
上面include标签里面framelayout的ID;再往下看它用mWindow获取了Activity里面DDecorView的id为android.R.id.content的framelayout;之后它利用 windowContentView.setId(View.NO_ID);把原来DecorView的id设置为没有id;然后用 contentView.setId(android.R.id.content)把AppCompatActivity的subDecor里面的framelayout的id设置为android.R.id.content。做到了兼容替换

 final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
                R.id.action_bar_activity_content);

        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
            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.
            windowContentView.setId(View.NO_ID);
            contentView.setId(android.R.id.content);

            // The decorContent may have a foreground drawable set (windowContentOverlay).
            // Remove this as we handle it ourselves
            if (windowContentView instanceof FrameLayout) {
                ((FrameLayout) windowContentView).setForeground(null);
            }
        }

        // Now set the Window's content view with the decor
        //把subDecor又添加到DecorView里了
        //下面的是Activity的DecorView添加布局,用的是decor.addView方法
        //View in = mLayoutInflater.inflate(layoutResource, null);
        //decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, //MATCH_PARENT));
        //这里用的是setContentView(subDecor)方法//这就相当于Activity的setCont//entView方法了,相当于把subDecor作为我们自定义的xml了,这样来兼容。
        mWindow.setContentView(subDecor);


        contentView.setAttachListener(new ContentFrameLayout.OnAttachListener() {
            @Override
            public void onAttachedFromWindow() {}

            @Override
            public void onDetachedFromWindow() {
                dismissPopups();
            }
        });

        return subDecor;

之后再回到setContentView,大家发现它是调用了ensureSubDecor()后用findViewById(android.R.id.content)找到这个ViewGroup,但其实这个时候ViewGroup已经做了替换,不再是Activity里DecorView的那个id为content的帧布局了。所以做到了兼容,但是也不影响你用Activity,但是AppCompatActivity在这里做了偷梁换柱。。

@Override
    public void setContentView(View v) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        contentParent.addView(v);
        mOriginalWindowCallback.onContentChanged();
    }

看图更易理解
这里写图片描述

注释:可以看到继承AppCompatActivity后,里面的ImageView,TextView等都变成兼容的控件了。谷歌悄悄的做了很多兼容的工作。
这里写图片描述
疑问:
mWindow.getDecorView();产生DecorView的时候,DecorView的xml是如何选择的?原来是根据主题来选择外层的view,但是这里的最外层的view选的是什么?
看代码发现:如果没设置样式的话会产生一个默认的布局R.layout.screen_simple

} else {
            // Embedded, so no decoration is needed.
            layoutResource = com.android.internal.R.layout.screen_simple;
            // System.out.println("Simple!");
        }

此外,
1,我们也可以根据AppCompatActivity的id为content的
布局做一些替换,来完成一些自定义控件。比如,在所有Activity都要弹出窗口dialog,我们就可以替换这个id,设置我们自定义的控件,那么这个AppCompatActivity的顶层布局就变成我们自定义的控件了,并且弹窗的同时,不影响弹窗后面的操作。2,SnakBar里面也是会找者id为content的ViewGroup,然后再把snakbar添加到这个ViewGroup里面;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值