Activity之setContentView源码阅读

来源:互联网 发布:婆婆定期揍儿媳 知乎 编辑:程序博客网 时间:2024/05/22 10:42

Activity之setContentView源码阅读

1 首先进入Activity中查看setContentView的代码

    public void setContentView(@LayoutRes int layoutResID) {        getWindow().setContentView(layoutResID);        initWindowDecorActionBar();    }

这里首先获取一个Window类,然后调用了Window的setContentView,查看Window源码发现它是一个抽象类,那我们肯定是要找到它的实现类。

我们在Activity的attach方法中找到的它的实现类PhoneWindow .

 final void attach(Context context, ActivityThread aThread,            Instrumentation instr, IBinder token, int ident,            Application application, Intent intent, ActivityInfo info,            CharSequence title, Activity parent, String id,            NonConfigurationInstances lastNonConfigurationInstances,            Configuration config, String referrer, IVoiceInteractor voiceInteractor,            Window window) {        attachBaseContext(context);        mFragments.attachHost(null /*parent*/);        //  这里new 了一个PhoneWindow的实现类,再设置一些参数        mWindow = new PhoneWindow(this, window);        mWindow.setWindowControllerCallback(this);        mWindow.setCallback(this);        mWindow.setOnWindowDismissedCallback(this);        mWindow.getLayoutInflater().setPrivateFactory(this);        }

在PhoneWindow源码中看看setContentView的实现

@Override    public void setContentView(int layoutResID) {        if (mContentParent == null) {            installDecor();        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {            mContentParent.removeAllViews();        }        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,                    getContext());            transitionTo(newScene);        } else {            mLayoutInflater.inflate(layoutResID, mContentParent);        }        mContentParent.requestApplyInsets();        final Callback cb = getCallback();        if (cb != null && !isDestroyed()) {            cb.onContentChanged();        }        mContentParentExplicitlySet = true;    }

可以看到,首先判断mContentParent是否为null,是则调用installDecor(),否则移除其内部所有的子Views,然后通过LayoutInflater.inflate将我们传入的layout放置到mContentParent中。从这里就能看出来mContentParent是个ViewGroup且包裹我们整个布局文件;而installDecor()就是去初始化我们这个mContentParent。

接下来,通过getCallBack拿到了一个CallBack对象,其实这个获取到的这个CallBack就是我们Activity自己,我们的Activity是实现了CallBack接口的。当我们的Activity不为空且没有被销毁的时候回掉了onContentChanged方法,而onContentChanged在Activity中是空实现。

PhoneWindow中的installDecor()方法实现

