Android-六大布局详解

布局概述

为了让组件(TextView,Button等)在不同的手机屏幕上都能运行良好(不同手机屏幕分辨率、尺寸并不完全相同),如果让程序手动控制每个组件的大小、位置,则将给编程带来巨大的困难,为了解决这个问题,Android提供了布局管理器。布局管理器可以根据运行平台来调整组件的大小,咱们程序员要做的只是为容器选择合适的布局管理器。

每当Acitivity.setContentView(@LayoutRes int layoutResID)方法被调用,或者一个View通过LayoutInflater对象inflater出来,那么相关的布局文件就会被加载并解析出来。XML文件中每个大写的XML节点对应着一个View对象,他们被系统实例化。在Acitviity或者Fragment的整个生命周期中,他们都是UI层级的一部分。这会影响到应用程序使用过程中的分配。

Android的布局管理器本身就是个UI组件,所有的布局管理器都是ViewGroup的子类,而ViewGroup是View的子类,所以布局管理器可以当成普通的UI组件使用,也可以作为容器类使用,可以调用多个重载addView()向布局管理器中添加组件,并且布局管理器可以互相嵌套,当然不推荐过多的嵌套(如果要兼容低端机型,最好不要超过5层)。

布局层级管理

让咱们一起了解一下每当系统绘制一个布局时,都会发生一些什么。这一过程由两个步骤完成:

1、绘制(Measurement)

a、根布局测量自身。

b、根布局要求它内部所有子组件测量自身。

c、所有自布局都需要让它们内部的子组件完成这样的操作,直到遍历完视图层级中所有的View。

2、摆放( Positioning)

a、当布局中所有的View都完成了测量,根布局则开始将它们摆放到合适的位置。b、所有子布局都需要做相同的事情,直到遍历完视图层级中所有的View。

b、当某个View的属性发生变化(如:TextView内容变化或ImageView图像发生变化),View自身会调用View.invalidate()方法(必须从 UI 线程调用),自底向上传播该请求,直到根布局(根布局会计算出需要重绘的区域,进而对整个布局层级中需要重绘的部分进行重绘)。布局层级越复杂,UI加载的速度就越慢。因此,在编写布局的时候,尽可能地扁平化是非常重要的。AbsoluteLayout已被弃用,咱就不多说它了。FrameLayout和TableLayout有各自的特殊用途,LinearLayout 和RelativeLayout是可以互换的,ConstraintLayout和RelativeLayout类似。也就是说,在编写布局时,可以选择其中一种,咱们可以以不同的方式来编写下面这个简单的布局。

LinearLayout

第一种方式是使用LinearLayout,虽然可读性比较强,但是性能比较差。由于嵌套LinearLayout会加深视图层级,每次摆放子组件时,相对需要消耗更多的计算。

<?xml version="1.0" encoding="utf-8"?>

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

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:orientation="vertical">

    <View

        android:id="@+id/view_top_1"

        android:layout_width="match_parent"

        android:layout_height="100dp"

        android:background="@color/color_666666"/>

    <View

        android:id="@+id/view_top_2"

        android:layout_width="200dp"

        android:layout_height="100dp"

        android:background="@color/teal_200"/>

    <LinearLayout

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:orientation="horizontal">

        <View

            android:id="@+id/view_top_3"

            android:layout_width="100dp"

            android:layout_height="100dp"

            android:background="@color/color_FF773D"/>

        <View

            android:id="@+id/view_top_4"

            android:layout_width="100dp"

            android:layout_height="100dp"

            android:background="@color/purple_500"/>

    </LinearLayout>

</LinearLayout>

LinearLayout视图层级如下所示:

LinearLayout视图层级

第二种方法基于RelativeLayout,在这种情况下,你不需要嵌套其他ViewGroup,因为每个子View可以相当于其他View,或相对与父控件进行摆放。

<?xml version="1.0" encoding="utf-8"?>

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

    android:layout_width="match_parent"

    android:layout_height="match_parent">

    <View

        android:id="@+id/view_top_1"

        android:layout_width="match_parent"

        android:layout_height="100dp"

        android:background="@color/color_666666"/>

    <View

        android:id="@+id/view_top_2"

        android:layout_width="200dp"

        android:layout_below="@id/view_top_1"

        android:layout_height="100dp"

        android:background="@color/teal_200"/>

    <View

        android:id="@+id/view_top_3"

        android:layout_width="100dp"

        android:layout_below="@id/view_top_2"

        android:layout_height="100dp"

        android:background="@color/color_FF773D"/>

    <View

        android:id="@+id/view_top_4"

        android:layout_width="100dp"

        android:layout_below="@id/view_top_2"

        android:layout_toRightOf="@id/view_top_3"

        android:layout_height="100dp"

        android:background="@color/purple_500"/>

