日期:
来源:Android 开发者收集编辑:朱涛
在上一篇文章《 2 小时入门 Jetpack Compose (上) 》里,我们已经完成了 Splash 页面的 UI 和动画了。
这篇文章,让我们来完成:「首页」+「详情页」吧。
首页
跟 Splash 页面不一样,ToDoApp 的首页,要复杂不少。从结构上来讲,它主要分为三个部分:
第一,页面顶部的 TopBar; 第二,页面的主要内容 Content,也就是 "待完成的任务列表"; 第三,页面右下角的 FloatingActionButton。
// 代码段 1@Composablefun HomeScreen() {Scaffold(scaffoldState = scaffoldState,// 1topBar = {HomeAppBar()},// 2content = {HomeContent()},// 3floatingActionButton = {HomeFab()})}// 1@Composablefun HomeAppBar() {}// 2@Composablefun HomeContent() {}// 3@Composablefun HomeFab() {}
// 代码段 2@Composablefun 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) {}
TopBar
// 代码段 3@Composablefun HomeAppBar(onDeleteAllConfirmed : () -> Unit) {HomeTopAppBar(onDeleteAllConfirmed = {onDeleteAllConfirmed()})}
// 代码段 4@Composablefun HomeTopAppBar(onDeleteAllConfirmed: () -> Unit) {TopAppBar(// 1,Title:任务列表title = {Text(text = stringResource(id = R.string.list_screen_title),color = MaterialTheme.colors.topAppBarContent)},// 2,选项:清空任务actions = {HomeAppBarActions(onDeleteAllConfirmed = onDeleteAllConfirmed)},backgroundColor = MaterialTheme.colors.topAppBarBackground)}@Composablefun HomeAppBarActions(onDeleteAllConfirmed: () -> Unit) {DeleteAllAction(onDeleteAllConfirmed = { isShowDialog = true })}
// 代码段 5@Composablefun DeleteAllAction(onDeleteAllConfirmed: () -> Unit) {var expanded by remember { mutableStateOf(false) }IconButton(onClick = { expanded = true }) {// 1,更多按钮Icon(painter = painterResource(id = R.drawable.ic_more),contentDescription = stringResource(id = R.string.delete_all_action),tint = MaterialTheme.colors.topAppBarContent)// 2,下拉列表DropdownMenu(expanded = expanded,onDismissRequest = { expanded = false }) {// 3,下拉列表,「清空任务」DropdownMenuItem(onClick = {expanded = falseonDeleteAllConfirmed()}) {Text(modifier = Modifier.padding(start = MEDIUM_PADDING),text = stringResource(id = R.string.delete_all_action),style = Typography.subtitle2)}}}}
@Composablefun TodoAlertDialog(title: String,msg: String,isShowDialog: Boolean, // 1onNoClicked: () -> Unit, // 2onYesClicked: () -> Unit, // 3) {if (isShowDialog) { // 4AlertDialog(title = {Text(text = title,fontSize = MaterialTheme.typography.h5.fontSize,fontWeight = FontWeight.Bold)},text = {Text(text = msg,fontSize = MaterialTheme.typography.subtitle1.fontSize,fontWeight = FontWeight.Normal)},confirmButton = {Button(onClick = {onYesClicked()onNoClicked()}){Text(text = stringResource(id = R.string.yes))}},dismissButton = {OutlinedButton(onClick = { onNoClicked() }){Text(text = stringResource(id = R.string.no))}},onDismissRequest = { onNoClicked() })}}
任务列表
@Composablefun HomeContent(allTasks: RequestState<List<Task>>,onSwipeToDelete: (Task) -> Unit,gotoTaskDetail: (taskId: Int) -> Unit,onUpdateTask: (Task) -> Unit) {if (allTasks is RequestState.Success &&allTasks.data.isNotEmpty()) {// 不为空HomeTasksColumn()} else {// 空页面HomeEmptyContent()}}
@Composablefun HomeTasksColumn1(tasks: List<Task>,gotoTaskDetail: (taskId: Int) -> Unit,onUpdateTask: (Task) -> Unit,) {LazyColumn {itemsIndexed(items = tasks,key = { _, task ->task.id}) { index, task ->TaskItem(task = task,gotoTaskDetail = gotoTaskDetail,onUpdateTask = onUpdateTask)}}}
@Composablefun HomeTasksColumn1(tasks: List<Task>,gotoTaskDetail: (taskId: Int) -> Unit,onUpdateTask: (Task) -> Unit,) {LazyColumn {itemsIndexed(items = tasks,key = { _, task ->task.id}) { index, task ->val size = remember(tasks) {tasks.size}// 省略部分代码AnimatedVisibility(visible = true,enter = slideInHorizontally(animationSpec = tween(durationMillis = 300),// index 越大,初始偏移越大initialOffsetX = { -(it * (index + 1) / (size + 2)) }),exit = shrinkVertically(animationSpec = tween(durationMillis = 300))) {TaskItem(task = task,gotoTaskDetail = gotoTaskDetail,onUpdateTask = onUpdateTask)}}}}
@Composablefun HomeTasksColumn(tasks: List<Task>,onSwipeToDelete: (Task) -> Unit,gotoTaskDetail: (taskId: Int) -> Unit,onUpdateTask: (Task) -> Unit,) {LazyColumn {itemsIndexed(items = tasks,key = { _, task ->task.id}) { index, task ->val size = remember(tasks) {tasks.size}// 省略部分AnimatedVisibility(visible = itemAppeared && !isDismissed,enter = slideInHorizontally(animationSpec = tween(durationMillis = 300),initialOffsetX = { -(it * (index + 1) / (size + 2)) }),exit = shrinkVertically(animationSpec = tween(durationMillis = 300))) {// 1,变化SwipeToDismiss(state = dismissState,directions = setOf(DismissDirection.EndToStart),dismissThresholds = { FractionalThreshold(fraction = 0.2f) },background = { SwipeBackground(degrees = degrees) },dismissContent = {TaskItem(task = task,gotoTaskDetail = gotoTaskDetail,onUpdateTask = onUpdateTask)})}}}}
@Composablefun HomeTasksColumn(tasks: List<Task>,onSwipeToDelete: (Task) -> Unit,gotoTaskDetail: (taskId: Int) -> Unit,onUpdateTask: (Task) -> Unit,) {LazyColumn {itemsIndexed(items = tasks,key = { _, task ->task.id}) { index, task ->// 省略部分// 1,变化val degrees by animateFloatAsState(if (dismissState.targetValue == DismissValue.Default)0felse-180f)// 省略部分AnimatedVisibility() {}}}}
@Composablefun HomeTasksColumn(tasks: List<Task>,onSwipeToDelete: (Task) -> Unit,gotoTaskDetail: (taskId: Int) -> Unit,onUpdateTask: (Task) -> Unit,) {LazyColumn {itemsIndexed(items = tasks,key = { _, task ->task.id}) { index, task ->// 省略部分// 1val isDeleteEnable by remember(degrees) {derivedStateOf { degrees == -180f }}val context = LocalContext.current// 2DisposableEffect(key1 = isDeleteEnable) {if (isDeleteEnable) {showToast(context, "松手后删除!")}onDispose {}}// 省略部分AnimatedVisibility() {}}}}
App 运行期间,Composable 方法是会被反复调用的,我们需要 isDeleteEnable 来标记用户是否触发了「侧滑删除」的阈值。这里我们使用了 derivedStateOf 来优化 Compose 的性能。
详情页
@Composablefun TaskDetailScreen() {Scaffold(topBar = {TaskDetailAppBar()},content = {TaskDetailContent()})}
第一排,包含一个 TextField、CheckBox; 第二排,还是一个 TextField。
@Composablefun TaskDetailContent() {Column() {Row(modifier = Modifier.fillMaxWidth()) {TextField()Checkbox()}Divider()TextField()}}
@Composablefun TaskDetailContent(modifier: Modifier = Modifier,title: String,onTitleChange: (String) -> Unit,description: String,onDescriptionChange: (String) -> Unit,isDone: Boolean,isDoneChange: (Boolean) -> Unit,) {Column(modifier = modifier.fillMaxSize().background(MaterialTheme.colors.background).padding(all = MEDIUM_PADDING)) {Row(modifier = Modifier.fillMaxWidth()) {TextField(modifier = Modifier.fillMaxWidth().weight(8F),value = title,onValueChange = { onTitleChange(it) },label = { Text(stringResource(id = R.string.enter_title)) },placeholder = { Text(text = stringResource(id = R.string.title)) },textStyle = MaterialTheme.typography.body1,singleLine = true,colors = TextFieldDefaults.textFieldColors(backgroundColor = Transparent))Checkbox(modifier = Modifier.weight(1F),checked = isDone,onCheckedChange = isDoneChange)}Divider(modifier = Modifier.height(SMALL_PADDING),color = MaterialTheme.colors.background)TextField(modifier = Modifier.fillMaxWidth().height(200.dp),value = description,onValueChange = { onDescriptionChange(it) },label = { Text(stringResource(id = R.string.enter_description)) },placeholder = { Text(text = stringResource(id = R.string.description)) },textStyle = MaterialTheme.typography.body1,)}}
结束语
恭喜!您已经完成课程 80% 的内容了。
接下来需要做的,就是跟随文章里的步骤,一行行的敲代码了。读文章只需要 10 分钟,只有真正花 2 小时写代码,才可能:「2 小时入门 Jetpack Compose」!
长按右侧二维码
查看更多开发者精彩分享
"开发者说·DTalk" 面向中国开发者们征集 Google 移动应用 (apps & games) 相关的产品/技术内容。欢迎大家前来分享您对移动应用的行业洞察或见解、移动开发过程中的心得或新发现、以及应用出海的实战经验总结和相关产品的使用反馈等。我们由衷地希望可以给这些出众的中国开发者们提供更好展现自己、充分发挥自己特长的平台。我们将通过大家的技术内容着重选出优秀案例进行谷歌开发技术专家 (GDE) 的推荐。
点击屏末 | 阅读原文 | 即刻报名参与 "开发者说·DTalk"