《Jetpack Compose系列学习》-13 Compose中的Scaffold和ConstraintLayout

956 阅读3分钟
Scaffold

我们知道Flutter里有一个Scaffold控件,中文翻译过来是脚手架的意思,它实现了基本的Material Design可视化的布局结构,提供了抽屉drawer、snackbar和底部导航栏的API,帮我们更方便的定义页面布局。在Compose中也有Scaffold,我们看看它是怎么使用的。

Scaffold的简单了解

我们先看看它的方法体参数:

@Composable
fun Scaffold(
    modifier: Modifier = Modifier,
    scaffoldState: ScaffoldState = rememberScaffoldState(),
    topBar: @Composable () -> Unit = {},
    bottomBar: @Composable () -> Unit = {},
    snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
    floatingActionButton: @Composable () -> Unit = {},
    floatingActionButtonPosition: FabPosition = FabPosition.End,
    isFloatingActionButtonDocked: Boolean = false,
    drawerContent: @Composable (ColumnScope.() -> Unit)? = null,
    drawerGesturesEnabled: Boolean = true,
    drawerShape: Shape = MaterialTheme.shapes.large,
    drawerElevation: Dp = DrawerDefaults.Elevation,
    drawerBackgroundColor: Color = MaterialTheme.colors.surface,
    drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
    drawerScrimColor: Color = DrawerDefaults.scrimColor,
    backgroundColor: Color = MaterialTheme.colors.background,
    contentColor: Color = contentColorFor(backgroundColor),
    content: @Composable (PaddingValues) -> Unit
)

可以看到,Scaffold的参数很多,但方便的是你可以选择使用其中的几个,因为这些参数都有默认值,我们通过一个例子来看看它的使用:

// Scaffold的使用
Scaffold(
    topBar = { /*标题栏*/
        TopAppBar(title = { Text("标题") }, navigationIcon = {
            IconButton(onClick = {  /*点击事件*/ }) {
                Icon(Icons.Filled.ArrowBack, "")
            }})
    },
    floatingActionButton = { /*悬浮按钮*/
        FloatingActionButton(onClick = {
            // Floating点击事件
            Log.e("LM" , "点击了FloatingButton")
        }) {
            Text("OK")
        }
    },
    content = { /*主内容*/
        Column(
            modifier = Modifier.fillMaxSize(),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally,
        ) {
            Text("主屏幕", fontSize = 40.sp)
        }
    }
)

我们添加了一个标题栏、一个悬浮按钮和主屏幕,这些在Android View里也不陌生,运行效果如下: image.png

点击悬浮按钮后可以看到控制台打印日志:

2022-04-09 09:22:42.330 21719-21719/com.carey.compose E/LM: 点击了FloatingButton
抽屉的实现

在Android View中要实现抽屉的效果不容易,但是在Compose中实现起来却很容易,在Scaffold的方法里有关于抽屉的参数,我们只需要设置上即可:

drawerContent = {
    Column(modifier = Modifier
        .fillMaxSize()
        .background(Color.Blue),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(text = "侧边栏抽屉", fontSize = 30.sp)
    }
}

untitled.gif

代码很简单,一个充满父布局的Column里包含一个Text,运行代码如上图,可以看到从左往右能拉出一个抽屉的效果。其实Scaffold还可以设置很多的样式和功能,可以根据自己的业务需求场景去实现自己的效果,可以去尝试下。

ConstraintLayout

ConstraintLayout还是很常见的,Android View中也常用,很好用,能很好的解决布局层次问题。如:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <TextView
        android:id="@+id/text1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="Text1"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>
    <TextView
        android:id="@+id/text2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="Text2"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/text1"/>
    <TextView
        android:id="@+id/text3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="Text3"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/text2"/>

</androidx.constraintlayout.widget.ConstraintLayout>

image.png

约束布局优点很多,但是要实现对齐要求比较复杂的较大布局时,ConstraintLayout很有用,但在创建简单布局时,Compose中首选Column和Row。和Column和Row等布局不同,约束布局需要额外添加依赖:

implementation 'androidx.constraintlayout:constraintlayout-compose:1.0.0'

虽然约束布局可以替代多个嵌套Row、Column、Box和自定义布局,但只是替代,性能上没有显著提升,因为Compose可以高效处理较深布局层次结构,所以还是推荐使用约束布局,方便后期维护。

Compose中的ConstraintLayout支持DSL:ConstraintLayout中每个可组合项都需要有与之关联的引用,引用是使用createRefs或createRef创建的,不同的是createRefs可以同时创建多个引用,而createRef只能创建一个引用。约束条件是使用constrainAs修饰符提供的,该修饰符将引用作为参数,既可以在主体lambda中指定约束条件,也可以使用linkTo或其它有用的方法指定。parent是一个现有的引用(ConstraintLayout本身),可用于指定对ConstraintLayout可组合项本身的约束条件。我们看一个例子:

@Composable
fun DefaultText(text: String, modifier: Modifier) {
    Text(
        text,
        modifier = modifier.size(100.dp)
            .background(Color.Red),
        fontSize = 30.sp,
        textAlign = TextAlign.Center
    )
}

ConstraintLayout(modifier = Modifier.fillMaxSize()) {
    val (one, two) = createRefs()
    val three = createRef()
    DefaultText(
        "",
        modifier = Modifier.constrainAs(one) {
            start.linkTo(parent.start)
            end.linkTo(parent.end)
            top.linkTo(parent.top, margin = 16.dp)
        }
    )

    DefaultText("Two", Modifier.constrainAs(two) {
        start.linkTo(parent.start)
        end.linkTo(parent.end)
        top.linkTo(one.bottom, margin = 16.dp)
    })

    DefaultText("Three", Modifier.constrainAs(three) {
        start.linkTo(parent.start)
        end.linkTo(parent.end)
        bottom.linkTo(parent.bottom, margin = 16.dp)
    })
}

image.png]

我们可以看到,首先创建了一个可组合项DefaultText,然后在ConstraintLayout中使用DefaultText,接着通过createRefs创建了一个多引用,又通过createRef创建了一个单引用。下面的可组合项通过constrainAs和引用进行关联,又通过start.linkTo(parent.start)和end.linkTo(parent.end)将可组合项定位到父布局的水平中间位置,并添加了margin=16dp,之后将可组合项Two放到One的下面,最后将可组合项Three放到父布局的底部。最终效果如上图所示。

我们可以看到,和Android View中的约束布局ConstraintLayout约束条件是一致的,大家可以试着用用其它的属性。