</RelativeLayout>

RelativeLayout视图层级如下所示:

RelativeLayout视图层级

通过两种方式,可以很容易看出,第一种方式LinearLayout需要3个视图层级和6个View,第二种方式RelativeLayout仅需要2个视图层级和5个View。

当然,虽然RelativeLayout效率更高,但不是所有情况都能通过相对布局的方式来完成控件摆放。所以通常情况下,这两种方式需要配合使用。

注意:为了保证应用程序的性能,在创建布局时,需要尽量避免重绘,布局层级应尽可能地扁平化,这样当View被重绘时,可以减少系统花费的时间。在条件允许的情况下,尽量的使用RelativeLayout和ConstraintLayout,而非LinearLayout,或者用GridLayoutl来替换LinearLayout。

开发者最常使用的是ViewGroup是LinearLayout,只是因为它很容易看懂,编写起来简单,所以它就成了Android开发新手的首选。出于这个原因,Google推出了一个全新的ViewGroup。在适当的时候时候使用它,可以减少冗余。它就是网格布局GridLayout下面文章中也有讲到。

布局复用

Android SDK提供了一个非常有用的标签。在某些情况下,当你希望在其他布局中用一些已存在的布局时,<include/>标签可通过制定相关引用ID,将一个布局添加到另一个布局。比如自定义一个标题栏,那么可以按照下面的方式,创建一个可重复用的布局文件。

<?xml version="1.0" encoding="utf-8"?>

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

    android:layout_width="match_parent"

    android:layout_height="wrap_content">

    <View

        android:id="@+id/view_top_1"

        android:layout_width="match_parent"

        android:layout_height="100dp"

        android:background="@color/color_666666"/>

</RelativeLayout>

接着,将<include/>标签放入相应的布局文件中,替换掉对应的View:

<?xml version="1.0" encoding="utf-8"?>

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

    android:layout_width="match_parent"

    android:layout_height="match_parent">

    <include layout="@layout/include_layout"/>

    <View

        android:id="@+id/view_top_2"

        android:layout_width="200dp"

        android:layout_height="100dp"

        android:background="@color/teal_200"/>

    <View

        android:id="@+id/view_top_3"

        android:layout_width="100dp"

        android:layout_height="100dp"

        android:background="@color/color_FF773D"/>

</RelativeLayout>

这么一来,当你希望重用某些View时,就不用复制/粘贴的方式来实现,只需要定义一个layout文件,然后通过<include/>引用即可。但是这样做,可能会引入一个冗余的ViewGroup(重用的布局文件的根视图)。

为此,Android SDK提供了另一个标签,用来帮我们减少布局冗余,让层级变得更加扁平化。我们只需要将可重用的根视图,替换为<merge/>标签即可。如下所示:

<?xml version="1.0" encoding="utf-8"?>

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

    android:layout_width="match_parent"

    android:layout_height="wrap_content">

    <View

        android:id="@+id/view_top_1"

        android:layout_width="match_parent"

        android:layout_height="100dp"

        android:background="@color/color_666666"/>

</merge>

如此一来,就没有了冗余的视图控件,因为系统会忽略<merge/>标签,并将<merge/>标签中的视图直接放置在相应的布局文件中,替换<include/>标签。

使用此标签时,需要记住它的两个主要限制。

a、它只能作为布局文件的跟来使用。

b、每次调用LayoutInflater.inflate()时,必须为<merge/>布局文件提供一个View,作为它的父容器:

LayoutInflater.from(this).inflate(R.layout.merge_layout,parent,true);

一、线性布局(LinearLayout常用)

LinearLayout是一个视图组,呈线性横向或纵向,依次绘制每个被添加进来的子组件。

LinearLayout的所有子组件依次堆叠,因此无论子视图有多宽,垂直列表每行均只有一个子组件,水平列表将只有一行高(最高子组件的高度加上内边距)。LinearLayout会考虑子组件之间的边距以及每个子组件的对齐方式(右对齐、居中对齐或左对齐)。

布局权重

LinearLayout还支持使用android:layout_weight属性为各个子组件分配权重。此属性会根据视图应在屏幕上占据的空间大小,向视图分配“重要性”值。如果拥有更大的权重值,视图便可展开,填充父视图中的任何剩余空间。子组件可指定权重值,然后系统会按照子组件所声明的权重值比例,为其分配视图组中的任何剩余空间。默认权重为零。

均等分布

如要创建线性布局,让每个子组件使用大小相同的屏幕空间,请将每个视图的android:layout_height设置为"0dp"(针对垂直布局),或将每个视图的android:layout_width设置为"0dp"(针对水平布局)。然后,请将每个视图的android:layout_weight设置为"1"。例如:

均等分布

不等分布

你也可创建线性布局,让子元素使用大小不同的屏幕空间:

