从setContentView谈谈android的布局层级

版权声明:本文为欢乐斗佛原创文章,未经其允许不得转载。 https://blog.csdn.net/qq379454816/article/details/53032001

因为android各版本的布局层级会有所差异,所以先告诉大家我测试的环境背景,如有在别的系统版本下面测试的结果有所出入请在下面留言支出,方便更多的读者可以从中获益,谢谢大家!

android studio:2.2.2

java版本:1.8

系统版本:ubuntu 

sdk版本:minSdkVersion 19, targetSdkVersion 25

手机版本:乐视6.01


前言

当我们新建一个应用的时候如果选择的是创建一个空的activity,那么AS默认会给我们重写onCreate()方法,并且在这个方法中为我们添加上setContenView方法,我们常规的做法是从这里进入对应的布局文件中删掉不必要的代码然后开始我们的布局代码编写。今天我们的讲解就从注释掉setContentView方法开始深入的讲解一下activity中布局的层级问题。

在这之前我先教大家如何使用AS查看应用的布局,当手机链接上AS之后,我们在打印logcat的地方也就是Android monitor标签这里会看到这样一个一行选项:



箭头所示的图标就是我们用来分析app布局的利器:layout inspector,今天我们的布局层级也是通过它来分析的。


一、不调用setContentView的情况下的布局层级


首先我们新见一个带有一个空白activity的app,取名LayoutHierarchy,下面的文章会简称为LH,建好应用之后我们注释掉setContentView这句代码,然后运行到手机上面,然后点击上面说的layoutInspector工具,过几秒之后AS会打开类似下面这样的界面:


这里我标出来了整个窗口大致分为这几个部分,今天主要用到的上面的4个并排中的后面三个,他们分别是:用来查看层级的窗口,用来查看运行效果的窗口,用来详细显示选中层级中的某个布局的详细参数状况。

将上面的3个窗口编号分别为:1、2、3,我们首先来看下窗口一中有那些东西:



别看到张开后这么复杂,其实我们折叠好之后之后PhoneWindow$DecorView这一个,然后我们再次展开一层,我们发现DecorView有2个子布局分别是LinearLayout和PhoneWindow$ImmersiveView,他们分别是我们的activity的根布局和状态栏的布局。继续往下展开,我们发现LinearLayout也有2个子布局,分别是ViewStub和FrameLayout,其中前面一个和actionbar有关,后面一个和我们的布局有关。再次展开FrameLayout,我们发现其只有一个子布局:ActionBarOverlayLayout,因为我们这个activity含有actionbar所以系统帮我们多套用了这层布局。接着展开,我们发现这个布局有2个子布局,分别为:ContentLayout和ActionBarContainer,前面一个是和我们SetContentView密切相关的一层布局,我们的SetContentView里面的布局就是添加在这层布局里面的,因为我们没有调用setContentView方法,所以这里没有子布局,后面的ActionBarContainer顾名思义就是和ActionBar相关的。我们展开ActionBarContainer发现其也有2个子布局:ToolBar和ActionBarContextView,因为我们的Activity不是直接继承的Activity而是继承了AppCompatActivity,所以这里的ActionBar其实是ToolBar,这也就是我们为什么使用兼容的类的时候在Activity中获取ActionBar不是直接调用的getActionBar()方法而是调用的getSupportActionBar()方法,之后我们教大家一种全中文圈没几个人使用的方式来获取我们的actionbar,这里的两个子布局ToolBar和ActionBarContextView,前面一个自然是我们的ActionBar,后面的一个呢就是在使用actionbar的风格为splite分离模式的时候使用的。刨根问底,最后我们再次展开ToolBar,我们发现其也是2个子布局:AppCompatTextView 和ActionMenuView,前面一个呢就是用来显示我们的标题的也就是大家看到的那个显示我们应用app的LayoutHierarchy的那个地方,后面的一个就是用来显示actionbar的别的图标的,比如home的返回按钮等。

到这里呢我就给大家逐层的分析了一遍布局的层级,大家是不是很惊讶,我们这是新建了一个activity没有添加我们自己的布局的情况下已经有这么多层的布局了,要是加上我们自己的布局,那一个页面是需要渲染多少次才可以绘制出来啊!而为了性能优化,我们必须要减少布局的层级,这样我们的app才能更快的渲染出来,才不会出现卡顿的现象。

另类的方式获取actionBar

上面我们分析的时候知道其实在这里actionbar使用的是toolbar,除了使用activity的getSupportACtionbar的方式获取到之外,我们可以直接使用FindviewById方法来获取actionbar,这里就不得不使用我们前面3个窗口中的第三个窗口了。在第一个窗口中我们选中ToolBar,然后第三个窗口会显示类似下面的这种情况:



这里列出的各个属性都是关于toolbar的,下面还有一些没有截取出来,大家如果有兴趣可以逐个的百度一下看看他们分别是用来控制toolbar的什么效果的,看见我在图中标出来的mId没有,没错,这个就是toolbar在系统的id值。

来到代码中,我们使用findviewbyId的方式来获取到toolbar并且将他设置成红色,修改后的代码如下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
        Toolbar toolbar = ((Toolbar) findViewById(R.id.action_bar));
        toolbar.setBackgroundColor(Color.RED);

    }
}

运行后的效果如下:



