Compose简介
Jetpack Compose 是一种全新的 Android UI 东西包,它答应开发者运用 Kotlin 语言编写声明式用户界面。
Jetpack Compose 的设计思想是运用函数式编程和响应式编程的形式来构建用户界面。开发者能够将 UI 组件界说为不可变的函数,这些组件能够接受参数、回来值,并且能够通过组合、嵌套等方式来创建杂乱的 UI 层次结构。而且,在 Jetpack Compose 中,能够运用状况、事情等响应式数据流来处理 UI 更新和交互。 Jetpack Compose 供给了丰厚的 UI 组件库,并且支撑自界说组件的开发和集成。它还供给了许多快捷的东西和功用,例如动画、主题、布局体系等,能够协助开发者快速构建高质量的用户体验。
总之,Jetpack Compose 是 Android 开发领域中一项重要的技术创新,它能够协助开发者愈加轻松、高效地构建和设计用户界面,并且供给了愈加现代化和强壮的东西和功用。
优势
- 更少的代码量。只需求运用kotlin而不用再分开编写kotlin与XML,跟踪变得跟简单。
- 更直观的代码。声明式UI,只需求构筑界面。
- 加速开发进程。传统View与Compose能够互调用,开发进程能够实时预览布局
下风
- Compose与原生View体系混合开发时,包体积增大
- 功用与传统XML布局相比并没有优势,通过多次迭代,目前与XML功用相等
开发实践
Talk is cheap, show me the code
登录页面
一个包含了头部图片、Logo、用户名暗码输入框和一个登录按钮的登录页。完成效果如下:
具体完成(解析在注释中):
// Composable注解函数标明此办法回来值为可组合视图
// 刷新次数和机遇不定,不要在其中处理事务(杂乱逻辑悉数放入ViewModel)
@Composable
fun LoginPage(loginViewModel: LoginViewModel, navigationActions: NavigationActions) {
// 在生命周期内采集StateFlow最新数据,以State形式出现
val uiState by loginViewModel.uiState.collectAsStateWithLifecycle()
// 获取Context办法
val context = LocalContext.current
// Box 可堆叠布局
Box {
// 图片
Image(
// 调整样式(width、height、background、padding、border、clickable等)
modifier = Modifier
// 扩展函数办法,转换成dp单位
.height(height = 265.dp)
.fillMaxWidth(),
// 图片缩放类型
contentScale = ContentScale.FillHeight,
// 图片内容
painter = painterResource(id = R.drawable.icon_heading_common),
// 图片描绘,xml中也有此属性
contentDescription = stringResource(
id = R.string.app_name
)
)
// 纵向布局
Column {
// 间隔,充任margin
Spacer(modifier = Modifier.height(250.dp))
Column(
Modifier
// 圆角
.clip(RoundedCornerShape(topStart = 10.dp, topEnd = 10.dp))
.background(Color.White)
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
// 间距
Spacer(modifier = Modifier.height(48.dp))
Image(
painter = painterResource(id = R.drawable.icon_login_header_pos),
contentDescription = stringResource(
id = R.string.app_name
)
)
Spacer(modifier = Modifier.height(48.dp))
// 笼统出来的输入框办法
InputText(
hint = "用户名",
needEncrypt = false,
value = uiState.userName,
onValueChanged = {
loginViewModel.onUserNameChanged(it)
loginViewModel.judgeCanLogin()
})
Spacer(modifier = Modifier.height(16.dp))
InputText(
hint = "暗码",
needEncrypt = true,
value = uiState.userPassword,
onValueChanged = {
loginViewModel.onUserPasswordChanged(it)
loginViewModel.judgeCanLogin()
})
Spacer(modifier = Modifier.height(32.dp))
// 制作登录按钮
Button(
shape = RoundedCornerShape(4.dp),
modifier = Modifier
.height(54.dp)
.fillMaxWidth()
.padding(horizontal = 20.dp)
.background(Color(247, 248, 249)),
colors = ButtonDefaults.buttonColors(
backgroundColor = Color(0xff287dfa),
contentColor = Color(0xffffffff),
// 不可点击时的样式
disabledBackgroundColor = Color(0xffbfbfbf),
disabledContentColor = Color(0xffffffff),
),
// 能否点击
enabled = uiState.isLoginEnable,
onClick = {
// 履行ViewModel中的登录逻辑
loginViewModel.login(loginSuccess = {
// 登录成功回调,此为跳转到首页
navigationActions.navigateToHome()
}, loginFailed = {})
}) {
Text(
"登录",
color = Color(0xffffffff),
fontSize = 16.sp
)
}
} }
// Loading显隐,只需求直接依据UiState中的布尔值,判断是否履行Loading的Composable办法
if (uiState.showLoading) {
Row {
Loading()
}
}
}
}
/**
* 自界说的EditText
*/
@Composable
fun InputText(
hint: String,
needEncrypt: Boolean,
value: String,
onValueChanged: (String) -> Unit
) {
// 等价于EditText
OutlinedTextField(
// 圆角
shape = RoundedCornerShape(4.dp),
modifier = Modifier
.height(54.dp)
.fillMaxWidth()
.padding(horizontal = 20.dp)
.background(Color(247, 248, 249)),
colors = TextFieldDefaults.outlinedTextFieldColors(
backgroundColor = Color(
0xfff7f8f9
),
// 边框颜色
focusedBorderColor = Color(
0xfff7f8f9
),
unfocusedBorderColor = Color(
0xfff7f8f9
),
textColor = Color(0xff666666)
), placeholder = {
// Hint现需求传入组件
Text(
hint,
color = Color(0xffbfbfbf),
fontSize = 16.sp
)
},
// 字体选项
textStyle = TextStyle(fontSize = 16.sp),
// 输入内容显隐
visualTransformation = if (needEncrypt) PasswordVisualTransformation() else VisualTransformation.None,
// 输入框内显现的值
value = value,
// 输入内容改变回调
onValueChange = onValueChanged
)
}
首页
效果如下:
// Preview注解能够实时预览UI效果
@Preview
@Composable
fun Home(homeViewModel: HomeViewModel = viewModel(factory = HomeViewModel.provideFactory())) {
// 回来键阻拦(在后面会提到)
BackPressHandler(onBackPressed = {
"点击了回来键".toast()
})
val uiState by homeViewModel.uiState.collectAsStateWithLifecycle()
Column(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth(),
) {
Column(
modifier = Modifier.padding(horizontal = 16.dp)
) {
Spacer(modifier = Modifier.height(40.dp))
Column(
modifier = Modifier
.height(125.dp)
.fillMaxWidth()
.clip(
shape = RoundedCornerShape(10.dp),
)
.background(color = Color(0xff287dfa))
) {}
} Spacer(modifier = Modifier.height(20.dp))
MenuGrid(itemDatas = uiState.menuList)
}
}
@Composable
fun MenuGrid(itemDatas: List<MenuBean>) {
// 懒加载列表,能够完成RecyclerView的效果。
// 同样的运用还有LazyColum、LazyRow、LazyHorizontalGrid等
LazyVerticalGrid(
modifier = Modifier.padding(horizontal = 8.dp),
// 分为3列
columns = GridCells.Fixed(3),
content = {
this.items(itemDatas) {
// 遍历,依据数据列表数量回来每个Item的布局
MenuItem(itemData = it)
}
})
}
/**
* 列表子项
*/
@Composable
fun MenuItem(itemData: MenuBean) {
val context = LocalContext.current
Column(modifier = Modifier
.padding(6.dp)
.clickable {
// 点击事情
MenuBean
.getMenuName(ActivityUtils.getTopActivity(), itemData.key)
.toast()
}) {
Column(
modifier = Modifier
.border(
width = 1.dp,
color = Color(0xfff5f5f5),
shape = RoundedCornerShape(10.dp),
)
.height(90.dp)
.fillMaxWidth()
.padding(8.dp)
.background(color = Color(0xffffffff))
) {
Spacer(modifier = Modifier.height(2.dp))
Image(
painter = painterResource(id = MenuBean.getMenuIcon(itemData.key)),
contentDescription = MenuBean.getActionName(itemData.key)
)
Spacer(modifier = Modifier.height(6.dp))
Text(
MenuBean.getMenuName(context, itemData.key),
color = Color(0xff333333),
fontSize = 13.sp
)
}
}
}
有关Effect
/**
* 回来键监听回调
*/
@Composable
fun BackPressHandler(onBackPressed: () -> Unit, enabled: Boolean = true) {
// 体系回来键阻拦器
val dispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
// remember函数能够理解为:写在函数内部的大局变量。不会跟从Composable办法反复履行。
val backCallback = remember {
object : OnBackPressedCallback(enabled) {
override fun handleOnBackPressed() {
onBackPressed()
}
}
}
// 在Effect中对外部回调进行注册,在dispatcher改变时履行
DisposableEffect(dispatcher) {
// DisposableEffect默认在每次onCommit时都会履行,
// 每次从头注册
dispatcher?.addCallback(backCallback)
// DisposableEffect中有必要完成onDispose,如不需求,可运用简化API-SideEffect
onDispose {
backCallback.remove() // 避免泄露
}
} // 能够在Effect中处理订阅逻辑
}
ViewModel
class LoginViewModel : ViewModel() {
// viewModel中处理逻辑
// 私有可变UiState
private val _uiState = MutableStateFlow(
LoginUiState(
isLoginEnable = false,
showLoading = false,
userName = "",
userPassword = ""
)
)
// 公开的不可变UiState
val uiState: StateFlow<LoginUiState> = _uiState.asStateFlow()
/**
* 更新用户名
*/
fun onUserNameChanged(userName: String) {
_uiState.update {
// MVI架构,仅更新entity中的指定字段,前台Compose代码中检测到字段变动会直接从头履行
it.copy(userName = userName)
}
}
/**
* 更新用户暗码
*/
fun onUserPasswordChanged(userPassword: String) {
_uiState.update {
it.copy(userPassword = userPassword)
}
}
/**
* 判断能否登录
*/
fun judgeCanLogin() {
_uiState.update {
it.copy(isLoginEnable = it.userName.isNotEmpty() && it.userPassword.isNotEmpty())
}
}
/**
* 履行登录
*/
fun login(loginSuccess: () -> Unit, loginFailed: () -> Unit) {
viewModelScope.launch {
showLoading()
// 推迟1秒模拟请求场景
delay(1000)
hideLoading()
loginSuccess()
}
}
private fun showLoading() {
_uiState.update {
it.copy(showLoading = true)
}
}
private fun hideLoading() {
_uiState.update {
it.copy(showLoading = false)
}
}
companion object {
fun provideFactory(): ViewModelProvider.Factory = object : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return LoginViewModel() as T
}
}
}
}
路由逻辑
object DemoDestinations {
const val SPLASH_ROUTE = "splash"
const val HOME_ROUTE = "home"
const val LOGIN_ROUTE = "login"
}
class NavigationActions(navController: NavHostController) {
val navigateToHome: () -> Unit = {
// 在具体页面中履行 navigationActions.navigateToHome() 即可跳转
navController.navigate(DemoDestinations.HOME_ROUTE) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
} val navigateToLogin: () -> Unit = {
navController.navigate(DemoDestinations.LOGIN_ROUTE)
}
val navigatePopBack: () -> Unit = {
// 回来,相当于finish()
navController.navigateUp()
}
}
@Composable
fun NaviGraph(
navController: NavHostController = rememberNavController(),
startDestination: String = DemoDestinations.SPLASH_ROUTE,
navigationActions: NavigationActions
) {
NavHost(
navController = navController,
startDestination = startDestination
) {
// 界说Splash页
composable(DemoDestinations.SPLASH_ROUTE) {
SplashPage(navigationActions)
}
// 定位登录页
composable(DemoDestinations.LOGIN_ROUTE) {
// ViewModel-Compose库中,专为Compose设计的大局ViewModel初始化办法
val loginViewModel: LoginViewModel =
viewModel(factory = LoginViewModel.provideFactory())
LoginPage(
loginViewModel, navigationActions
)
}
// 界说首页
composable(DemoDestinations.HOME_ROUTE) {
val homeViewModel: HomeViewModel = viewModel(factory = HomeViewModel.provideFactory())
Home(
homeViewModel
)
}
}}
将写好的页面放入Activity中
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
// 和Flutter相同,Activity成为了Compose的展示容器
ComposeDemoApp()
}
}
}
@Composable
fun ComposeDemoApp() {
ComposeDemoTheme {
// remember函数是Compose中的重要API,用于包装一些计算成本较高的逻辑或一些state数据,避免函数重复履行
// 能够理解为:写在函数内部的大局变量。不会跟从Composable办法的反复履行而重复初始化。
// 有很多remember最初的API,都是不同功用的remember的具体完成
val systemUiController = rememberSystemUiController()
val useDarkIcons = MaterialTheme.colors.isLight
// SideEffect相当于DisposableEffect的简化版,能够用来更新外部状况。
SideEffect {
// 通明状况栏
systemUiController.setStatusBarColor(
color = Color.Transparent,
darkIcons = useDarkIcons
)
}
Surface(
modifier = Modifier
.fillMaxSize()
.fillMaxHeight(),
color = MaterialTheme.colors.background,
) {
val navController = rememberNavController()
val navigationActions = remember(navController) {
NavigationActions(navController)
}
NaviGraph(navController = navController, navigationActions = navigationActions)
}
}}
参阅链接
Jetpack Compose 运用入门 | Android Developers