fix: FAB 在切换页面时文字溢出

This commit is contained in:
Super12138 2025-11-09 10:10:30 +08:00
parent 0fb4192493
commit 7fa618a611
5 changed files with 55 additions and 96 deletions

View file

@ -37,7 +37,7 @@ android {
applicationId = "cn.super12138.todo" applicationId = "cn.super12138.todo"
minSdk = 24 minSdk = 24
targetSdk = 36 targetSdk = 36
versionCode = 791 versionCode = 883
versionName = "2.3.3" versionName = "2.3.3"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

View file

@ -1,34 +1,29 @@
package cn.super12138.todo.ui.components package cn.super12138.todo.ui.components
import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Row import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.FloatingActionButtonDefaults import androidx.compose.material3.FloatingActionButtonDefaults
import androidx.compose.material3.FloatingActionButtonElevation import androidx.compose.material3.FloatingActionButtonElevation
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.SmallExtendedFloatingActionButton
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.contentColorFor import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import cn.super12138.todo.utils.VibrationUtils import cn.super12138.todo.utils.VibrationUtils
/** /**
* 带动画且比 Material 3 内置组件动画好看的的可扩展 FAB * 封装的内置组件 FAB在点击时支持添加震动反馈
* * 内置组件的动画总是卡一下 * * 现在内置组件的动画好像好一点了
* *
* 将缩放部分转为最简单的`AnimatedVisibility`实现 * 将缩放部分转为最简单的`AnimatedVisibility`实现
* @param icon FAB 的前置图标 * @param icon FAB 的前置图标
* @param text FAB 的文本 * @param text FAB 的文本
* @param textOverflow FAB 文本溢出显示方案
* @param expanded 是否为展开状态 * @param expanded 是否为展开状态
* @param containerColor FAB 容器的颜色 * @param containerColor FAB 容器的颜色
* @param contentColor FAB 文本和图标颜色通常不需要自己指定会自动依据容器颜色设置 * @param contentColor FAB 文本和图标颜色通常不需要自己指定会自动依据容器颜色设置
@ -36,20 +31,34 @@ import cn.super12138.todo.utils.VibrationUtils
* @param onClick 点击 FAB 后的回调 * @param onClick 点击 FAB 后的回调
* @param modifier `Modifier` 修改器 * @param modifier `Modifier` 修改器
*/ */
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
fun AnimatedExtendedFloatingActionButton( fun TodoFloatingActionButton(
modifier: Modifier = Modifier,
icon: ImageVector,
text: String, text: String,
textOverflow: TextOverflow = TextOverflow.Clip, icon: ImageVector,
expanded: Boolean, onClick: () -> Unit,
modifier: Modifier = Modifier,
expanded: Boolean = true,
shape: Shape = FloatingActionButtonDefaults.smallExtendedFabShape,
containerColor: Color = FloatingActionButtonDefaults.containerColor, containerColor: Color = FloatingActionButtonDefaults.containerColor,
contentColor: Color = contentColorFor(containerColor), contentColor: Color = contentColorFor(containerColor),
elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(), elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
onClick: () -> Unit interactionSource: MutableInteractionSource? = null,
) { ) {
val view = LocalView.current val view = LocalView.current
FloatingActionButton( SmallExtendedFloatingActionButton(
text = {
Text(
text = text,
overflow = TextOverflow.Clip
)
},
icon = {
Icon(
imageVector = icon,
contentDescription = null
)
},
onClick = { onClick = {
VibrationUtils.performHapticFeedback(view) VibrationUtils.performHapticFeedback(view)
onClick() onClick()
@ -57,27 +66,8 @@ fun AnimatedExtendedFloatingActionButton(
elevation = elevation, elevation = elevation,
containerColor = containerColor, containerColor = containerColor,
contentColor = contentColor, contentColor = contentColor,
shape = shape,
interactionSource = interactionSource,
modifier = modifier modifier = modifier
) { )
Row(
modifier = Modifier.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = icon,
contentDescription = null
)
// Spacer(Modifier.width(if (expanded) 8.dp else 0.dp))
AnimatedVisibility(expanded) {
Row {
Spacer(Modifier.width(8.dp))
Text(
text = text,
maxLines = 1,
overflow = textOverflow
)
}
}
}
}
} }

View file

@ -23,9 +23,7 @@ import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.Save import androidx.compose.material.icons.outlined.Save
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SmallExtendedFloatingActionButton
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
@ -51,6 +49,7 @@ import cn.super12138.todo.ui.TodoDefaults
import cn.super12138.todo.ui.components.ChipItem import cn.super12138.todo.ui.components.ChipItem
import cn.super12138.todo.ui.components.ConfirmDialog import cn.super12138.todo.ui.components.ConfirmDialog
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
import cn.super12138.todo.ui.components.TodoFloatingActionButton
import cn.super12138.todo.ui.pages.editor.components.TodoCategoryChip import cn.super12138.todo.ui.pages.editor.components.TodoCategoryChip
import cn.super12138.todo.ui.pages.editor.components.TodoCategoryTextField import cn.super12138.todo.ui.pages.editor.components.TodoCategoryTextField
import cn.super12138.todo.ui.pages.editor.components.TodoContentTextField import cn.super12138.todo.ui.pages.editor.components.TodoContentTextField
@ -115,41 +114,33 @@ fun TodoEditorPage(
} }
} }
BackHandler { checkModifiedBeforeBack() } BackHandler(onBack = ::checkModifiedBeforeBack)
LargeTopAppBarScaffold( LargeTopAppBarScaffold(
title = stringResource(if (toDo != null) R.string.title_edit_task else R.string.action_add_task), title = stringResource(if (toDo != null) R.string.title_edit_task else R.string.action_add_task),
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
floatingActionButton = { floatingActionButton = {
with(sharedTransitionScope) { with(sharedTransitionScope) {
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { Row(
horizontalArrangement = Arrangement.spacedBy(10.dp),
modifier = Modifier.imePadding()
) {
if (toDo !== null) { if (toDo !== null) {
SmallExtendedFloatingActionButton( TodoFloatingActionButton(
text = { Text(stringResource(R.string.action_delete)) }, text = stringResource(R.string.action_delete),
icon = { icon = Icons.Outlined.Delete,
Icon(
imageVector = Icons.Outlined.Delete,
contentDescription = null
)
},
expanded = true, expanded = true,
containerColor = MaterialTheme.colorScheme.errorContainer, containerColor = MaterialTheme.colorScheme.errorContainer,
onClick = { uiState.showDeleteConfirmDialog = true }, onClick = { uiState.showDeleteConfirmDialog = true }
modifier = Modifier.imePadding()
) )
} }
SmallExtendedFloatingActionButton( TodoFloatingActionButton(
text = { Text(stringResource(R.string.action_save)) }, text = stringResource(R.string.action_save),
icon = { icon = Icons.Outlined.Save,
Icon(
imageVector = Icons.Outlined.Save,
contentDescription = null
)
},
expanded = true, expanded = true,
onClick = { onClick = {
if (uiState.setErrorIfNotValid()) { if (uiState.setErrorIfNotValid()) {
return@SmallExtendedFloatingActionButton return@TodoFloatingActionButton
} else { } else {
uiState.clearError() uiState.clearError()
val newTodo = TodoEntity( val newTodo = TodoEntity(
@ -163,7 +154,6 @@ fun TodoEditorPage(
} }
}, },
modifier = Modifier modifier = Modifier
.imePadding()
.sharedElement( .sharedElement(
sharedContentState = rememberSharedContentState(key = Constants.KEY_TODO_FAB_TRANSITION), sharedContentState = rememberSharedContentState(key = Constants.KEY_TODO_FAB_TRANSITION),
animatedVisibilityScope = animatedVisibilityScope animatedVisibilityScope = animatedVisibilityScope
@ -172,7 +162,7 @@ fun TodoEditorPage(
} }
} }
}, },
onBack = { checkModifiedBeforeBack() }, onBack = ::checkModifiedBeforeBack,
modifier = modifier modifier = modifier
) { innerPadding -> ) { innerPadding ->
LazyColumn( LazyColumn(
@ -183,19 +173,12 @@ fun TodoEditorPage(
.fillMaxSize() .fillMaxSize()
) { ) {
item { item {
// with(sharedTransitionScope) {
TodoContentTextField( TodoContentTextField(
value = uiState.toDoContent, value = uiState.toDoContent,
onValueChange = { uiState.toDoContent = it }, onValueChange = { uiState.toDoContent = it },
isError = uiState.isErrorContent, isError = uiState.isErrorContent,
modifier = Modifier modifier = Modifier.fillMaxWidth()
.fillMaxWidth()
/*.sharedBounds(
sharedContentState = rememberSharedContentState("${Constants.KEY_TODO_CONTENT_TRANSITION}_${toDo?.id}"),
animatedVisibilityScope = animatedVisibilityScope
)*/
) )
// }
} }
item { item {
@ -217,20 +200,13 @@ fun TodoEditorPage(
enter = fadeIn() + expandVertically(), enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically() exit = fadeOut() + shrinkVertically()
) { ) {
// with(sharedTransitionScope) {
TodoCategoryTextField( TodoCategoryTextField(
value = uiState.categoryContent, value = uiState.categoryContent,
onValueChange = { uiState.categoryContent = it }, onValueChange = { uiState.categoryContent = it },
isError = uiState.isErrorCategory, isError = uiState.isErrorCategory,
supportingText = stringResource(uiState.categorySupportingText), supportingText = stringResource(uiState.categorySupportingText),
modifier = Modifier modifier = Modifier.fillMaxWidth()
.fillMaxWidth()
/*.sharedBounds(
sharedContentState = rememberSharedContentState("${Constants.KEY_TODO_CATEGORY_TRANSITION}_${toDo?.id}"),
animatedVisibilityScope = animatedVisibilityScope
)*/
) )
// }
} }
} }

View file

@ -13,10 +13,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Add import androidx.compose.material.icons.outlined.Add
import androidx.compose.material.icons.outlined.Delete import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.SmallExtendedFloatingActionButton
import androidx.compose.material3.Text
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
import androidx.compose.material3.animateFloatingActionButton import androidx.compose.material3.animateFloatingActionButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -37,6 +34,7 @@ import cn.super12138.todo.constants.Constants
import cn.super12138.todo.logic.database.TodoEntity import cn.super12138.todo.logic.database.TodoEntity
import cn.super12138.todo.logic.datastore.DataStoreManager import cn.super12138.todo.logic.datastore.DataStoreManager
import cn.super12138.todo.ui.components.ConfirmDialog import cn.super12138.todo.ui.components.ConfirmDialog
import cn.super12138.todo.ui.components.TodoFloatingActionButton
import cn.super12138.todo.ui.pages.main.components.TodoTopAppBar import cn.super12138.todo.ui.pages.main.components.TodoTopAppBar
import cn.super12138.todo.ui.viewmodels.MainViewModel import cn.super12138.todo.ui.viewmodels.MainViewModel
@ -84,14 +82,9 @@ fun MainPage(
}, },
floatingActionButton = { floatingActionButton = {
with(sharedTransitionScope) { with(sharedTransitionScope) {
SmallExtendedFloatingActionButton( TodoFloatingActionButton(
text = { Text(stringResource(R.string.action_add_task)) }, text = stringResource(R.string.action_add_task),
icon = { icon = Icons.Outlined.Add,
Icon(
imageVector = Icons.Outlined.Add,
contentDescription = null
)
},
expanded = expandedFab, expanded = expandedFab,
onClick = { toTodoEditPage(null) }, onClick = { toTodoEditPage(null) },
modifier = Modifier modifier = Modifier

View file

@ -34,8 +34,8 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntOffset
import cn.super12138.todo.R import cn.super12138.todo.R
import cn.super12138.todo.logic.datastore.DataStoreManager import cn.super12138.todo.logic.datastore.DataStoreManager
import cn.super12138.todo.ui.components.AnimatedExtendedFloatingActionButton
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
import cn.super12138.todo.ui.components.TodoFloatingActionButton
import cn.super12138.todo.ui.pages.settings.components.category.CategoryItem import cn.super12138.todo.ui.pages.settings.components.category.CategoryItem
import cn.super12138.todo.ui.pages.settings.components.category.CategoryPromptDialog import cn.super12138.todo.ui.pages.settings.components.category.CategoryPromptDialog
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -65,7 +65,7 @@ fun SettingsDataCategory(
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
snackbarHost = { SnackbarHost(snackbarHostState) }, snackbarHost = { SnackbarHost(snackbarHostState) },
floatingActionButton = { floatingActionButton = {
AnimatedExtendedFloatingActionButton( TodoFloatingActionButton(
icon = Icons.Outlined.Add, icon = Icons.Outlined.Add,
text = stringResource(R.string.action_add_category), text = stringResource(R.string.action_add_category),
expanded = isExpanded, expanded = isExpanded,
@ -81,7 +81,7 @@ fun SettingsDataCategory(
state = listState, state = listState,
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(innerPadding) .padding(innerPadding) //TODO: 可能需要清除屏幕边距
) { ) {
if (categories.isEmpty()) { if (categories.isEmpty()) {
item { item {