·a、如果有三个文本字段,其中两个声明权重为 1,另一个未赋予权重,那么没有权重的第三个文本字段就不会展开,而仅占据其内容所需的区域。另一方面,另外两个文本字段将以同等幅度展开,填充测量三个字段后仍剩余的空间。

·b、如果有三个文本字段,其中两个字段声明权重为 1,而为第三个字段赋予权重 2(而非 0),那么现在相当于声明第三个字段比另外两个字段更为重要,因此,该字段将获得总剩余空间的一半,而其他两个字段均享余下的空间。

不等分布

二、相对布局(RelativeLayout常用)

RelativeLayout是一个视图组,每个子组件位置是相对的,可以相对于同一层级下其他控件,也可以相对于父控件。

RelativeLayout是用于设计用户界面的非常强大的实用程序,因为它可以消除嵌套视图组并保持布局层次结构平坦,从而提高性能。如果你发现自己使用了多个嵌套LinearLayout组,则可以将它们替换为单个RelativeLayout。

定位视图

RelativeLayout让子视图指定它们相对于父视图或彼此的位置(由 ID 指定)。因此,如果A组件的位置是由B组件的位置来决定,Android要求先定义B组件,再定义A组件

RelativeLayout.LayoutParams

为了控制RelativeLayout布局容器中各子组件的布局分布,RelativeLayout提供了一个内部类:RelativeLayout.LayoutParams,该类提供了大量的XML属性来控制RelativeLayout布局容器中子组件的布局分布。

不设置相对位置(重叠在一起)

不设置相对位置

设置相对位置,未出现重叠

设置相对位置

三、网格布局(GridLayout)

GridLayout把这个容器划分成rows×columns个网格,每个网格可以放一个组件。 除此之外,也可以设置一个组件横跨多少列、一个组件纵跨多少行(支持跨行和跨列以及每个单元格组内的任意对齐形式)。

GridLayout提供了setColumnCount(int)和setRowCount(int)方法来控制该网络的列数和行数。

GridLayout.LayoutParams

为了控制GridLayout布局容器中各子组件的布局分布,GridLayout提供了一个内部类:GridLayout.LayoutParams,该类提供了大量的XML属性来控制GridLayout布局容器中子组件的布局分布。

这样的布局用LinearLayout也能做,但是相对麻烦一点,所以在适当的时候时候使用GridLayout就非常的有必要了。而且你可能注意到了,子View中并没有指定android:layout_width和android:layout_height属性。这是因为这两个属性的默认值都是LayoutPrams.WRAP_COUNTENT,而在此,我们希望使用的就是LayoutPrams.WRAP_COUNTENT,所以就没必要指定了。GridLayout和LinaerLayout十分相似,所以将LinaerLayout替换为GridLayout也相当简单。

四、表格布局(TableLayout)

TableLayout继承了LinerarLayout,因此它的本质依然是线性布局管理器。表格采用行、列的形式来管理UI组件,TableLayout并不需要明确地声明包含多少行、多少列,而是通过TableRow、其他组件来控制表格的行数和列数。

每次向TableLayout中添加TableRow,该TableRow就是一个表格行,TableRow也是容器,因此它也可以不断的添加其他组件,每添加一个子组件该表格就增加一列。

如果直接向TableLayout添加组件,那么这个组件将直接占一行。

在TableLayout中、列的宽度由该列最宽的那个单元格决定,整个TableLayout的宽度取决于父容器的宽度(默认占满父容器)

在TableLayout中,可以为单元格设置的三种行为方式:

Collapsed:如果某列被设为Collapsed,那么该列所有单元格都会被隐藏。

Shrinkable:如果某列被设为Shrinkable,那么该列所有单元格的宽度可以被收缩,以保证该变革能适应父容器的宽度。

Stretchable:如果某列被设为Stretchable,那么该列所有单元格的宽度可以被拉伸,以保证组件能完全填充满表格空余空间。

TableLayout继承了LinerarLayout,因此它完全可以支持LinerarLayout所支持的XML属性。初次之外还支持下面的XML属性。

TableLayout的常用XML属性和相关方法说明

XML属性相关方法说明

android:collapseColumn

ssetColumnCollapsed(int,boolean)要折叠的列的从零开始的索引。 

android:shrinkColumns

setShrinkAllColumns(boolean)要收缩的列的从零开始的索引。 

android:stretchColumns

setStretchAllColumns(boolean)要拉伸的列的从零开始的索引。 

TableLayout

