Column
我们知道在Android View中,竖向线性布局这么写:
<?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">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Text1"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Text2"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Text3"/>
</LinearLayout>
竖向线性布局就是在屏幕上垂直排列的控件,如图:
Compose中的竖向线性布局用Column去实现
Column {
Text(text = "Text1") // 控件1
Text(text = "Text2") // 控件2
Text(text = "Text3") // 控件3
}
我们可以看到Column中包含了三个Text控件,控件垂直竖向排列,默认显示在左上角,如果我们想让其显示在屏幕的正中间只需要添加两行代码:
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center, // 垂直居中
horizontalAlignment = Alignment.CenterHorizontally) // 水平居中
{
Text(text = "Text1") // 控件1
Text(text = "Text2") // 控件2
Text(text = "Text3") // 控件3
}
接下来我们看看Column的源码:
@Composable
inline fun Column(
modifier: Modifier = Modifier,
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
content: @Composable ColumnScope.() -> Unit
) {
val measurePolicy = columnMeasurePolicy(verticalArrangement, horizontalAlignment)
Layout(
content = { ColumnScopeInstance.content() },
measurePolicy = measurePolicy,
modifier = modifier
)
}
我们看到Column方法被inline修饰,表示为内联函数,inline的工作原理就是将内联函数整体赋值到调用处,提高了代码的运行效率。再看看参数:
modifier
这个是修饰符,前面已经接触挺多了,可以设置大小样式等
verticalArrangement
布局子控件的垂直排列方式,默认是Arrangement.Top,也就是从上到下排列。
horizontalAlignment
布局子控件的水平排列方式,默认Alignment.Start,也就是从左到右排列。
verticalArrangement的类型是Arrangement.Vertical,那么verticalArrangement可以设置哪些值呢?我们先看看Arrangement是什么。
@Immutable
object Arrangement {
/**
* 水平
*/
@Stable
interface Horizontal {
/**
* Spacing that should be added between any two adjacent layout children.
*/
val spacing get() = 0.dp
/**
* Horizontally places the layout children.
*
* @param totalSize Available space that can be occupied by the children, in pixels.
* @param sizes An array of sizes of all children, in pixels.
* @param layoutDirection A layout direction, left-to-right or right-to-left, of the parent
* layout that should be taken into account when determining positions of the children.
* @param outPositions An array of the size of [sizes] that returns the calculated
* positions relative to the left, in pixels.
*/
fun Density.arrange(
totalSize: Int,
sizes: IntArray,
layoutDirection: LayoutDirection,
outPositions: IntArray
)
}
/**
* 垂直
*/
@Stable
interface Vertical {
/**
* Spacing that should be added between any two adjacent layout children.
*/
val spacing get() = 0.dp
/**
* Vertically places the layout children.
*
* @param totalSize Available space that can be occupied by the children, in pixels.
* @param sizes An array of sizes of all children, in pixels.
* @param outPositions An array of the size of [sizes] that returns the calculated
* positions relative to the top, in pixels.
*/
fun Density.arrange(
totalSize: Int,
sizes: IntArray,
outPositions: IntArray
)
}
/**
* Used to specify the horizontal arrangement of the layout's children in horizontal layouts
* like [Row], or the vertical arrangement of the layout's children in vertical layouts like
* [Column].
*/
@Stable
interface HorizontalOrVertical : Horizontal, Vertical {
/**
* Spacing that should be added between any two adjacent layout children.
*/
override val spacing: Dp get() = 0.dp
}
// 省略...
从源码可以看到,Arrangement是一个单例,并用注解Immutable声明了不可变,后面定义了3个内部接口————Horizontal、Vertical和HorizontalOrVertical,顾名思义,分别是横向的、纵向的和横纵。上面例子中的Arrangement.Vertical,所以只要实现了Arrangement.Vertical接口的类都可以作为verticalArrangement的参数。HorizontalOrVertical接口继承自Horizontal和vertical,那么实现了HorizontalOrVertical接口的类也可以作为verticalArrangement的参数来使用,如下所示:
@Stable
val Start = object : Horizontal {
// 省略...
}
@Stable
val End = object : Horizontal {
// 省略...
}
@Stable
val Top = object : Vertical {
// 省略...
}
@Stable
val Bottom = object : Vertical {
// 省略...
}
@Stable
val Center = object : HorizontalOrVertical {
// 省略...
}
@Stable
val SpaceEvenly = object : HorizontalOrVertical {
// 省略...
}
@Stable
val SpaceBetween = object : HorizontalOrVertical {
// 省略...
}
@Stable
val SpaceAround = object : HorizontalOrVertical {
// 省略...
}
@Stable
fun spacedBy(space: Dp): HorizontalOrVertical =
SpacedAligned(space, true, null)
其中Center、Top和Bottom好理解,这里面SpaceEvenly、SpaceBetween、SpaceAround和spacedBy容易弄混,我们通过代码来看看它们的区别:
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.SpaceEvenly,
horizontalAlignment = Alignment.Start)
{
DefaultText("Text1") // 控件1
DefaultText("Text2") // 控件2
DefaultText("Text3") // 控件3
}
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.SpaceAround,
horizontalAlignment = Alignment.CenterHorizontally)
{
DefaultText("Text1") // 控件1
DefaultText("Text2") // 控件2
DefaultText("Text3") // 控件3
}
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.SpaceBetween,
horizontalAlignment = Alignment.End)
{
DefaultText("Text1") // 控件1
DefaultText("Text2") // 控件2
DefaultText("Text3") // 控件3
}
@Composable
fun DefaultText(text: String) {
Text(text = text,
modifier = Modifier.size(100.dp).background(Color.Red),
fontSize = 30.sp,
textAlign = TextAlign.Center
)
}
我们把SpaceEvenly、SpaceBetween、SpaceAround都用上了,不同的是水平对齐方式分别是开始、中间和末尾,我们看看效果:
看出来区别了吗?其实SpaceEvenly是子控件在Column中均匀分布;SpaceAround是子控件在Column中均匀分布,将屏幕三等分;SpaceBetween是子控件的间距均匀分布,第一个控件前和最后一个控件后没有可用空间。
最后还剩下spaceBy,它很简单,就是使相邻的两个子控件隔开固定的距离,来看个例子:
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(20.dp), // 间隔20dp
horizontalAlignment = Alignment.CenterHorizontally)
{
DefaultText("Text1") // 控件1
DefaultText("Text2") // 控件2
DefaultText("Text3") // 控件3
}
horizontalAlignment参数类型是Alignment.Horizontal,这个好理解,我们布局子控件时肯定要从横竖两个方向来约束其位置,我们来看看它的源码:
@Stable
fun interface Alignment {
fun align(size: IntSize, space: IntSize, layoutDirection: LayoutDirection): IntOffset
@Stable
fun interface Horizontal {
fun align(size: Int, space: Int, layoutDirection: LayoutDirection): Int
}
@Stable
fun interface Vertical {
fun align(size: Int, space: Int): Int
}
companion object {
// 2D Alignments.
@Stable
val TopStart: Alignment = BiasAlignment(-1f, -1f)
@Stable
val TopCenter: Alignment = BiasAlignment(0f, -1f)
@Stable
val TopEnd: Alignment = BiasAlignment(1f, -1f)
@Stable
val CenterStart: Alignment = BiasAlignment(-1f, 0f)
@Stable
val Center: Alignment = BiasAlignment(0f, 0f)
@Stable
val CenterEnd: Alignment = BiasAlignment(1f, 0f)
@Stable
val BottomStart: Alignment = BiasAlignment(-1f, 1f)
@Stable
val BottomCenter: Alignment = BiasAlignment(0f, 1f)
@Stable
val BottomEnd: Alignment = BiasAlignment(1f, 1f)
// 1D Alignment.Verticals.
@Stable
val Top: Vertical = BiasAlignment.Vertical(-1f)
@Stable
val CenterVertically: Vertical = BiasAlignment.Vertical(0f)
@Stable
val Bottom: Vertical = BiasAlignment.Vertical(1f)
// 1D Alignment.Horizontals.
@Stable
val Start: Horizontal = BiasAlignment.Horizontal(-1f)
@Stable
val CenterHorizontally: Horizontal = BiasAlignment.Horizontal(0f)
@Stable
val End: Horizontal = BiasAlignment.Horizontal(1f)
}
}
// 省略...
Alignment是一个接口,但前面多了"fun",使用这个关键字标记接口后,只要将此类接口作为参数,就可以将lambda作为参数传递。我们还发现Alignment和Arrangement有点类似,也有Horizontal和Vertical接口,由于Alignment是一个接口,所以新增了伴生对象,在里面添加了Horizontal和Vertical的相关参数,这里面看Horizontal.Start、Horizontal.End和Horizontal.CenterHorizontally是不是很熟悉了,因为上面例子中已经用过了,就不再说了。是不是很简单?下面我们会接着学习横向线性布局Row。每天进步一点点。