Android源码setContentView()之Material Design适配

前言:

上篇文章中已经对普通Activity的setContentView()是如何将xml文件解析成view加载到App内存中进行了分析,但是从Android 5.0后提出的新概念Material Design(需要科学上网)以及后面的各个版本的变动,就导致google需要对各个版本进行适配 包括这篇的要说的setContentView() 这篇主要分析google工程师是如何对5.0以上的setContentView()进行适配的

分析:

老规矩先来一张代码执行流程图


阅读源码:
//首先需要继承AppCompatActivity
public class MainActivity extends AppCompatActivity{
     @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}
public class AppCompatActivity extends FragmentActivity {
  //代理类AppCompatDelegate
  private AppCompatDelegate mDelegate;
  @Override
  public void setContentView(@LayoutRes int  layoutResID) {
        getDelegate().setContentView(layoutResID);
    }
  //根据不同的SDK获取对应的 AppCompatDelegate实现类
  @NonNull
  public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }  
}

所谓代理类 顾名思义代理执行 比如:

    @Override
    protected void onPostResume() {
        super.onPostResume();
        getDelegate().onPostResume();
    }

    @Override
    protected void onStart() {
        super.onStart();
        getDelegate().onStart();
    }

    @Override
    protected void onStop() {
        super.onStop();
        getDelegate().onStop();
    }

    @Override
    public View findViewById(@IdRes int id) {
        return getDelegate().findViewById(id);
    }

AppCompatDelegate代理实现类的创建:

public abstract class AppCompatDelegate {
    public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
        return create(activity, activity.getWindow(), callback);
    }
    //根据不同SDK 创建不同的AppCompatDelegate 实现类 (类似于简单工厂方法)
   private static AppCompatDelegate create(Context context, Window window,
            AppCompatCallback callback) {
        final int sdk = Build.VERSION.SDK_INT;
        if (BuildCompat.isAtLeastN()) {
            return new AppCompatDelegateImplN(context, window, callback);
        } else 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 AppCompatDelegateImplV9(context, window, callback);
        }
    }
}

AppCompatActivity的代理类AppCompatDelegate 是由SDK版本来决定的 不过各个版本有一定的继承关系 如图:


不过setContentView(layoutResID)是直接在AppCompatDelegateImplV9中实现的 其他版本也没重写 所以直接进入AppCompatDelegateImplV9

class AppCompatDelegateImplV9{
    @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();
    }
}

有一个 ensureSubDecor()函数 :

private ViewGroup mSubDecor;