我们看到整个状态栏和标题栏都变成红色的了,如果只是需要标题栏是红色的,状态栏不变色该怎么办呢?如果我们是21以上的手机,只需要设置调用方法getWindow().setStatusBarColor()方法就可以,添加后的代码如下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
        Toolbar toolbar = ((Toolbar) findViewById(R.id.action_bar));
        toolbar.setBackgroundColor(Color.RED);
        getWindow().setStatusBarColor(Color.BLACK);
    }
}

好了,后了上面的代码基础之后,我们设置状态栏和actionbar的显示与隐藏,颜色值样式值就变得非常的容易了,这里就不带大家一般般的去实现了,有不懂的欢迎在下面留言。

因为网上流氓的网站太多了,经常有网站不经过我同样就转载我的文章,这里贴上我的博客地址,好让大家看见文章的时候知道作者是谁,我的博客地址:blog.csdn.net/qq379454816.

ok,下面就来教大家如何通过不设置setContentView就可以显示我们的布局,这里我使用上面带大家分析布局层级的时候告诉大家的一个方法,就是上面说的那个FrameLayout,我们使用findviewById找到它,然后添加上我们的布局文件,我们的布局文件就是一个款和高都是marchParent的一个imageView,修改后代码如下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
        Toolbar toolbar = ((Toolbar) findViewById(R.id.action_bar));
        toolbar.setBackgroundColor(Color.RED);
        getWindow().setStatusBarColor(Color.BLACK);

        FrameLayout framelayout = (FrameLayout) findViewById(android.R.id.content);
        View view = LayoutInflater.from(this).inflate(R.layout.activity_main, null);
        framelayout.addView(view, 0);

    }
}

运行上面的代码就可以成功的加载上我们的布局文件,效果图如下:


看到这里你有没有一点小激动呢?是不是对以前的很多细节都恍然大悟呢?原来google给我们设计android系统的时候考虑到安全因素之外还是给我们提供了很多的途径去实现新东西的,只是我们缺少发现的精神,网上的文章也是千遍一律,我们百度只是了解那些东西而不是用来照搬的,所以我们要多去实践。

上面使用findviewbyId()方法也可以用在activity里面是fragment的情况,我们直接可以把fragment放置到这个id为content的里面,这样可以减少一层布局。


全屏去掉actionbar的时候情况

下面我们来看看全屏没有actionbar的情况下的布局情况,我们在style文件中将主题设置为:android:style/Theme.Holo.NoActionBar,然后设置属性: <item name="android:windowFullscreen">true</item>,在activity中将前面获取toolbar的代码注释掉,如下:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
//        Toolbar toolbar = ((Toolbar) findViewById(R.id.action_bar));
//        toolbar.setBackgroundColor(Color.RED);
//        getWindow().setStatusBarColor(Color.BLACK);
//
        FrameLayout framelayout = (FrameLayout) findViewById(android.R.id.content);
        View view = LayoutInflater.from(this).inflate(R.layout.activity_main, null);
        framelayout.addView(view, 0);



    }

运行后我们看下效果:


屏幕上面就一张图片,我们打开layoutInspector来看看这个情况下的布局:


可以看到就算是这样的情况也会有3层布局,这里我们先不讨论google为什么这样设计,文章最后总结的时候我会告诉大家为什么要这样设计。


二、使用setcontentView


我们先将代码还原到创建app的初始状态,如下所示:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
////        Toolbar toolbar = ((Toolbar) findViewById(R.id.action_bar));
////        toolbar.setBackgroundColor(Color.RED);
////        getWindow().setStatusBarColor(Color.BLACK);
////
//        FrameLayout framelayout = (FrameLayout) findViewById(android.R.id.content);
//        View view = LayoutInflater.from(this).inflate(R.layout.activity_main, null);
//        framelayout.addView(view, 0);



    }
}
看下运行效果:




LayoutInspector中的情况:



这种情况和我们使用findview替换的方式其实是一样多的布局,为什么会这样呢?我们查看源码发现,其实底层的setcontentView 的代码也是find到content替换的:

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

总结

今天给大家介绍这篇文章的目的是要大家认识到布局是怎样一层一层的绘制到屏幕上面的,只有我们清楚它的绘制流程我们才可以在使用的过程中更加灵活的应对出现问题的时候也可以做到更加的从容。如果在使用fragment的情况下我们就可以直接用content来作为fragment的容器而不需要再次设置一个容器,当然这只能适合activity只有一个fragment的情况。那么google为什么在一个简单的activity上面设置那么多层次的布局呢?岂不是很浪费性能?其实google这样设计也是有原因的,我们的视图不只是用来展现一个画面给用户,我们更过的是让手机与用户去交互,交互的话就避免不了处理触摸和点击事件,那么如何一层层的给点击事件处理好呢?你当然想到了是否分发和拦截的情况,没错,google这样设计就是为了在系统层可以更好的控制点击事件的分发。那么这样就可以只添加一个LinearLayout就可以了,为什么下面还添加一个FrameLayout?也许他这样设计还处于考虑别的原因,我暂时不知道为什么这样就设计,如果有知道的同学欢迎给我留言,感谢大家能看到这里,码字不容易,如果你觉得这篇文章对你有些许的帮助,请给个赞,谢谢大家!


扫描关注我的微信公众号:



阅读更多
换一批

没有更多推荐了,返回首页