文章目录
一、调整界面
首先,新建名为 BasicComposeTest 的项目,项目类型选择为 Empty Compose Project,创建后初始代码如下:
package com.bignerdranch.android.basiccomposetest
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.bignerdranch.android.basiccomposetest.ui.theme.BasicComposeTestTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BasicComposeTestTheme {
// A surface container using the 'background' color from the theme
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
Greeting("Android")
}
}
}
}
}
@Composable
fun Greeting(name: String) {
Text(text = "Hello $name!")
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
BasicComposeTestTheme {
Greeting("Android")
}
}
将 Text 用 Surface() 封装后,代码如下:
@Composable
fun Greeting(name: String) {
Surface(color = MaterialTheme.colors.primary) {
Text(text = "Hello $name!")
}
}
效果如下:
给文本设置 padding,代码如下:
@Composable
fun Greeting(name: String) {
Surface(color = MaterialTheme.colors.primary) {
Text(text = "Hello $name!", modifier = Modifier.padding(24.dp))
}
}
效果如下:
二、重复使用@Composable
package com.bignerdranch.android.basiccomposetest
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.bignerdranch.android.basiccomposetest.ui.theme.BasicComposeTestTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BasicComposeTestTheme {
MyApp()
}
}
}
}
@Composable
private fun MyApp() {
Surface(
color = MaterialTheme.colors.background
) {
Greeting("Android")
}
}
@Composable
fun Greeting(name: String) {
Surface(color = MaterialTheme.colors.primary) {
Text(text = "Hello $name!", modifier = Modifier.padding(24.dp))
}
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
BasicComposeTestTheme {
MyApp()
}
}
三、创建 Row 和 Column
@Composable
fun Greeting(name: String) {
Surface(color = MaterialTheme.colors.primary) {
Column(modifier = Modifier.padding(24.dp)) {
Text(text = "Hello,")
Text(text = name)
}
}
}
使用 for 循环,代码如下:
@Composable
fun MyApp(names: List<String> = listOf("World", "Compose")) {
Column {
for (name in names) {
Greeting(name = name)
}
}
}
设置 padding 和 fillMaxWidth,代码如下:
@Composable
fun MyApp(names: List<String> = listOf("World", "Compose")) {
Column(modifier = Modifier.padding(vertical = 4.dp)) {
for (name in names) {
Greeting(name = name)
}
}
}
@Composable
private fun Greeting(name: String) {
Surface(
color = MaterialTheme.colors.primary,
modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Column(modifier = Modifier.fillMaxWidth().padding(24.dp)) {
Text(text = "Hello, ")
Text(text = name)
}
}
}
效果如下:
用 weight 修饰符会让元素填满所有可用空间,使其“具有弹性”,也就是会推开其他没有权重的元素(即“无弹性”元素)。该修饰符还会使 fillMaxWidth 修饰符变得多余。代码如下:
@Composable
private fun Greeting(name: String) {
Surface(
color = MaterialTheme.colors.primary,
modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Row(modifier = Modifier.padding(24.dp)) {
Column(
modifier = Modifier.weight(1f)
) {
Text(text = "Hello, ")
Text(text = name)
}
OutlinedButton(onClick = { /*TODO*/ }) {
Text(text = "Show less")
}
}
}
}
效果如下:
四、Compose 的 state
通过 remember 和 mutableStateOf,可以使用 state,代码如下:
@Composable
private fun Greeting(name: String) {
val expanded = remember { mutableStateOf(false) }
val extraPadding = if (expanded.value) 48.dp else 0.dp
Surface(
color = MaterialTheme.colors.primary,
modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Row(modifier = Modifier.padding(24.dp)) {
Column(modifier = Modifier
.weight(1f)
.padding(bottom = extraPadding)
) {
Text(text = "Hello, ")
Text(text = name)
}
OutlinedButton(
onClick = { expanded.value = !expanded.value }
) {
Text(if (expanded.value) "Show less" else "Show more")
}
}
}
}
效果如下:
五、state 提升
state 提升前,代码如下:
@Composable
fun OnBoardingScreen() {
var shouldShowOnBoarding by remember { mutableStateOf(true) }
Surface {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Welcome to the Basics Test!")
Button(
modifier = Modifier.padding(vertical = 24.dp),
onClick = { shouldShowOnBoarding = false }
) {
Text("Continue")
}
}
}
}
@Preview(showBackground = true, widthDp = 320, heightDp = 320)
@Composable
fun OnBoardingPreview() {
BasicComposeTestTheme {
OnBoardingScreen()
}
}
预览效果如下:
state 提升到上层函数后,代码如下:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BasicComposeTestTheme {
MyApp()
}
}
}
}
@Composable
fun MyApp() {
var shouldShowOnBoarding by remember { mutableStateOf(true) }
if (shouldShowOnBoarding)
OnBoardingScreen { shouldShowOnBoarding = false }
else
Greetings()
}
@Composable
fun Greetings(names: List<String> = listOf("World", "Compose")) {
Column(modifier = Modifier.padding(vertical = 4.dp)) {
for (name in names) {
Greeting(name = name)
}
}
}
@Composable
private fun Greeting(name: String) {
val expanded = remember { mutableStateOf(false) }
val extraPadding = if (expanded.value) 48.dp else 0.dp
Surface(
color = MaterialTheme.colors.primary,
modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Row(modifier = Modifier.padding(24.dp)) {
Column(
modifier = Modifier
.weight(1f)
.padding(bottom = extraPadding)
) {
Text(text = "Hello, ")
Text(text = name)
}
OutlinedButton(onClick = { expanded.value = !expanded.value }) {
Text(if (expanded.value) "Show less" else "Show more")
}
}
}
}
@Preview(showBackground = true, widthDp = 320)
@Composable
fun DefaultPreview() {
BasicComposeTestTheme {
MyApp()
}
}
@Composable
fun OnBoardingScreen(onContinueClicked: () -> Unit) {
Surface {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Welcome to the Basics Test!")
Button(
modifier = Modifier.padding(vertical = 24.dp),
onClick = onContinueClicked,
) {
Text("Continue")
}
}
}
}
@Preview(showBackground = true, widthDp = 320, heightDp = 320)
@Composable
fun OnBoardingPreview() {
BasicComposeTestTheme {
OnBoardingScreen(onContinueClicked = {})
}
}
预览后,实现了布局切换,效果如下:
六、用 LazyColumn() 渲染滚动列表
到目前为止,已经在 Column 中显示了两条问候语。如果要千上万条问候语,就要用LazyColumn 和 LazyRow。他们相当于 Android View 中的 RecyclerView(比后者性能更优),用于显示可滚动列,他们只会渲染屏幕上可见的内容(而不是全部列表数据),从而提升性能。代码如下:
@Composable
fun Greetings(names: List<String> = List(1000) { "$it" }) {
LazyColumn(modifier = Modifier.padding(vertical = 4.dp)) {
items(items = names) { name ->
Greeting(name = name)
}
}
}
预览后,列表可滚动,效果如下:
七、用 rememberSaveable 保留 state
用 remember 的话,每次配置更改(如旋转屏幕)、进程终止时都会重置,因为其只在 @Composable 被 composition 时起作用,因为旋转屏幕后整个 Activity 都丢失了,所有 state 也就都丢失了。
而用 rememberSaveable 的话,会让配置更改、进程终止时保留 state,代码如下:
@Composable
fun MyApp() {
var shouldShowOnBoarding by rememberSaveable { mutableStateOf(true) }
if (shouldShowOnBoarding)
OnBoardingScreen { shouldShowOnBoarding = false }
else
Greetings()
}
运行后,旋转屏幕时,state 仍被保留,效果如下:
八、用 animateDpAsState 添加动画
代码如下:
@Composable
private fun Greeting(name: String) {
var expanded by remember { mutableStateOf(false) }
val extraPadding by animateDpAsState(
if (expanded) 48.dp else 0.dp,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
)
Surface(
color = MaterialTheme.colors.primary,
modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Row(modifier = Modifier.padding(24.dp)) {
Column(
modifier = Modifier
.weight(1f)
.padding(bottom = extraPadding.coerceAtLeast(0.dp))
) {
Text(text = "Hello, ")
Text(text = name)
}
OutlinedButton(
onClick = { expanded = !expanded }
) {
Text(if (expanded) "Show less" else "Show more")
}
}
}
}
运行后,效果如下:
九、样式、主题
可以为字体设置样式,代码如下:
@Composable
private fun Greeting(name: String) {
var expanded by remember { mutableStateOf(false) }
val extraPadding by animateDpAsState(
if (expanded) 48.dp else 0.dp,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
)
Surface(
color = MaterialTheme.colors.primary,
modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Row(modifier = Modifier.padding(24.dp)) {
Column(
modifier = Modifier
.weight(1f)
.padding(bottom = extraPadding.coerceAtLeast(0.dp))
) {
Text(text = "Hello, ")
Text(text = name, style = MaterialTheme.typography.h4.copy(
fontWeight = FontWeight.ExtraBold
))
}
OutlinedButton(
onClick = { expanded = !expanded }
) {
Text(if (expanded) "Show less" else "Show more")
}
}
}
}
字体样式效果如下:
在 Color.kt 中添加颜色,代码如下:
val Navy = Color(0xFF073042)
val Blue = Color(0xFF4285F4)
val LightBlue = Color(0xFFD7EFFE)
val Chartreuse = Color(0xFFEFF7CF)
在 Theme.kt 中使用颜色,代码如下:
private val DarkColorPalette = darkColors(
surface = Blue,
onSurface = Navy,
primary = Navy,
onPrimary = Chartreuse,
)
private val LightColorPalette = lightColors(
primary = LightBlue,
onPrimary = Navy,
surface = Blue,
onSurface = Color.White,
}
运行后,效果如下:
十、用 IconButton 替换 Button
IconButton(onClick = { expanded = !expanded }) {
Icon(
imageVector = if (expanded) Icons.Filled.ExpandLess else Icons.Filled.ExpandMore,
contentDescription = if (expanded) {
stringResource(R.string.show_less)
} else {
stringResource(R.string.show_more)
}
)
}
效果如下:
运行后,完整效果如下: