本文同步发表于我的微信公众号,在微信搜索
OpenCV or Android
即可关注。
前言
第三周挑战赛是速度比拼,按照官方发出的设计图最快完成且符合所有设计规范者胜出。不仅要做得快,还要做得好,奖品自然不会少。这一期的奖品是:Google Pixel 5。深知干不过业界大佬们,花个半天纯当练手完成题目。
设计图
设计风格
界面导航
界面标注
完整设计图:https://github.com/android/android-dev-challenge-compose/blob/assets/Bloom.zip
知识点
- 主题:自定义主题
- 列表:LazyColumn、LazyRow
- 文字:文本输入框、风格化文本、自定义字体
- 导航:基础使用、底部导航栏集成
关键实现
定义主题
界面布局中使用主题元素配置color、shape、typography等内容。localImages、localElevations为自定义内容。
@Composable
fun BloomTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable() () -> Unit) {
val colors = if (darkTheme) {
DarkColorPalette
} else {
LightColorPalette
}
val images = if (darkTheme) DarkImages else LightImages
val elevations = AllElevations
CompositionLocalProvider(
localImages provides images,
localElevations provides elevations
) {
MaterialTheme(
colors = colors,
typography = typography,
shapes = shapes,
content = content
)
}
}
object BloomTheme {
val colors: Colors
@Composable
get() = MaterialTheme.colors
val typography: Typography
@Composable
get() = MaterialTheme.typography
val shapes: Shapes
@Composable
get() = MaterialTheme.shapes
val images: Images
@Composable
get() = localImages.current
val elevations: Elevations
@Composable
get() = localElevations.current
}
@Immutable
data class Images(
@DrawableRes val welcomeBackground: Int,
@DrawableRes val welcomeIllos: Int,
@DrawableRes val welcomeLogo: Int
)
internal val localImages = staticCompositionLocalOf<Images> {
error("No LocalImages specified")
}
风格化文本
val termsString = buildTermsString(
stringResource(R.string.login_terms),
listOf(
stringResource(R.string.terms),
stringResource(R.string.privancy)
)
)
Text(
text = termsString,
textAlign = TextAlign.Center,
modifier = Modifier.paddingFromBaseline(
top = 24.dp,
bottom = 16.dp
)
)
fun buildTermsString(source: String, segments: List<String>) = buildAnnotatedString {
append(source)
for (segment in segments) {
val startIndex = source.indexOf(segment)
val endIndex = startIndex + segment.length
addStyle(
style = SpanStyle(textDecoration = TextDecoration.Underline),
start = startIndex,
end = endIndex
)
}
}
带边框密码文本输入
TextField(
modifier = Modifier
.height(56.dp)
.fillMaxWidth()
.border( // 设置边框
width = 1.dp,
color = Color(0xFF9E9E9E),
shape = BloomTheme.shapes.small
),
value = password,
onValueChange = { password = it },
singleLine = true,
placeholder = {
Text(
text = stringResource(R.string.password_hint),
style = BloomTheme.typography.body1,
)
},
textStyle = BloomTheme.typography.body1,
colors = TextFieldDefaults.textFieldColors(
backgroundColor = Color.Transparent,
textColor = BloomTheme.colors.onPrimary
),
visualTransformation = PasswordVisualTransformation(), // 密码形式
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password) // 密码安全键盘
)
底部导航栏
sealed class Screen(
val route: String,
@StringRes val resourceId: Int,
val icon: ImageVector
) {
object Home : Screen("home", R.string.home, Icons.Filled.Home)
object Favorites : Screen("favorite", R.string.favorites, Icons.Filled.FavoriteBorder)
object Profile : Screen("profile", R.string.profile, Icons.Filled.AccountCircle)
object Cart : Screen("cart", R.string.cart, Icons.Filled.ShoppingCart)
}
val items = listOf(
Screen.Home,
Screen.Favorites,
Screen.Profile,
Screen.Cart
)
@Composable
fun Main() {
val navController = rememberNavController()
Scaffold(
bottomBar = {
BottomNavigation(
modifier = Modifier
.navigationBarsPadding()
.height(56.dp),
backgroundColor = MaterialTheme.colors.primary,
contentColor = MaterialTheme.colors.onPrimary,
elevation = BloomTheme.elevations.bottomNavigation
) {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.arguments?.getString(KEY_ROUTE)
items.forEach { screen ->
BottomNavigationItem(
selected = currentRoute == screen.route,
onClick = {
navController.navigate(screen.route) {
popUpTo = navController.graph.startDestination
launchSingleTop = true
}
},
icon = {
Icon(
imageVector = screen.icon,
contentDescription = stringResource(id = screen.resourceId),
modifier = Modifier.size(24.dp, 24.dp),
tint = MaterialTheme.colors.onPrimary
)
},
label = {
Text(
stringResource(id = screen.resourceId),
style = MaterialTheme.typography.caption
)
}
)
}
}
}
) {
NavHost(navController, startDestination = Screen.Home.route) {
composable(Screen.Home.route) { Home() }
composable(Screen.Favorites.route) { Favorites() }
composable(Screen.Profile.route) { Profile() }
composable(Screen.Cart.route) { Cart() }
}
}
}