目录
- 配置颜色、字体与形状
- Welcome Page
- Login Page
- Home Page
- 主题
- CompositionLocal
配置颜色、字体与形状
-ui.theme.Color.kt
val pink100 = Color(0xFFFFF1F1)
val pink900 = Color(0xFF3F2C2C)
val white = Color(0xFFFFFFFF)
val white850 = Color(0xD9FFFFFF)
val gray = Color(0xFF232323)
- ui.theme.Type.kt
先将Nunito Sans字体家族放入 res/font,再根据设计稿写代码
val nunitoSansFamily = FontFamily(
Font(R.font.nunitosans_light, FontWeight.Light),
Font(R.font.nunitosans_semibold, FontWeight.SemiBold),
Font(R.font.nunitosans_bold, FontWeight.Bold)
)
val h1 = TextStyle(
fontSize = 18.sp,
fontFamily = nunitoSansFamily,
fontWeight = FontWeight.Bold
)
val h2 = TextStyle(
fontSize = 14.sp,
letterSpacing = 0.15.sp,
fontFamily = nunitoSansFamily,
fontWeight = FontWeight.Bold
)
val subtitle1 = TextStyle(
fontSize = 16.sp,
fontFamily = nunitoSansFamily,
fontWeight = FontWeight.Light
)
val body1 = TextStyle(
fontSize = 14.sp,
fontFamily = nunitoSansFamily,
fontWeight = FontWeight.Light
)
val body2 = TextStyle(
fontSize = 12.sp,
fontFamily = nunitoSansFamily,
fontWeight = FontWeight.Light
)
val button = TextStyle(
fontSize = 14.sp,
letterSpacing = 1.sp,
fontFamily = nunitoSansFamily,
fontWeight = FontWeight.SemiBold,
color = white
)
val caption = TextStyle(
fontSize = 12.sp,
fontFamily = nunitoSansFamily,
fontWeight = FontWeight.SemiBold
)
- ui.theme/Shape.kt
Welcome Page
@Preview(showBackground = true)
@Composable
fun WelcomeTitle() {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
Image(
painter = rememberVectorPainter(image = ImageVector.vectorResource(id = R.drawable.ic_light_logo)),
contentDescription = "weclome_logo",
modifier = Modifier
.wrapContentWidth()
.height(32.dp)
)
Box(
modifier = Modifier
.fillMaxWidth()
.height(32.dp),
contentAlignment = Alignment.BottomCenter
) {
Text(
text = "Beautiful home garden solutions",
textAlign = TextAlign.Center,
style = subtitle1,
color = gray
)
}
}
}
@Preview(showBackground = true)
@Composable
fun WelcomeButtons() {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
){
Button(
onClick = { },
modifier = Modifier
.height(48.dp)
.padding(horizontal = 16.dp)
.fillMaxWidth()
.clip(medium),
colors = ButtonDefaults.buttonColors(backgroundColor = pink900)
) {
Text(
text = "Create account",
style = button,
color = white
)
}
Spacer(modifier = Modifier.height(24.dp))
TextButton(
onClick = { },
) {
Text(
text = "Log in",
style = button,
color = pink900,
)
}
}
}
@Preview(showBackground = true)
@Composable
fun LeafImage() {
Image(
painter = rememberVectorPainter(image = ImageVector.vectorResource(id = R.drawable.ic_light_welcome_illos)),
contentDescription = "weclome_illos",
modifier = Modifier
.wrapContentSize()
.padding(start = 88.dp)
)
}
@Preview(showBackground = true)
@Composable
fun WelcomeContent() {
Column(modifier = Modifier
.fillMaxSize()
) {
Spacer(Modifier.height(72.dp))
LeafImage()
Spacer(modifier = Modifier.height(48.dp))
WelcomeTitle()
Spacer(modifier = Modifier.height(40.dp))
WelcomeButtons()
}
}
@Composable
fun WelcomePage() {
Box(
modifier = Modifier
.fillMaxSize()
.background(pink100)
) {
Image(
painter = rememberVectorPainter(image = ImageVector.vectorResource(id = R.drawable.ic_light_welcome_bg)),
contentDescription = "weclome_bg",
modifier = Modifier.fillMaxSize()
)
WelcomeContent()
}
}
Login Page
@Preview(showBackground = true)
@Composable
fun TopText() {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
var keywordPre = "By Clicking below you agree to our".split(" ")
var keywordPost = "and consent".split(" ")
for (word in keywordPre) {
Text(
text = word,
style = body2,
color = gray,
)
}
Text(
text = "Terms of Use",
style = body2,
color = gray,
textDecoration = TextDecoration.Underline
)
for (word in keywordPost) {
Text(
text = word,
style = body2,
color = gray,
)
}
}
}
@Preview(showBackground = true)
@Composable
fun BottomText() {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
Text(text = " to Our ",
style = body2,
color = gray
)
Text(text = "Privacy Policy.",
style = body2,
color = gray,
textDecoration = TextDecoration.Underline
)
}
}
@Preview(showBackground = true)
@Composable
fun HintWithUnderline() {
Column(
modifier = Modifier.paddingFromBaseline(top = 24.dp, bottom = 16.dp)
){
TopText()
BottomText()
}
}
@Preview(showBackground = true)
@Composable
fun LoginInputBox() {
Column {
OutlinedTextField(
value = "",
onValueChange = {},
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.clip(small),
placeholder = {
Text(
text = "Email address",
style = body1,
color = gray
)
}
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = "",
onValueChange = {},
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.clip(small),
placeholder = {
Text(
text = "Password(8+ Characters)",
style = body1,
color = gray
)
}
)
}
}
@Preview(showBackground = true, showSystemUi = false)
@Composable
fun LoginTitle() {
Text(
text = "Log in with email",
modifier = Modifier
.fillMaxWidth()
.paddingFromBaseline(top = 184.dp, bottom = 16.dp),
style = h1,
color = gray,
textAlign = TextAlign.Center
)
}
@Preview(showBackground = true)
@Composable
fun LoginButton() {
Button(
onClick = { },
modifier = Modifier
.height(48.dp)
.fillMaxWidth()
.clip(medium),
colors = ButtonDefaults.buttonColors(backgroundColor = pink900)
) {
Text(
text = "Log in",
style = button,
color = white
)
}
}
@Composable
fun LoginPage() {
Column(
Modifier
.fillMaxSize()
.background(white)
.padding(horizontal = 16.dp)
) {
LoginTitle()
LoginInputBox()
HintWithUnderline()
LoginButton()
}
}
@Composable
@Preview
fun LoginPageLightPreview() {
BloomTheme() {
LoginPage()
}
}
Home Page
val small = RoundedCornerShape(4.dp)
val medium = RoundedCornerShape(24.dp)
val shapes = Shapes(
small = RoundedCornerShape(4.dp),
medium = RoundedCornerShape(4.dp),
large = RoundedCornerShape(0.dp)
)
data class ImageItem(val name: String, val resId: Int)
val bloomBannerList = listOf(
ImageItem("Desert chic", R.drawable.desert_chic),
ImageItem("Tiny terrariums", R.drawable.tiny_terrariums),
ImageItem("Jungle Vibes", R.drawable.jungle_vibes)
)
val bloomInfoList = listOf(
ImageItem("Monstera", R.drawable.monstera),
ImageItem("Aglaonema", R.drawable.aglaonema),
ImageItem("Peace lily", R.drawable.peace_lily),
ImageItem("Fiddle leaf tree", R.drawable.fiddle_leaf),
ImageItem("Desert chic", R.drawable.desert_chic),
ImageItem("Tiny terrariums", R.drawable.tiny_terrariums),
ImageItem("Jungle Vibes", R.drawable.jungle_vibes)
)
val navList = listOf(
ImageItem("Home", R.drawable.ic_home),
ImageItem("Favorites", R.drawable.ic_favorite_border),
ImageItem("Profile", R.drawable.ic_account_circle),
ImageItem("Cart", R.drawable.ic_shopping_cart)
)
@Preview(showBackground = true)
@Composable
fun BloomRowBanner() {
Column {
Box(
Modifier.fillMaxWidth()
) {
Text(
text = "Browse themes",
style = h1,
color = gray,
modifier = Modifier
.fillMaxWidth()
.paddingFromBaseline(top = 32.dp)
)
}
Spacer(modifier = Modifier.height(16.dp))
LazyRow(
modifier = Modifier.height(136.dp)
) {
items(bloomBannerList.size) {
if (it != 0) {
Spacer(modifier = Modifier.width(8.dp))
}
PlantCard(bloomBannerList[it])
}
}
}
}
@Preview(showBackground = true)
@Composable
fun BloomInfoList() {
Column {
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "Design your home garden",
style = h1,
color = gray,
modifier = Modifier.paddingFromBaseline(top = 40.dp)
)
Icon(
painterResource(id = R.drawable.ic_filter_list),
"filter",
modifier = Modifier
.padding(top = 24.dp)
.size(24.dp)
)
}
Spacer(modifier = Modifier.height(16.dp))
LazyColumn(
modifier = Modifier
.fillMaxWidth(),
contentPadding = PaddingValues(bottom = 56.dp)
) {
items(bloomInfoList.size) {
if (it != 0) {
Spacer(modifier = Modifier.height(8.dp))
}
DesignCard(bloomInfoList[it])
}
}
}
}
@Preview(showBackground = true)
@Composable
fun SearchBar() {
Box {
TextField(
value = "",
onValueChange = {},
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.clip(RoundedCornerShape(4.dp))
.border(BorderStroke(1.dp, Color.Black)),
leadingIcon = {
Icon(
painter = rememberVectorPainter(image = ImageVector.vectorResource(id = R.drawable.ic_search)),
contentDescription = "search",
modifier = Modifier.size(18.dp)
)
},
placeholder = {
Text(
text = "Search",
style = body1,
color = gray
)
},
colors = TextFieldDefaults.outlinedTextFieldColors(
backgroundColor = white,
unfocusedBorderColor = white,
focusedBorderColor = white,
),
)
}
}
@Composable
fun HomePage() {
Scaffold(
bottomBar = {
BottomBar()
}
) {
Column(
Modifier
.fillMaxSize()
.background(white)
.padding(horizontal = 16.dp)
) {
Spacer(modifier = Modifier.height(40.dp))
SearchBar()
BloomRowBanner()
BloomInfoList()
}
}
}
@Composable
fun DesignCard(plant: ImageItem) {
Row(
modifier = Modifier.fillMaxWidth()
) {
Image(
painterResource(id = plant.resId),
contentScale = ContentScale.Crop,
contentDescription = "image",
modifier = Modifier
.size(64.dp)
.clip(RoundedCornerShape(4.dp))
)
Spacer(modifier = Modifier.width(16.dp))
Column {
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Column {
Text(
text = plant.name,
style = h2,
color = gray,
modifier = Modifier.paddingFromBaseline(top = 24.dp)
)
Text(
text = "This is a description",
style = body1,
color = gray,
modifier = Modifier
)
}
Checkbox(
modifier = Modifier
.padding(top = 24.dp)
.size(24.dp),
checked = false,
onCheckedChange = {
// plant.enable = it
},
colors = CheckboxDefaults.colors(
checkmarkColor = white
)
)
}
Divider(color = gray, modifier = Modifier.padding(top = 16.dp), thickness = 0.5.dp)
}
}
}
@Composable
fun PlantCard(plant: ImageItem) {
Card(
modifier = Modifier
.size(136.dp)
.clip(RoundedCornerShape(4.dp))
) {
Column {
Image(
painterResource(id = plant.resId),
contentScale = ContentScale.Crop,
contentDescription = "image",
modifier = Modifier
.fillMaxWidth()
.height(96.dp)
)
Box(
Modifier
.fillMaxWidth()
.padding(start = 16.dp)
) {
Text(
text = plant.name,
style = h2,
color = gray,
modifier = Modifier
.fillMaxWidth()
.paddingFromBaseline(top = 24.dp, bottom = 16.dp)
)
}
}
}
}
@Composable
fun BottomBar() {
BottomNavigation(
elevation = 16.dp,
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.background(pink100)
) {
navList.forEach {
BottomNavigationItem(
onClick = {},
icon = {
Icon(
painterResource(id = it.resId),
contentDescription = null,
modifier = Modifier.size(24.dp),
)
},
label = {
Text(
it.name,
style = caption,
color = gray,
)
},
selected = ("Home" == it.name)
)
}
}
}
@Preview(showBackground = true)
@Composable
fun BottomBarPreview() {
BloomTheme() {
BottomBar()
}
}
@Preview(showBackground = true)
@Composable
fun DesignCardPreview() {
BloomTheme() {
DesignCard(bloomInfoList[0])
}
}
@Preview(showBackground = true)
@Composable
fun PlantCardPreview() {
BloomTheme() {
PlantCard(bloomBannerList[0])
}
}
@Preview(showBackground = true)
@Composable
fun HomePageLightPreview() {
BloomTheme() {
HomePage()
}
}
主题
private val BloomLightColorPaltte = lightColors(
primary = pink100,
secondary = pink900,
background = white,
surface = white850,
onPrimary = gray,
onSecondary = white,
onBackground = gray,
onSurface = gray,
)
private val BloomDarkColorPaltte = darkColors(
primary = green900,
secondary = green300,
background = gray,
surface = white150,
onPrimary = white,
onSecondary = gray,
onBackground = white,
onSurface = white850
)
open class WelcomeAssets private constructor(
var background: Int,
var illos: Int,
var logo: Int
) {
object LightWelcomeAssets : WelcomeAssets(
background = R.drawable.ic_light_welcome_bg,
illos = R.drawable.ic_light_welcome_illos,
logo = R.drawable.ic_light_logo
)
object DarkWelcomeAssets : WelcomeAssets(
background = R.drawable.ic_dark_welcome_bg,
illos = R.drawable.ic_dark_welcome_illos,
logo = R.drawable.ic_dark_logo
)
}
internal var LocalWelcomeAssets = staticCompositionLocalOf { WelcomeAssets.LightWelcomeAssets as WelcomeAssets }
val MaterialTheme.welcomeAssets
@Composable
@ReadOnlyComposable
get() = LocalWelcomeAssets.current
enum class BloomTheme {
LIGHT, DARK
}
@Composable
fun BloomTheme(theme: BloomTheme = BloomTheme.LIGHT, content: @Composable() () -> Unit) {
CompositionLocalProvider(
LocalWelcomeAssets provides if (theme == BloomTheme.DARK) WelcomeAssets.DarkWelcomeAssets else WelcomeAssets.LightWelcomeAssets,
) {
MaterialTheme(
colors = if (theme == BloomTheme.DARK) BloomDarkColorPaltte else BloomLightColorPaltte,
typography = bloomTypoGraphy,
shapes = shapes,
content = content
)
}
}
CompositionLocal
CompositionLocal 是 Jetpack Compose 中的一种数据传递方式。它可以在组合组件之间传递可变数据,而无需通过 props 或 state 管理器来传递数据。这个特性比传统的数据传递方式更为高效和方便。
例如,我们可以通过 CompositionLocal 在应用程序的不同部分中传递数据,如主题、语言环境、字体等。这个特性可以让我们在组合应用程序中更轻松地使用全局状态,而不必每次都传递数据。
此外,CompositionLocal 还可以用于实现本地化和自定义主题等功能的高效性。因此,它是 Jetpack Compose 中非常重要的一个组成部分。
- 如果CompositonLocal提供得知发生更改的可能性很小或永远无法改变,利用staticCompositionLocal能显著提升性能
总结
- 所有视图组件建议用顶级函数声明
随笔:
- 简历 遇到什么问题,用了什么方法,如何进行优化
《Jetpack Compose从入门到实战》第一章 全新的 Android UI 框架
《Jetpack Compose从入门到实战》 第二章 了解常用UI组件