Android开发: View - 布局和窗口

Activity —> Window —> DecorView

Activity设置显示内容的过程

将布局放入窗口界面(SDK23)

从MyActivity中使用的setContentView(…)方法开始,这个方法是父类AppCompatActivity的。

public class MyActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.a_view_progress);
    }
}

AppCompatActivity中setContentView(…)方法的实现

public class AppCompatActivity extends FragmentActivity implements ... {
    @Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }

    ....

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

进一步是由抽象类AppCompatDelegate对象调用setContentView(…)方法,现在要去找到这个方法是在那里实现的。看getDelegate方法的实现,进入看AppCompatDelegate.create(…)方法。


public abstract class AppCompatDelegate {

    public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
        return create(activity, activity.getWindow(), callback);
    }

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

所以找到AppCompatDelegateImplV7类中实现的setContentView(…)方法。

class AppCompatDelegateImplV7 extends AppCompatDelegateImplBase
        implements MenuBuilder.Callback, LayoutInflaterFactory {
    @Override
    public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        //以后可以在onContentChanged()方法里面去findViewById了。
        mOriginalWindowCallback.onContentChanged();
    }
}

这个方法实现说明,我们调用Activity中的setContentView(…)中传入的View会被添加到ViewGroup对象contentParent 中,不难看出contentParent 是同为ViewGroup对象的mSubDecor的子控件。所以我们传入的View最后是添加到了mSubDecor中。

class AppCompatDelegateImplV7 extends AppCompatDelegateImplBase
        implements MenuBuilder.Callback, LayoutInflaterFactory {
   private void ensureSubDecor() {
        if (!mSubDecorInstalled) {
            //初始化
            mSubDecor = createSubDecor();

            // 设置标题的
            CharSequence title = getTitle();
            if (!TextUtils.isEmpty(title)) {
                onTitleChanged(title);
            }

            //窗口大小设置
            applyFixedSizeWindow();

            //AppCompatDelegateImplV7 派生类可覆写的方法
            onSubDecorInstalled(mSubDecor);

            //是否初始化的标志位
            mSubDecorInstalled = true;

            PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
            if (!isDestroyed() && (st == null || st.menu == null)) {
                invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
            }
        }
    }
}

看看此对象的初始化过程ensureSubDecor()方法。

    private ViewGroup createSubDecor() {
        ...
        ViewGroup subDecor = null;
        ...
        mWindow.setContentView(subDecor);
        ...
        return subDecor;        
    }

主要是对界面、标题和内容的设置,最后是由抽象类Window对象调用setContentView(…)进行内容设置,具体实现稍后再看,我们先看applyFixedSizeWindow()对窗口大小的设置。

    private void applyFixedSizeWindow() {
        ContentFrameLayout cfl = (ContentFrameLayout) mSubDecor.findViewById(android.R.id.content);

        final View windowDecor = mWindow.getDecorView();
        cfl.setDecorPadding(windowDecor.getPaddingLeft(),windowDecor.getPaddingTop(),
        windowDecor.getPaddingRight(),windowDecor.getPaddingBottom());

        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
        a.getValue(R.styleable.AppCompatTheme_windowMinWidthMajor, cfl.getMinWidthMajor());
        a.getValue(R.styleable.AppCompatTheme_windowMinWidthMinor, cfl.getMinWidthMinor());

        if (a.hasValue(R.styleable.AppCompatTheme_windowFixedWidthMajor)) {
            a.getValue(R.styleable.AppCompatTheme_windowFixedWidthMajor,
                    cfl.getFixedWidthMajor());
        }
        if (a.hasValue(R.styleable.AppCompatTheme_windowFixedWidthMinor)) {
            a.getValue(R.styleable.AppCompatTheme_windowFixedWidthMinor,
                    cfl.getFixedWidthMinor());
        }
        if (a.hasValue(R.styleable.AppCompatTheme_windowFixedHeightMajor)) {
            a.getValue(R.styleable.AppCompatTheme_windowFixedHeightMajor,
                    cfl.getFixedHeightMajor());
        }
        if (a.hasValue(R.styleable.AppCompatTheme_windowFixedHeightMinor)) {
            a.getValue(R.styleable.AppCompatTheme_windowFixedHeightMinor,
                    cfl.getFixedHeightMinor());
        }
        a.recycle();

        cfl.requestLayout();
    } attach

对于mWindow对象,进行了 setContentView(…)getDecorView() 的操作。现在我们要查找这两个方法的具体实现,mWindow对象是在AppCompatDelegateImplV7构造方法里面复制的。联系上文AppCompatDelegate.create(…)方法,mWindow是由activity.getWindow()dialog.getWindow()方法获取,这里我们看Activity获取Window方式的。


public class Activity extends ContextThemeWrapper implements ... {

    final void attach( ... ) {
        ...
        mWindow = new PhoneWindow(this);
        ...
    }
}

所以这两方法的具体实现还得再PhoneWindow里面去查看。


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

    private void installDecor() {
        ...
        if (mDecor == null) {
            mDecor = generateDecor();
        }
        ...

        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
            ...
        }
    }

    protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
    }
}

在执行setContentView(…),首先对mContentParent 为空时进行初始化操作,不为空时清除所有的View,然后通过LayoutInflater.inflate将我们传入的layout放置到mContentParent中。当mContentParent为空进行初始化时,会先获会调用generateDecor()获取mDecor,在将获取的mDecor传入generateLayout()方法获取mContentParent,这两个方法在后面看实现。