<?xml version="1.0" encoding="utf-8"?>

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

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:layout_margin="10dp"

    android:orientation="vertical">

    <TableLayout

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:shrinkColumns="1"

        android:stretchColumns="2">

        <Button

            android:layout_width="match_parent"

            android:layout_height="wrap_content"

            android:text="顶层大佬" />

        <TableRow>

            <Button

                android:layout_width="match_parent"

                android:layout_height="wrap_content"

                android:text="不变按钮" />

            <Button

                android:layout_width="match_parent"

                android:layout_height="wrap_content"

                android:layout_marginLeft="10dp"

                android:text="收缩按钮" />

            <Button

                android:layout_width="match_parent"

                android:layout_height="wrap_content"

                android:layout_marginLeft="10dp"

                android:text="拉伸按钮" />

        </TableRow>

    </TableLayout>

    <TableLayout

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:collapseColumns="0"

        android:stretchColumns="2">

        <Button

            android:layout_width="match_parent"

            android:layout_height="wrap_content"

            android:text="高层大佬" />

        <TableRow>

            <Button

                android:layout_width="match_parent"

                android:layout_height="wrap_content"

                android:text="隐藏按钮" />

            <Button

                android:layout_width="match_parent"

                android:layout_height="wrap_content"

                android:layout_marginLeft="10dp"

                android:text="不变按钮" />

            <Button

                android:layout_width="match_parent"

                android:layout_height="wrap_content"

                android:layout_marginLeft="10dp"

                android:text="拉伸按钮" />

        </TableRow>

    </TableLayout>

</LinearLayout>

五、帧布局(FrameLayout)

FrameLayout将控件以栈的形势堆叠起来,最近添加进去的控件绘制在最顶部。FrameLayout为每个加入其中的组件创建一个空白的区域(称为一帧),每个子组件占据一帧,这些帧都会根据gravity属性执行自动对齐。

FrameLayout包含的子元素也受到FrameLayout.LayoutParams的控制,因此它所包含的子元素也可以指定android:layout_gravity。

霓虹灯

六、约束布局(ConstraintLayout)

将该库作为依赖项添加到app/ build.gradle文件中,

dependencies {    implementation "androidx.constraintlayout:constraintlayout:2.0.4"    // To use constraintlayout in compose    implementation "androidx.constraintlayout:constraintlayout-compose:1.0.0-alpha06"}

ConstraintLayout允许您以灵活的方式定位和调整子组件的大小。 它与RelativeLayout类似,所有的视图都是根据兄弟视图和父布局之间的关系来布局的,但是它比RelativeLayout更灵活,并且更易于在Android Studio的布局编辑器中使用。

ConstraintLayout的所有功能都可以直接从布局编辑器的可视化工具中使用,因为布局API和布局编辑器是专门为对方构建的。 所以你可以使用ConstraintLayout完全通过拖放操作来构建你的布局,而不是编辑XML。

请注意,约束中不能有循环依赖。

ConstraintLayout

相对定位

相对定位是在ConstraintLayout 中创建布局的基本构建块之一。这些约束允许您相对于另一个小部件定位给定的小部件。您可以在水平和垂直轴上约束小部件:

水平轴:左、右、起点和终点

垂直轴:顶边、底边和文本基线

一般概念是将小部件的给定一侧约束到任何其他小部件的另一侧。

以下是可用约束的列表:

layout_constraintLeft_toLeftOf

layout_constraintLeft_toRightOf

layout_constraintRight_toLeftOf

layout_constraintRight_toRightOf

layout_constraintTop_toTopOf

layout_constraintTop_toBottomOf

layout_constraintBottom_toTopOf

layout_constraintBottom_toBottomOf

layout_constraintBaseline_toBaselineOf

layout_constraintStart_toEndOf

layout_constraintStart_toStartOf

layout_constraintEnd_toStartOf

layout_constraintEnd_toEndOf

边距

如果设置了边距,它们将应用于相应的约束(如果存在),将边距强制为目标边和源边之间的空间。通常的布局边距属性可用于此效果:

android:layout_marginStart

android:layout_marginEnd

android:layout_marginLeft

android:layout_marginTop

android:layout_marginRight

android:layout_marginBottom

请注意,边距只能为正数或等于零,并且需要一个Dimension.

连接到GONE 小部件时的边距

当位置约束目标的可见性为时View.GONE,您还可以使用以下属性指示要使用的不同边距值:

layout_goneMarginStart

layout_goneMarginEnd

layout_goneMarginLeft

layout_goneMarginTop

layout_goneMarginRight

layout_goneMarginBottom

尺寸限制

您可以为ConstraintLayout自身定义最小和最大尺寸:

android:minWidth 设置布局的最小宽度

android:minHeight 设置布局的最小高度

android:maxWidth 设置布局的最大宽度

android:maxHeight 设置布局的最大高度

、绝对布局(AbsoluteLayout)

因为灵活性太差,在API Level 3中被废弃。在实际使用中你需要为所有子组件指定x,y坐标。它的直接子类是WebView。

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

推荐阅读更多精彩内容