(M)Activity详解之setContentView

本文探讨了Android Activity中setContentView方法的工作原理,揭示了它如何设置Activity的布局。通过分析PhoneWindow的generateLayout和installDecor方法,发现布局实际上是添加到mContentParent,一个FrameLayout实例上,而mContentParent是嵌套在DecorView中的。DecorView提供了默认的布局结构,确保视图正确显示。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在写一个Activity的时候,经常会先调用setContentView方法来设置Activity的布局,那么setContentView中究竟做了什么呢?

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}
Activity的getWindow()方法在之前,Activity的初始化的时候,就已经了解了,其是在Activity的attach方法中初始化的

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) {
    ......
    // 初始化PhoneWindow对象
    mWindow = new PhoneWindow(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
        mWindow.setSoftInputMode(info.softInputMode);
    }
    if (info.uiOptions != 0) {
        mWindow.setUiOptions(info.uiOptions);
    }
    ......
}
getWindow()方法中取得的mWindow就是PhoneWindow对象,那么Activity的setContentView首先调用了PhoneWindow中的setContentView方法

@Override
public void setContentView(int layoutResID) {
    // 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();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }
    ......
}
PhoneWindow的installDecor方法

private void installDecor() {
    if (mDecor == null) {
        // 第一次启动一个Activity的时候,新建mDecor对象
        mDecor = generateDecor();
        ......
    }
    if (mContentParent == null) {
        mContentParent = generateLayout(mDecor);
        ......
    }
}
protected DecorView generateDecor() {
    // new一个DecorView对象
    return new DecorView(getContext(), -1);
}
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
    ......
}
初始化mDecor,其为DecorView对象,父类为FrameLayout

在看PhoneWindow的generateLayout方法,注意其传入的是DecorView对象

protected ViewGroup generateLayout(DecorView decor) {
    ......
    int layoutResource;
    int features = getLocalFeatures();
    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);
    } 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;
    } 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;
        }
    } 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;
    }
    mDecor.startChanging();
    View in = mLayoutInflater.inflate(layoutResource, null);
    // 将其加入刚刚创建的DecorView对象中
    decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    mContentRoot = (ViewGroup) in;
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    ......
    return contentParent;
}
从这个方法可以看到,generateLayout方法其实是确认Activity的布局文件,然后将其加入刚刚创建的DecorView中,而其返回值为DecorView中的一个id为com.android.internal.R.id.content的View,查看刚刚所有的layout文件,均有一个id为com.android.internal.R.id.content的FrameLayout,那么contentParent对象是做什么用的呢?

返回PhoneWindow的installDecor方法中,可以看到,这个方法中主要是初始化了mDecor和mContentParent对象,而mContentParent是FrameLayout,且是花在mDecor上的

再返回PhoneWindow的setContentView中,我们发现setContentView方法中传递了一个参数(View或者layoutid),那么是在什么地方用的呢?

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 {
        // 以mContentParent为父View,传入的layoutResID为layout布局
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    ......
}
从这儿,我们可以发现,我们传入的layoutResID所对应的布局或者View,就是画在刚刚定义的mContentParent上的

那么得出结论,我们传入的View或者布局,都是画在mContentParent上的,最终是画在DecorView上的,而DecorView为我们提供了一个既定的layout布局



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值