不难看出,Activity的setContentView(…)设置可见视图时,此视图先被DecorView中 id = android.R.id.content 的ViewGroup收容(在AppCompatDelegateImplV7类setContentView(…)方法中可以看出)。DecorView又被PhoneWindow对象收容(在AppCompatDelegateImplV7类createSubDecor()方法中可以看出)。以看出Activity中关于界 面的绘制实际上全是交给Window对象来做的。绘制类图的话,可以看出Activity聚合了一个Window对象。

从窗口界面拿控件使用(SDK23)

在Activity中使用findViewById最终是调用Window里面的方法。

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

前面就知道了getDecorView()返回的是对象是mDecor,这个对象初始化的时间是不是将布局设置进入窗口,初始化mContentParent 的时候。我们看看generateDecor()generateLayout()方法的实现。

既然使用mDecor.findViewById(id)来获取我们在布局中写入的控件,那是不是在generateDecor()获取mDecor的时候将我们的布局放入这个对象中。

    protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
    }
    public DecorView(Context context, int featureId) {  
          super(context);  
          mFeatureId = featureId;  
    }  

可以看出generateDecor( )方法只是初始化了一个FrameLayout对象,并没有在其内部压入布局文件,既然mDecor拥有我们的布局控件,在初始化的时候没有加入,那肯定在其他的时候加入了。看下generateLayout(mDecor)方法实现,这方法名都带了Layout,估摸着就是他了。

    protected ViewGroup generateLayout(DecorView decor){

            //先获取WindowStyle中的各种属性,对Window进行requestFeature或者setFlags等设置
            TypedArray a=getWindowStyle();

            if(a.getBoolean(com.android.internal.R.styleable.Window_windowNoTitle,false)){
            requestFeature(FEATURE_NO_TITLE);
            }
            //...根据当前sdk的版本判断需不需要加入menukey
            WindowManager.LayoutParams params=getAttributes();
            //设置  params.softInputMode 软键盘的模式;
            //当前是浮动Activity,在params中设置FLAG_DIM_BEHIND并记录dimAmount的值。
            //在params.windowAnimations 中记录 WindowAnimationStyle

            /**
             * 判断features和mIsFloating,为layoutResource进行赋值操作(R.layout.screen_custom_title、R.layout.screen_action_bar等等)
             * features,除了theme中设置,我们也可以在Activity的onCreate的setContentView之前进行requestFeature,设置
             * 这说明了,为什么需要在setContentView前调用requestFeature设置全屏什么的。得到了layoutResource以后
             */
            int layoutResource;
            int features=getLocalFeatures();
            // System.out.println("Features: 0x" + Integer.toHexString(features));
            if((features&((1<<FEATURE_LEFT_ICON)|(1<<FEATURE_RIGHT_ICON)))!=0){
            if(mIsFloating){
            TypedValue res=new TypedValue();
            getContext().getTheme().resolveAttribute(
            com.android.internal.R.attr.dialogTitleIconsDecorLayout,res,true);
            layoutResource=res.resourceId;
            }else{
            layoutResource=com.android.internal.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=com.android.internal.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(
            com.android.internal.R.attr.dialogCustomTitleDecorLayout,res,true);
            layoutResource=res.resourceId;
            }else{
            layoutResource=com.android.internal.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(
            com.android.internal.R.attr.dialogTitleDecorLayout,res,true);
            layoutResource=res.resourceId;
            }else if((features&(1<<FEATURE_ACTION_BAR))!=0){
            layoutResource=com.android.internal.R.layout.screen_action_bar;
            }else{
            layoutResource=com.android.internal.R.layout.screen_title;
            }
            // System.out.println("Title!");
            }else if((features&(1<<FEATURE_ACTION_MODE_OVERLAY))!=0){
            layoutResource=com.android.internal.R.layout.screen_simple_overlay_action_mode;
            }else{
            // Embedded, so no decoration is needed.
            layoutResource=com.android.internal.R.layout.screen_simple;
            // System.out.println("Simple!");
            }


            /**
             * 使用LayoutInflater把布局加载为View,并加入到decor中。
             */
            View in=mLayoutInflater.inflate(layoutResource,null);
            decor.addView(in,new ViewGroup.LayoutParams(MATCH_PARENT,MATCH_PARENT));

            /**
             * 将mDecor.findViewById放入R.id.content中
             * 返回mDecor(布局)中的id为content的View,一般为FrameLayout。
             */
            ViewGroup contentParent=(ViewGroup)findViewById(ID_ANDROID_CONTENT);
            //...

            return contentParent;
            }
    }

有了mContentParent,然后把我们写的布局文件通过inflater加入到mContentParent中。

总结分析

View显示的流程:

  1. Activity.setContentView() –> AppCompatDelegateImplV7.setContentView() –> 加入 SubDecorView (android.R.id.content)
  2. SubDecorView 初始化:AppCompatDelegateImplV7.createSubDecor() –> 建立 SubDecorViewmWindow.setContentView(subDecor)
  3. mWindow 对象:Activity.attach() 中赋值 PhoneWindow
  4. mWindow.setContentView(subDecor) 相当于 PhoneWindow.setContentView(subDecor) –> 初始化 mContentParent 并将 subDecor 视图 inflatemContentParent

Activity是Android程序的载体,没有Activity程序就没有可视界面,也就没有View了。这并不是说Activity就是一个View,它的功能更像是一个控制器,在Activity中我们对View进行控制操作。Activity中的setContentView()方法实际上是调用Window对象的setContentView()方法,所以绘图的操作是Window对象执行的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值