private void installDecor() {        mForceDecorInstall = false;        //  判断mDecor是否为空 如果为空就调用generateDecor(-1)方法生成一个mDecor        if (mDecor == null) {            mDecor = generateDecor(-1);            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);            mDecor.setIsRootNamespace(true);            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);            }        } else {            mDecor.setWindow(this);        }        if (mContentParent == null) {            mContentParent = generateLayout(mDecor);       // 代码省略 ... }

generateDecor(-1)方法实现:

protected DecorView generateDecor(int featureId) {        Context context;        if (mUseDecorContext) {            Context applicationContext = getContext().getApplicationContext();            if (applicationContext == null) {                context = getContext();            } else {                context = new DecorContext(applicationContext, getContext().getResources());                if (mTheme != -1) {                    context.setTheme(mTheme);                }            }        } else {            context = getContext();        }        // 最终 new了一个DecorView对象,为FrameLayout的子类。        //              this指向PhoneWindow类        return new DecorView(context, -1, this, getAttributes());    }

在DecorView的构造方法中也调用的 setWindow(window)方法。

接着installDecor()方法往下走,先判断 mContentParent是否为空,如果为空调用generateLayout(mDecor)方法返回一个mContentParent。

generateLayout(mDecor)方法实现:

    protected ViewGroup generateLayout(DecorView decor) {        // Apply data from current theme.        TypedArray a = getWindowStyle();        // 这就是为什么要在setContentView()代码前调用requestFeature(XXX属性);        //...Window_windowIsFloating,Window_windowNoTitle,Window_windowActionBar...         //  首先通过WindowStyle中设置的各种属性,对Window进行requestFeature或者setFlags          mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);        int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)                & (~getForcedWindowFlags());        if (mIsFloating) {            setLayout(WRAP_CONTENT, WRAP_CONTENT);            setFlags(0, flagsToUpdate);        } else {            setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);        }        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {            requestFeature(FEATURE_NO_TITLE);        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {            // Don't allow an action bar if there is no title.            requestFeature(FEATURE_ACTION_BAR);        }        // 代码省略...         // Inflate the window decor.        //重点        int layoutResource;        int features = getLocalFeatures();        // System.out.println("Features: 0x" + Integer.toHexString(features));        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {            layoutResource = R.layout.screen_swipe_dismiss;        } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {            if (mIsFloating) {                TypedValue res = new TypedValue();                getContext().getTheme().resolveAttribute(                        R.attr.dialogTitleIconsDecorLayout, res, true);                layoutResource = res.resourceId;            } else {                layoutResource = R.layout.screen_title_icons;            }            // XXX Remove this once action bar supports these features.            removeFeature(FEATURE_ACTION_BAR);            // System.out.println("Title Icons!");        } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0                && (features & (1 << FEATURE_ACTION_BAR)) == 0) {            // Special case for a window with only a progress bar (and title).            // XXX Need to have a no-title version of embedded windows.            layoutResource = R.layout.screen_progress;            // System.out.println("Progress!");        } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {            // Special case for a window with a custom title.            // If the window is floating, we need a dialog layout            if (mIsFloating) {                TypedValue res = new TypedValue();                getContext().getTheme().resolveAttribute(                        R.attr.dialogCustomTitleDecorLayout, res, true);                layoutResource = res.resourceId;            } else {                layoutResource = R.layout.screen_custom_title;            }            // XXX Remove this once action bar supports these features.            removeFeature(FEATURE_ACTION_BAR);        } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {            // If no other features and not embedded, only need a title.            // If the window is floating, we need a dialog layout            if (mIsFloating) {                TypedValue res = new TypedValue();                getContext().getTheme().resolveAttribute(                        R.attr.dialogTitleDecorLayout, res, true);                layoutResource = res.resourceId;            } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {                layoutResource = a.getResourceId(                        R.styleable.Window_windowActionBarFullscreenDecorLayout,                        R.layout.screen_action_bar);            } else {                layoutResource = R.layout.screen_title;            }            // System.out.println("Title!");        } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {            layoutResource = R.layout.screen_simple_overlay_action_mode;        } else {            // Embedded, so no decoration is needed.            layoutResource = R.layout.screen_simple;            // System.out.println("Simple!");        }        // 通过对features和mIsFloating的判断,为layoutResource进行赋值        mDecor.startChanging();        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);        if (contentParent == null) {            throw new RuntimeException("Window couldn't find content container view");        }        if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {            ProgressBar progress = getCircularProgressBar(false);            if (progress != null) {                progress.setIndeterminate(true);            }        }        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {            registerSwipeCallbacks();        }        // Remaining setup -- of background and title -- that only applies        // to top-level windows.        if (getContainer() == null) {            final Drawable background;            if (mBackgroundResource != 0) {                background = getContext().getDrawable(mBackgroundResource);            } else {                background = mBackgroundDrawable;            }            mDecor.setWindowBackground(background);            final Drawable frame;            if (mFrameResource != 0) {                frame = getContext().getDrawable(mFrameResource);            } else {                frame = null;            }            mDecor.setWindowFrame(frame);            mDecor.setElevation(mElevation);            mDecor.setClipToOutline(mClipToOutline);            if (mTitle != null) {                setTitle(mTitle);            }            if (mTitleColor == 0) {                mTitleColor = mTextColor;            }            setTitleColor(mTitleColor);        }        mDecor.finishChanging();        return contentParent;    }

通过对features和mIsFloating的判断,选择系统中的一个布局文件id为layoutResource进行赋值,然后调用 mDecor.onResourcesLoaded(mLayoutInflater, layoutResource)方法,

根据我们赋值后的layoutResource实例化为view,并将其添加至mDecor(FrameLayout)中,布局文件中都包含一个id为content的FrameLayout

 void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {         // 代码省略        final View root = inflater.inflate(layoutResource, null);     // 代码省略            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));             // 代码省略    }

在调用完 mDecor.onResourcesLoaded(mLayoutInflater, layoutResource)后接着就找到contentParent 并且返回

 ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

通过mDecor.findViewById传入R.id.content,返回mDecor(布局)中的id为content的View,一般为FrameLayout。

@Nullable    public View findViewById(@IdRes int id) {        return getDecorView().findViewById(id);    }

再看看setContentView源码;

上面分析了installDecor()中的主要实现,并且为mContentParent 赋值,当我们多次调用setContentView()时,会先判断 mContentParent是否为空,为空,则取生成一个,不为空,则先移除其中的所有子View,所以我们可以多次调用setContentView();

@Override    public void setContentView(int layoutResID) {        if (mContentParent == null) {            installDecor();        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {            mContentParent.removeAllViews();        }        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,                    getContext());            transitionTo(newScene);        } else {        //   将 我们自己的layoutResID 实例化为View并将其添加为mContentParent的子View             mLayoutInflater.inflate(layoutResID, mContentParent);        }        mContentParent.requestApplyInsets();        final Callback cb = getCallback();        if (cb != null && !isDestroyed()) {            cb.onContentChanged();        }        mContentParentExplicitlySet = true;    }

回顾

首先初始化mDecor,即DecorView为FrameLayout的子类。就是我们整个窗口的根视图了。

然后,根据theme中的属性值,选择合适的布局,通过infalter.inflater放入到我们的mDecor中。

在这些布局中,一般会包含ActionBar,Title,和一个id为content的FrameLayout。

最后,我们在Activity中设置的布局,会通过infalter.inflater压入到我们的id为content的FrameLayout中去。

最终的布局结构图

这里写图片描述

原创粉丝点击