(一)概述
Jetpack Compose 是 Android 官方推出的 UI 工具包,用来以声明式方式构建界面,逐步取代传统的 XML + View。简而言之就是以Kotlin DSL代码声明UI状态,Compose框架会自动渲染界面。
Kotlin
@Composable
fun Greeting(name: String) {
Text(text = "Hello $name")
}
说明:参数name 变了,UI 自动更新, 不需要 再findViewById / setText
Kotlin
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("Count: $count")
}
说明:状态驱动 UI(State-driven),count是状态,使用remember后,同一个compose方法中的count会进行缓存,状态变化时,会自动更新count状态值,进而更新UI。
(二)Compose的作用
1. 降低代码量
例如一个按钮点击,传统的xml layout等方式,需要编写xml 组件等,然后findViewById,setOnClickListener,setText等代码量大,而Compose极其简单:
Kotlin
@Composable
fun AppButton(text: String, onClick: () -> Unit) {
Button(onClick = onClick) {
Text(text)
}
}
说明:所有组件均以@Composable标记,且只能在 Composable 里调用 Composable,本质是 UI 的最小可复用单元
2. UI与状态一致
原来xml的方式容易出现UI与数据不同步等问题,而Compose UI只来源于State,不会出现UI数据不同步问题。
3. 高度可复用、可组合
Kotlin
如:Column {
AppTopBar()
Content()
AppButton()
}
UI 像搭积木,比自定义 View 更简单,比 include/layout 更灵活
4.支持即时预览
Kotlin
@Preview
@Composable
fun PreviewGreeting() {
Greeting("Compose")
}
说明:使用@Preview 不用每修改一点编译一次App, Compose支持修改组件后 即时预览UI效果。
关于Compose的作用和好处还有很多,例如,单元/UI测试, 与 Kotlin / Flow / Coroutines 天然契合等等,后面再详细介绍。
(三)Text 封装
1. Compose 特性声明
Groovy
buildFeatures {
compose = true
}
说明:在 build.gradle.kts 声明支持Compose特性
2. Text 组件封装
Kotlin
package com.leo.wechat.ui.theme
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
private val DarkColorScheme = darkColorScheme(
primary = WeChatGreen,
onPrimary = Color.Black,
secondary = WeChatGreen,
onSecondary = Color.Black,
background = WeChatDarkBackground,
onBackground = WeChatDarkTextPrimary,
surface = WeChatDarkSurface,
onSurface = WeChatDarkTextPrimary,
outline = WeChatDarkDivider,
error = WeChatDarkError,
tertiary = WeChatDarkWarning
)
private val LightColorScheme = lightColorScheme(
// 主色:Button、Checkbox...强调动作等默认背景
primary = WeChatGreen,
// primary内容颜色:Button的文字,primary背景上的Icon
onPrimary = Color.White,
// 次强调颜色:次级按钮,Toggle,Chip
secondary = WeChatGreen,
// secondary内容颜色
onSecondary = Color.White,
// 页面底色:Scaffold.containerColor,页面根布局,List背景
background = WeChatLightBackground,
// 页面文字颜色:页面标题,普通正文Text,TopBar标题
onBackground = WeChatLightTextPrimary,
// 卡片/列表项/浮层 背景色:Card,ListItem,BottomSheet,Dialog
surface = WeChatLightSurface,
// surface 上的文字/图标颜色:列表项文字,Card 内容,Dialog 内容
onSurface = WeChatLightTextPrimary,
// 分割线/边框/描边颜色:Divider,OutlinedButton,输入框边框
outline = WeChatLightDivider,
// 错误/风险 背颜色
error = WeChatLightError,
// 警告/提醒 背颜色
tertiary = WeChatLightWarning
)
/* Compose Theme 才是真正的 UI 主题 */
@Composable
fun ComposeTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = false,
content: @Composable () -> Unit,
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,// 所有颜色
typography = Typography,// 所有字体
content = content
)
}
说明:编写 APP 主题,主要用于统一各组件使用到的不同颜色和字体及深浅色模式下的切换。
Kotlin
package com.leo.wechat.ui.theme
import androidx.compose.ui.graphics.Color
// Main Color
val WeChatGreen = Color(0xFF07C160)
// LightColor Mode
val WeChatLightBackground = Color(0xFFF7F7F7)
val WeChatLightSurface = Color(0xFFFFFFFF)
val WeChatLightTextPrimary = Color(0xFF191919)
val WeChatLightTextSecondary = Color(0xFF888888)
val WeChatLightDivider = Color(0xFFE5E5E5)
val WeChatLightError = Color(0xFFF53F3F)
val WeChatLightWarning = Color(0xFFFFA940)
// DarkColor Mode
val WeChatDarkBackground = Color(0xFF111111)
val WeChatDarkSurface = Color(0xFF1C1C1E)
val WeChatDarkTextPrimary = Color(0xFFEDEDED)
val WeChatDarkTextSecondary = Color(0xFF9B9B9B)
val WeChatDarkDivider = Color(0xFF2C2C2E)
val WeChatDarkError = Color(0xFFFF6B6B)
val WeChatDarkWarning = Color(0xFFFFC069)
说明:定义应用中用到主要颜色等
Kotlin
package com.leo.wechat.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
import com.leo.wechat.R
// Set of Font Family
val Bariol = FontFamily(
Font(R.font.bariol_regular, FontWeight.Normal),
Font(R.font.bariol_bold, FontWeight.Bold),
)
// Set of Material typography styles to start with
val Typography = Typography(
// 大标题(页面标题)
titleLarge = TextStyle(
fontSize = 28.sp,
fontFamily = Bariol,
fontWeight = FontWeight.Bold,
lineHeight = (28 * 0.9).sp
),
// 中标题(Section / AppBar)
titleMedium = TextStyle(
fontSize = 24.sp,
fontFamily = Bariol,
fontWeight = FontWeight.Bold,
lineHeight = (24 * 0.9).sp
),
// 小标题(列表标题)
titleSmall = TextStyle(
fontSize = 20.sp,
fontFamily = Bariol,
fontWeight = FontWeight.Medium,
lineHeight = (20 * 0.9).sp
),
// 正文主内容
bodyLarge = TextStyle(
fontSize = 18.sp,
fontFamily = Bariol,
fontWeight = FontWeight.Normal,
lineHeight = (18 * 0.9).sp
),
// 次要正文
bodyMedium = TextStyle(
fontSize = 16.sp,
fontFamily = Bariol,
fontWeight = FontWeight.Normal,
lineHeight = (16 * 0.9).sp
),
// 辅助说明 / 时间戳
bodySmall = TextStyle(
fontSize = 14.sp,
fontFamily = Bariol,
fontWeight = FontWeight.Normal,
lineHeight = (14 * 0.9).sp
),
// 按钮 / 标签
labelLarge = TextStyle(
fontSize = 16.sp,
fontFamily = Bariol,
fontWeight = FontWeight.Medium
)
)
说明:定义应用中用到的主要字体样式等
Kotlin
package com.leo.wechat.ui.component.text
sealed class AppTextState {
data object Normal : AppTextState()
data object Disabled : AppTextState()
data object Error : AppTextState()
data object Warning : AppTextState()
data object Success : AppTextState()
}
说明:定义应用中的Text几种状态
Kotlin
package com.leo.wechat.ui.component.text
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
object AppTextColor {
const val DisabledAlpha = 0.38f
@Composable
fun normal(base: Color): Color = base
@Composable
fun disabled(): Color =
MaterialTheme.colorScheme.onSurface.copy(alpha = DisabledAlpha)
@Composable
fun error(): Color =
MaterialTheme.colorScheme.error
@Composable
fun warning(): Color =
MaterialTheme.colorScheme.tertiary
@Composable
fun success(): Color =
MaterialTheme.colorScheme.primary
}
说明:定义应用中Text不同状态下对应的颜色
Kotlin
package com.leo.wechat.ui.component.text
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.TextUnit
@Composable
internal fun AppTextState.resolveColor(
baseColor: Color,
): Color = when (this) {
AppTextState.Normal -> AppTextColor.normal(baseColor)
AppTextState.Disabled -> AppTextColor.disabled()
AppTextState.Error -> AppTextColor.error()
AppTextState.Warning -> AppTextColor.warning()
AppTextState.Success -> AppTextColor.success()
}
private fun AppTextState.resolveStyle(base: TextStyle): TextStyle =
when (this) {
AppTextState.Error, AppTextState.Warning ->
base.copy(fontWeight = FontWeight.Medium)
else -> base
}
@Composable
fun AppText(
text: String,
modifier: Modifier = Modifier,
state: AppTextState = AppTextState.Normal,
color: Color = MaterialTheme.colorScheme.onBackground,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign = TextAlign.Unspecified,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,// word wrap
maxLines: Int = Int.MAX_VALUE,
minLines: Int = 1,
onTextLayout: ((TextLayoutResult) -> Unit)? = null,
textStyle: TextStyle = MaterialTheme.typography.bodyLarge,
) {
Text(
text = text,
modifier = modifier,
color = state.resolveColor(color),
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight,
overflow = overflow,
softWrap = softWrap,
maxLines = maxLines,
minLines = minLines,
onTextLayout = onTextLayout,
style = state.resolveStyle(textStyle)
)
}
说明:定义一个应用中通用的Text,支持不同状态下的颜色样式等。