《Jetpack Compose系列学习》-10 Compose竖向线性布局Column

1,078 阅读5分钟
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>

竖向线性布局就是在屏幕上垂直排列的控件,如图:

image.png

Compose中的竖向线性布局用Column去实现

Column {
    Text(text = "Text1") // 控件1
    Text(text = "Text2") // 控件2
    Text(text = "Text3") // 控件3
}

image.png

我们可以看到Column中包含了三个Text控件,控件垂直竖向排列,默认显示在左上角,如果我们想让其显示在屏幕的正中间只需要添加两行代码:

Column(
    modifier = Modifier.fillMaxSize(), 
    verticalArrangement = Arrangement.Center, // 垂直居中
    horizontalAlignment = Alignment.CenterHorizontally) // 水平居中
{
    Text(text = "Text1") // 控件1
    Text(text = "Text2") // 控件2
    Text(text = "Text3") // 控件3
}

image.png

接下来我们看看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都用上了,不同的是水平对齐方式分别是开始、中间和末尾,我们看看效果:

image.png

看出来区别了吗?其实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
}

image.png

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。每天进步一点点。