Compose 中的基本布局
- 官方文档:
https://developer.android.google.cn/codelabs/jetpack-compose-layouts?hl=zh-cn#0
演示
kotlin
@Composable
fun SearchBar(
modifier: Modifier = Modifier
) {
TextField(
value = "",
onValueChange = {},
modifier = modifier
.fillMaxWidth()
.heightIn(min = 56.dp),
leadingIcon = {
Icon(
imageVector = Icons.Default.Search,
contentDescription = null
)
},
placeholder = {
Text(stringResource(R.string.placeholder_search))
},
colors = TextFieldDefaults.colors(
unfocusedContainerColor = MaterialTheme.colorScheme.surface,
focusedContainerColor = MaterialTheme.colorScheme.surface
),
)
}
kotlin
@Composable
fun AlignYourBodyElement(
@DrawableRes drawable: Int,
@StringRes text: Int,
modifier: Modifier = Modifier
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
) {
Image(
painter = painterResource(drawable),
contentDescription = null,
modifier = Modifier
.size(88.dp)
.clip(CircleShape),
contentScale = ContentScale.Crop,
)
Text(
text = stringResource(text),
modifier = Modifier.paddingFromBaseline(top = 24.dp, bottom = 8.dp),
)
}
}
class AlignYourBodyData(
@DrawableRes val drawable: Int,
@StringRes val text: Int,
)
@Composable
fun AlignYourBodyRow(
alignYourBodyData: List<AlignYourBodyData>,
modifier: Modifier = Modifier
) {
LazyRow(
horizontalArrangement = Arrangement.spacedBy(8.dp),
contentPadding = PaddingValues(8.dp),
modifier = modifier
) {
items(alignYourBodyData) { item ->
AlignYourBodyElement(item.drawable, item.text)
}
}
}
kotlin
@Composable
fun FavoriteCollectionCard(
@DrawableRes drawable: Int,
@StringRes text: Int,
modifier: Modifier = Modifier
) {
Surface(
shape = MaterialTheme.shapes.medium,
modifier = modifier
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.width(255.dp)
) {
Image(
painter = painterResource(drawable),
contentDescription = null,
modifier = Modifier.size(80.dp),
contentScale = ContentScale.Crop,
)
Text(text = stringResource(text), modifier = Modifier.padding(start = 8.dp))
}
}
}
class FavoriteCollectionsData(
@DrawableRes val drawable: Int,
@StringRes val text: Int,
)
@Composable
fun FavoriteCollectionsGrid(
favoriteCollectionsData: List<FavoriteCollectionsData>,
modifier: Modifier = Modifier
) {
LazyHorizontalGrid(
rows = GridCells.Fixed(2),
modifier = modifier.height(208.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
contentPadding = PaddingValues(16.dp),
) {
items(favoriteCollectionsData) { item ->
FavoriteCollectionCard(item.drawable, item.text, modifier = Modifier.height(80.dp))
}
}
}
kotlin
@Composable
fun HomeSection(
@StringRes title: Int,
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Column(modifier) {
Text(
stringResource(title),
style = MaterialTheme.typography.titleMedium,
modifier = Modifier
.paddingFromBaseline(top = 40.dp, bottom = 16.dp)
.padding(horizontal = 16.dp)
)
content()
}
}
@Composable
fun HomeScreen(modifier: Modifier = Modifier) {
Column(
modifier = modifier.verticalScroll(rememberScrollState()),
) {
Spacer(Modifier.height(16.dp))
SearchBar(Modifier.padding(horizontal = 16.dp))
HomeSection(title = R.string.align_your_body) {
AlignYourBodyRow(
alignYourBodyData = listOf(
AlignYourBodyData(
drawable = R.drawable.ab1_inversions,
text = R.string.ab1_inversions
),
AlignYourBodyData(
drawable = R.drawable.ab1_inversions,
text = R.string.ab1_inversions
),
AlignYourBodyData(
drawable = R.drawable.ab1_inversions,
text = R.string.ab1_inversions
),
AlignYourBodyData(
drawable = R.drawable.ab1_inversions,
text = R.string.ab1_inversions
),
AlignYourBodyData(
drawable = R.drawable.ab1_inversions,
text = R.string.ab1_inversions
),
),
)
}
HomeSection(title = R.string.favorite_collections) {
FavoriteCollectionsGrid(
favoriteCollectionsData = listOf(
FavoriteCollectionsData(
drawable = R.drawable.fc2_nature_meditations,
text = R.string.fc2_nature_meditations
),
FavoriteCollectionsData(
drawable = R.drawable.fc2_nature_meditations,
text = R.string.fc2_nature_meditations
),
FavoriteCollectionsData(
drawable = R.drawable.fc2_nature_meditations,
text = R.string.fc2_nature_meditations
),
FavoriteCollectionsData(
drawable = R.drawable.fc2_nature_meditations,
text = R.string.fc2_nature_meditations
),
FavoriteCollectionsData(
drawable = R.drawable.fc2_nature_meditations,
text = R.string.fc2_nature_meditations
),
),
)
}
Spacer(Modifier.height(16.dp))
}
}
kotlin
@Composable
fun SootheBottomNavigation(modifier: Modifier = Modifier) {
NavigationBar(
modifier = modifier
) {
NavigationBarItem(
icon = {
Icon(
imageVector = Icons.Default.Spa,
contentDescription = null
)
},
label = {
Text(
text = stringResource(R.string.bottom_navigation_home)
)
},
selected = true,
onClick = {}
)
NavigationBarItem(
icon = {
Icon(
imageVector = Icons.Default.AccountCircle,
contentDescription = null
)
},
label = {
Text(
text = stringResource(R.string.bottom_navigation_profile)
)
},
selected = false,
onClick = {}
)
}
}
@Composable
fun SootheNavigationRail(modifier: Modifier = Modifier) {
NavigationRail(
modifier = modifier.padding(start = 8.dp, end = 8.dp)
) {
Column(
modifier = modifier.fillMaxHeight(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
NavigationRailItem(
icon = {
Icon(
imageVector = Icons.Default.Spa,
contentDescription = null
)
},
label = {
Text(
text = stringResource(R.string.bottom_navigation_home)
)
},
selected = true,
onClick = {}
)
Spacer(modifier = Modifier.height(16.dp))
NavigationRailItem(
icon = {
Icon(
imageVector = Icons.Default.AccountCircle,
contentDescription = null
)
},
label = {
Text(
text = stringResource(R.string.bottom_navigation_profile)
)
},
selected = false,
onClick = {}
)
}
}
}
kotlin
@Composable
fun MyApp(windowSize: WindowSizeClass) {
when (windowSize.widthSizeClass) {
WindowWidthSizeClass.Compact -> {
Column {
HomeScreen(Modifier.weight(1f))
SootheBottomNavigation()
}
}
WindowWidthSizeClass.Expanded -> {
Row {
SootheNavigationRail()
HomeScreen()
}
}
}
}
1、8dp 网格
-
8dp 网格是 Android 界面设计中的一套基础对齐系统,它规定所有UI元素的尺寸和间距都应该是 8dp 的整数倍
-
所有组件的大小、边距、内边距和位置,都基于 8dp 这个基本单位。例如,常见的按钮高度为 48dp,应用栏高度为 56dp
2、标准的 Compose 组件
kotlin
@Composable
fun AlignYourBodyElement(
modifier: Modifier = Modifier
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
) {
Image(
painter = painterResource(R.drawable.ab1_inversions),
contentDescription = null,
modifier = Modifier
.size(88.dp)
.clip(CircleShape),
contentScale = ContentScale.Crop,
)
Text(
text = stringResource(R.string.ab1_inversions),
modifier = Modifier.paddingFromBaseline(top = 24.dp, bottom = 8.dp),
)
}
}
@Preview(showBackground = true)
@Composable
fun AlignYourBodyElementPreview() {
AlignYourBodyElement()
}
kotlin
@Composable
fun AlignYourBodyElement(
@DrawableRes drawable: Int,
@StringRes text: Int,
modifier: Modifier = Modifier
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
) {
Image(
painter = painterResource(drawable),
contentDescription = null,
modifier = Modifier
.size(88.dp)
.clip(CircleShape),
contentScale = ContentScale.Crop,
)
Text(
text = stringResource(text),
modifier = Modifier.paddingFromBaseline(top = 24.dp, bottom = 8.dp),
)
}
}
@Preview(showBackground = true)
@Composable
fun AlignYourBodyElementPreview() {
AlignYourBodyElement(
drawable = R.drawable.ab1_inversions,
text = R.string.ab1_inversions,
modifier = Modifier.padding(8.dp)
)
}
3、painterResource 函数与 stringResource 函数
(1)基本介绍
- painterResource 和 stringResource 都是 Jetpack Compose 中用来加载资源的可组合函数
- painterResource 函数负责加载图片
kotlin
@Composable
fun painterResource(@DrawableRes id: Int): Painter {
val context = LocalContext.current
val res = resources()
val value = remember { TypedValue() }
res.getValue(id, value, true)
val path = value.string
// Assume .xml suffix implies loading a VectorDrawable resource
return if (path?.endsWith(".xml") == true) {
val imageVector = loadVectorResource(context.theme, res, id, value.changingConfigurations)
rememberVectorPainter(imageVector)
} else {
// Otherwise load the bitmap resource
val imageBitmap = remember(path, id, context.theme) {
loadImageBitmapResource(path, res, id)
}
BitmapPainter(imageBitmap)
}
}
- stringResource 函数负责加载文字
kotlin
@Composable
@ReadOnlyComposable
fun stringResource(@StringRes id: Int): String {
val resources = resources()
return resources.getString(id)
}
- painterResource 函数和 stringResource 函数是对传统 Android 原生 API 的封装,让它们在 Compose 的世界里使用起来更方便、更符合声明式 UI 的风格
(2)演示
kotlin
Image(
painter = painterResource(id = R.drawable.dog),
contentDescription = stringResource(id = R.string.dog_content_description)
)
4、手动添加滚动行为
(1)基本介绍
-
LazyRow 和 LazyHorizontalGrid 等延迟布局会自动添加滚动行为
-
在列表中有许多元素或需要加载大型数据集时,需要使用延迟布局,因此一次发出所有项不仅会降低性能,还会拖慢应用的运行速度
-
如果列表中的元素数量有限,也可以选择使用简单的 Column 或 Row,然后手动添加滚动行为
-
可以使用 verticalScroll 或 horizontalScroll 修饰符。这些修饰符需要 ScrollState,后者包含当前的滚动状态,可用于从外部修改滚动状态
-
使用 rememberScrollState 创建一个持久的 ScrollState 实例
kotlin
fun Modifier.verticalScroll(
state: ScrollState,
enabled: Boolean = true,
flingBehavior: FlingBehavior? = null,
reverseScrolling: Boolean = false
) = scroll(
state = state,
isScrollable = enabled,
reverseScrolling = reverseScrolling,
flingBehavior = flingBehavior,
isVertical = true
)
fun Modifier.horizontalScroll(
state: ScrollState,
enabled: Boolean = true,
flingBehavior: FlingBehavior? = null,
reverseScrolling: Boolean = false
) = scroll(
state = state,
isScrollable = enabled,
reverseScrolling = reverseScrolling,
flingBehavior = flingBehavior,
isVertical = false
)
(2)演示
kotlin
Column(
modifier = Modifier.verticalScroll(rememberScrollState()),
) {
...
}
5、导航栏
kotlin
@Composable
fun SootheBottomNavigation(modifier: Modifier = Modifier) {
NavigationBar(
modifier = modifier
) {
NavigationBarItem(
icon = {
Icon(
imageVector = Icons.Default.Spa,
contentDescription = null
)
},
label = {
Text(
text = stringResource(R.string.bottom_navigation_home)
)
},
selected = true,
onClick = {}
)
NavigationBarItem(
icon = {
Icon(
imageVector = Icons.Default.AccountCircle,
contentDescription = null
)
},
label = {
Text(
text = stringResource(R.string.bottom_navigation_profile)
)
},
selected = false,
onClick = {}
)
}
}
- 适配横屏模式下的显示效果
kotlin
@Composable
fun SootheNavigationRail(modifier: Modifier = Modifier) {
NavigationRail(
modifier = modifier.padding(start = 8.dp, end = 8.dp)
) {
Column(
modifier = modifier.fillMaxHeight(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
NavigationRailItem(
icon = {
Icon(
imageVector = Icons.Default.Spa,
contentDescription = null
)
},
label = {
Text(
text = stringResource(R.string.bottom_navigation_home)
)
},
selected = true,
onClick = {}
)
Spacer(modifier = Modifier.height(16.dp))
NavigationRailItem(
icon = {
Icon(
imageVector = Icons.Default.AccountCircle,
contentDescription = null
)
},
label = {
Text(
text = stringResource(R.string.bottom_navigation_profile)
)
},
selected = false,
onClick = {}
)
}
}
}