private void ensureSubDecor() {
        if (!mSubDecorInstalled) {
          //获取根部布局视图
            mSubDecor = createSubDecor();
            // If a title was set before we installed the decor, propagate it now
            CharSequence title = getTitle();
            if (!TextUtils.isEmpty(title)) {
                onTitleChanged(title);
            }
     }

接着进入createSubDecor()函数:

private ViewGroup createSubDecor() {
        //获取Activity主题
        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
        //如果使用的主题不是AppCompatTheme的子类就会抛异常 这也解释了 为什么在使用AppCompatActivity的时候 必须使用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.");
        }
        
        if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
 //设置主题样式Window.FEATURE_NO_TITLE           requestWindowFeature(Window.FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) {
         //设置主题样式 WindowCompat.FEATURE_ACTION_BAR 
  requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);
        }
        if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) {
         //设置主题样式  WindowCompat.FEATURE_ACTION_BAR_OVERLAY   requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
        }
        if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) {
            //设置主题样式WindowCompat.FEATURE_ACTION_MODE_OVERLAYrequestWindowFeature(FEATURE_ACTION_MODE_OVERLAY);
        }
        mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false);
        a.recycle();

        //这句代码很重要 执行了PhoneWindow中的installDecor()函数 这个函数初始化了DecorView 而且还将根布局解析 并且添加到了DecorView中 比如常用的`R.layout.screen_simple`
        mWindow.getDecorView();

        //创建LayoutInflater实例
        final LayoutInflater inflater = LayoutInflater.from(mContext);
        ViewGroup subDecor = null;

        //根据不同的主题样式 为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);

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

                // 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 (mDecorContentParent == null) {
            mTitleView = (TextView) subDecor.findViewById(R.id.title);
        }
      //反射调用View的makeOptionalFitsSystemWindows()函数 
      ViewUtils.makeOptionalFitsSystemWindows(subDecor);

          /*...............关键代码从这里看.......................................*/

        //根据R.id.action_bar_activity_content获取到布局中的ContentFrameLayout View
        final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(R.id.action_bar_activity_content);

        //获取mWindow(PhoneView对象 在attach()时创建的)DecorView中的 android.R.id.content 也就是Activity默认提供的根布局视图中的FrameLayout
        final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);

        //因为上面执行了 mWindow.getDecorView()这句代码 此时 windowContentView肯定不为空
        if (windowContentView != null) {
             //可能已经在窗口的内容视图中添加了视图,所以我们需要
            //将它们迁移到我们的内容视图
            while (windowContentView.getChildCount() > 0) {  
                //将第0个内容视图删除           
                final View child = windowContentView.getChildAt(0);
                windowContentView.removeViewAt(0);
                //将删除的视图添加AppCompatActivity 提供的根布局视图contentView中
                contentView.addView(child);
            }
            //这里将的windowContentView的id设置为-1 就是将R.layout.screen_simple中的FrameLayout id设置为-1
            //然后将contentView的id设置为android.R.id.content. 偷梁换柱
            windowContentView.setId(View.NO_ID);
            contentView.setId(android.R.id.content);

            //去掉windowContentView的前景 由我们自己处理
            if (windowContentView instanceof FrameLayout) {
                ((FrameLayout) windowContentView).setForeground(null);
            }
        }
        // 调用PhoneWindow的setContentView(View view)
        mWindow.setContentView(subDecor);

        //将AppCompatActivity 提供的根视图返回
        return subDecor;
    }

google 将根布局中的View为了达到Material Design效果都做了兼容性处理 R.layout.abc_screen_simple

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>

//abc_screen_content_include

<merge xmlns:android="http://schemas.android.com/apk/res/android">

    <android.support.v7.widget.ContentFrameLayout
            android:id="@id/action_bar_activity_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:foregroundGravity="fill_horizontal|top"
            android:foreground="?android:attr/windowContentOverlay" />

</merge>

到这里再继续进入PhoneWindow的setContentView(View view)继续分析

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

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        //因为在AppCompatDelegateImplV9已经调用过installDecor()函数 所以mContentParent不会为null
        if (mContentParent == null) {
            installDecor();
        } 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 {
            //这里几直接将AppCompatDelegateImplV9 中获取的根视图添加到mContentParent中 上文已经分析过这个mContentParent 是一个FrameLayout
            mContentParent.addView(view, params);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }
}    

到这里视图已经添加到DecorView中 在回去看AppCompatDelegateImplV9 sheContentView()

@Override
public void setContentView(int resId) {    
        //这个函数已经分析过
        ensureSubDecor();
        //这里获取android.R.id.content 是经过id替换的FrameLayout 也就是AppCompatActivity提供的根视图中的FrameLayout
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        //删除所有子控件
        contentParent.removeAllViews();
        //将内容视图添加到contentParent(FrameLayout)中
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        //窗口改变完成 回调监听
        mOriginalWindowCallback.onContentChanged();   
}

说了那么说 不如来的一张图爽快:


在Activity提供的默认根视图中 google工程师又向FrameLayout中添加AppCompatActivity提供做了兼容处理的根视图并且把id给替换了 同时可以看到内容视图中的TextView 已经被替换为可兼容的AppCompatTextView

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

推荐阅读更多精彩内容