Compose Weekly #3:Bloom

907 阅读2分钟

本文同步发表于我的微信公众号,微信搜索 OpenCV or Android 即可关注。

前言

第三周挑战赛是速度比拼,按照官方发出的设计图最快完成且符合所有设计规范者胜出。不仅要做得快,还要做得好,奖品自然不会少。这一期的奖品是:Google Pixel 5。深知干不过业界大佬们,花个半天纯当练手完成题目。

设计图

设计风格

设计风格

界面导航

界面导航

界面标注

界面标注

完整设计图:github.com/android/and…

知识点

  • 主题:自定义主题
  • 列表: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() }
        }
    }
}

效果

Welcome

Welcome

Login

Login

Home

Home

源码

github.com/onlyloveyd/…