一、几种基本布局
1、线性布局
LinearLayout又称为线性布局,是一种非常常用的布局。这个布局会将它所包含的控件在线性方向上依次排列。这里主要通过设置 android:orientation 属性来指定布局的排列方式。如果为 vertical 则在垂直方向上线性排列,如果为 horizontal 则会在水平方向上排列。垂直方向上如下所示,水平方向只需要修改 vertical 这里不过多叙述:
这里需要注意的是,如果 LinearLayout 的排列方向是 horizontal ,内部的控件就绝对不能将宽度指定为 match_parent,因为这样的话,单独一个控件就会将整个水平方向占满,其他控件就没有可放置的位置了。同理,如果为 vertical,内部控件的高度就不能指定为 match_parent。
android:gravity 用来指定文字在控件中的对齐方式。android:layout_gravity 用于指定控件在布局中的对齐方式。但需要注意的是,当 LinearLayout 的排列方式是 horizontal 时,只有垂直方向上的对齐方式才会生效,因而此时水平方向上的长度是不固定的,每添加一个控件,水平方向上的长度都会改变,因而无法指定该方向上的对齐方式。同理,当 LinearLayout 的排列方向是 vertical 时,只有水平方向上的对齐方式才会生效,如下所示:
接下来看 LinearLayout 的另一个重要属性 android:layout_weight。它允许我们使用比例的方式来指定控件的大小。比如我们正在编写一个消息发送界面,需要一个文本编辑框和一个发送按钮,修改代码:
这里我们将宽度都指定成了0dp,然后使用了 android:layout_weight 属性,此时宽度就不应该再由 android:layout_width 决定。在这里我们将 android:layout_weight 属性的值都设为1,着表示 EditText 和 Button 将在水平方向平分宽度。
其原理是,系统会将 LinearLayout 下所有控件的 layout_weight 值相加,得到一个总值,然后每个控件所占大小的比例就是该控件的 layout_weight 的值除以刚才算出来的总值。因此如果想让 EditText 占屏幕的3/5,Button 占屏幕的2/5,只需要将 EditText 的 layout_weight 改为3,Button的 layout_weight 改为2就行了。
2、相对布局
RelativeLayout 又称作相对布局,和 LinearLayout 得排列规则不同,RelativeLayout 显得更加随意一些,它可以通过相对定位的方式让控件出现在布局的任何位置。修改代码如下所示:
我们让 Button1 和父布局的左上角对齐,Button2 和父布局的右上角对齐,Button3 居中显示,Button4 和父布局的左下角对齐,Button5 和父布局的右下角对齐。这几个属性很清楚的能说明它们的作用。效果图如下:
上面例子中每个控件都是相对于父布局进行定位的,此外还可以相对控件进行定位。如下所示:
android:layout_above 属性可以让一个控件位于另一个控件的上方,需要为这个属性指定相对控件id的引用,这里为button3表示让该控件位于 Button3 的上方,其他属性类似。
3、帧布局
FrameLayout 又称作帧布局。这种布局没有方便的定位方式,所有的控件都会默认摆放在布局的左上方。当然除了这种默认效果之外,我们还可以使用 layout_gravity 属性来指定控件在布局中的对齐方式,这和 LinearLayout 中的用法是相似的,如下所示:
我们指定 TextView 在 FrameLayout 中居左对齐,指定 ImageView 在 FrameLayout 中居右对齐,效果如下所示:
4、百分比布局
只有 LinearLayout 支持使用 layout_weight 属性来实现按比例指定控件大小的功能,其他两种布局都不支持。为此,Android 引入了一种全新的布局方式来解决此问题——百分比布局。在这种布局中为我们可以不在使用 wrap_content、match_parent等方式来指定控件的大小,而是允许直接指定控件在布局中所占的百分比,这样的话就可以轻松实现平分布局甚至是任意比例分割布局的效果了。
由于 LinearLayout 本身已经支持按比例指定控件的大小了,因此百分比布局只为 FrameLayout 和 RelativeLayout 进行了功能扩展,并提供了 PercentFrameLayout 和 PercentRelativeLayout 这两个全新的布局。
不同于前面3种布局,百分比布局属于新增布局,为此,Android 团队将百分比布局定义在 support 库当中,我们只需要在项目的build.gradle中添加百分比布局库的依赖,就能保证百分比布局在 Android 所有系统版本上的兼容性了。
dependencies {
...
implementation 'com.android.support:percent:24.2.1'
}
接下来修改 activity_main.xml 中的代码,如下所示:
最外层我们使用了 PercentFrameLayout,由于百分比布局并不是内置在系统的SDK当中的,所有需要把完整的包路径写出来。然后还必须定义一个app的命名控件,这样才能使用百分比布局。
在 PercentFrameLayout 中我们定义了4哥按钮,使用 layout_widthPercent 和 layout_heightPercent 属性指定了各按钮的高度和宽度为布局的50%。
不过 PercentFrameLayout 还是会继承 FrameLayout 的特性,即所有的控件默认都是摆放在布局的左上角。那么为了让这4各按钮不会重叠,还是借助了 layout_gravity 来分别将4各按钮放在不同的位置。
5、ConstraintLayout
Android新特性介绍,ConstraintLayout完全解析
实战篇ConstraintLayout的崛起之路
6、番外:创建自定义控件
我们所用的所有控件都是直接或者间接继承自 View 的,所用的布局都是直接或间接继承自 ViewGroup 的。View 是android 中最基本的一种UI组件,它可以在屏幕上绘制一块矩形区域,并能影响这块区域的各种事件,因此,我们使用的各种控件其实就是在 View 的基础上又添加了各自特有的功能。而 ViewGroup 则是一种特殊的 View,它可以包含很多子 View 和子 ViewGroup,是一个用于放置控件和布局的容器。
(1)、引入布局
比如需要自定义一个标题栏,如果在每个活动中的布局中都编写一遍同样的标题栏代码,会导致代码大量的重复。这个时候就可以使用引入布局的方式来解决这个问题,新建一个布局 title.xml,在 title.xml 中完成布局i标题栏的布局,然后在活动中添加如下代码:
然后将系统自带的标题栏隐藏掉就行了。使用这种方式,不管有多少布局需要添加标题栏,只需要一行 include 语句就可以了。
(2)、创建自定义控件
引入布局的技巧确实解决了重写布局代码的问题,但是如果布局中有一些控件要求能够响应事件,我们还是需要在每个活动中为这些控件单独写一次事件注册的代码。比如说标题栏中的返回按钮,其实不管是在哪个活动中,这个按钮的功能都是相同的,即销毁当前活动。而如果在每一个活动中都需要重新注册返回按钮的点击事件,无疑会增加很多重复代码,这种情况最好使用自定义控件的方式来解决。
新建 TitleLayout 继承自 LinearLayout,让它成为我们自定义的标题栏控件,代码如下所示:
public class TitleLayout extends LinearLayout {
public TitleLayout(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.title, this);
}
}
首先我们重写了 LinearLayout 中带有两个参数的构造函数,在布局中引入 TitleLayout 控件就会调用这个构造函数。然后再构造函数中需要对标题栏布局进行动态加载,这就要借助 LayoutInflater 来实现了。通过 LayoutInflater 的 form() 方法可以构建出一个 LayoutInflater 对象,然后调用 inflate() 方法来动态加载布局文件,inflate() 方法接收两个参数,第一个参数是要加载的布局的id,这里我们传入 R.layout.title ,第二个参数是给加载好的布局再添加一个父布局,这里我们想要指定为 TitleLayout,于是直接传入 this。
现在自定义控件已经创建好了,然后我们需要在布局文件中添加这个自定义控件,如下所示:
添加自定义控件和添加普通控件的方式基本一样的,只不过在添加自定义控件 的时候,我们需要指定控件的完整类名,包名在这里是不可以省略的。
下面尝试为标题栏的按钮注册点击事件,修改 TitleLayout 中 的代码,如下所示:
public class TitleLayout extends LinearLayout {
public TitleLayout(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.title, this);
Button titleBack = (Button) findViewById(R.id.title_back);
Button titleEdit = (Button) findViewById(R.id.title_edit);
titleBack.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
((Activity) getContext()).finish();
}
});
titleEdit.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getContext(), "You clicked Edit button",
Toast.LENGTH_SHORT).show();
}
});
}
}
这样的话,每当我们在一个布局中引入 TitleLayout 时,返回按钮和编辑按钮的点击事件就已经自动实现好了,省去了很多编写重复代码的工作。