注册

Android Compose 初探!

使用前的准备工作

  1. android studio Arctic Fox版本或更新的版本

  2. 如果是一个新项目,可以在创建的时候,新建一个Empty Compose Activity

    image.png

  3. 在module的build.gradle文件中添加

    android {
    buildFeatures {
    compose true
    }
    composeOptions {
    kotlinCompilerExtensionVersion compose_version
    kotlinCompilerVersion '1.4.32'
    }
    }
    dependencies {
    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.3.0'
    implementation "androidx.compose.ui:ui:$compose_version"
    implementation "androidx.compose.material:material:$compose_version"
    implementation "androidx.compose.ui:ui-tooling:$compose_version"
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
    implementation 'androidx.activity:activity-compose:1.3.0-alpha06'
    }

需要添加

buildFeatures {
compose true
}

组件

组件的定义

在Compose中一个UI组件就是一个带有@Composable注解的函数

@Composable
fun Greeting(name: String) {
Text(text = "Hello $name!")
}

布局组件

如果没有采用布局组件,直接单视图写到一个Compose中,会存在异常的情况。官方是这么说的:

A Composable function might emit several UI elements. However, if you don't provide guidance on how they should be arranged, Compose might arrange the elements in a way you don't like

  • Row 横向排列视图, Row的相关属性如下:
    inline fun Row(
    modifier: Modifier = Modifier,
    horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
    verticalAlignment: Alignment.Vertical = Alignment.Top,
    content: @Composable RowScope.() -> Unit
    )
  • Column 纵向排列视图, 其属性和上面的Row类似
  • Box 将一个元素覆盖在另一个上面, 类似于FrameLayout这种

视图组件

  • Text 类似于原生View中的TextView
  • Button 按钮
  • LazyColumn 类似于原生RecyclerView
  • Image 图片控件 关于网络图片,可以采用Coil框架
  • TextField 文件输入框
  • Surface用来控制组件的背景,边框,文本颜色等
  • AlertDialog 弹窗控件,类似于原生View中的AlertDialog

组件的状态管理

remember

通过remember来记录组件某些相关属性值,当属性发生变化,会自动触发UI的更新。

@Composable
fun HelloContent() {
Column(modifier = Modifier.padding(16.dp)) {
var nameState = remember { mutableStateOf("") }
var name = nameState.value;
Text(
text = "Hello, $name!",
modifier = Modifier.padding(bottom = 8.dp),
style = MaterialTheme.typography.h5
)
TextField(
value = name,
onValueChange = { println("data----->$it");nameState.value = it }
)
}
}

这段代码实现的功能就是当用户在一个输入框中输入文字的时候,即时回显在页面上。当采用这种方式编码时,状态是耦合在组件中,当调用者不关心内部的状态的,这种方式是ok的,但它的弊端就是不利于组件的复用。我们可以将状态和组件分离开,此时,便就是利用状态提升(state hoisting)的手段

@Composable
fun HelloScreen() {
var nameState = remember { mutableStateOf("") }
HelloContent(name = nameState.value, onNameChange = { nameState.value = it })
}

@Composable
fun HelloContent(name: String, onNameChange: (String) -> Unit) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Hello, $name!",
modifier = Modifier.padding(bottom = 8.dp),
style = MaterialTheme.typography.h5
)
TextField(
value = name,
onValueChange = { onNameChange(it) }
)
}
}

这里是将状态提到HelloContent的外面, 方面HelloContent组件的复用

rememberSaveable

remember类似,区别在于rememberSaveable进行状态管理时,当activity或进程重新创建了(如屏幕旋转),其状态信息不会丢失。 将上面的var nameState = remember { mutableStateOf("") } 中的remember换成rememberSaveable就可以了

ViewModel

可以利用ViewModel进行全局的状态管理

class HelloViewModel : ViewModel() {

// LiveData holds state which is observed by the UI
// (state flows down from ViewModel)
private val _name = MutableLiveData("")
val name: LiveData = _name

// onNameChange is an event we're defining that the UI can invoke
// (events flow up from UI)
fun onNameChange(newName: String) {
_name.value = newName
}
}

@Composable
fun HelloScreen(helloViewModel: HelloViewModel = viewModel()) {
// by default, viewModel() follows the Lifecycle as the Activity or Fragment
// that calls HelloScreen(). This lifecycle can be modified by callers of HelloScreen.

// name is the current value of [helloViewModel.name]
// with an initial value of ""
val name: String by helloViewModel.name.observeAsState("")
HelloContent(name = name, onNameChange = { helloViewModel.onNameChange(it) })
}

Modifers

Modifers是用来装饰composable, Modifiers用来告诉一个UI元素如何布局,显示,和相关的行为。

布局相关的属性

  • fillMaxWidth
  • matchParentSize
  • height
  • width
  • padding
  • size

显示

  • background
  • clip: 如Modifier.clip(RoundedCornerShape(4.dp)),一个圆角便出来了

绑定事件

利用clickable来绑定事件

Row(
Modifier
.fillMaxWidth()
.clickable { onClick(); },
verticalAlignment = Alignment.CenterVertically
) {
...
}

实例

采用Compose方案的开发体验非常接近于用Vue或React, 代码结构非常清晰,不用xml来画UI确实省了不少事,以下是一段代码片断来画一个微信的个人中心页

image.png

@Preview(showBackground = true)
@Composable
fun PersonalCenter() {
Column() {
Header("Hello World", "Wechat_0001")
Divider(
Modifier
.fillMaxWidth()
.height(8.dp), GrayBg
)
RowList()
Divider(
Modifier
.fillMaxHeight(), GrayBg
)
}
}

@Composable
fun Header(nickName: String, wechatNo: String) {
Row(
Modifier
.fillMaxWidth()
.padding(24.dp, 24.dp, 16.dp, 24.dp),
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(R.drawable.avatar),
contentDescription = "头像",
Modifier
.size(50.dp)
.clip(
RoundedCornerShape(4.dp)
)
)
Column() {
Text(nickName, Modifier.padding(12.dp, 2.dp, 0.dp, 0.dp), TextColor, fontSize = 18.sp)
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
"微信号 :$wechatNo",
Modifier
.padding(12.dp, 10.dp, 0.dp, 0.dp)
.weight(1.0f), TextColorGray, fontSize = 14.sp
)
Icon(painterResource(R.drawable.ic_qrcode), "二维码", Modifier.size(16.dp))
Icon(
painterResource(R.drawable.right_arrow_3),
contentDescription = "more",
Modifier.padding(12.dp, 0.dp, 0.dp, 0.dp)
)
}
}
}
}

@Composable
fun RowItem(@DrawableRes icon: Int, title: String, onClick: () -> Unit) {
Row(
Modifier
.fillMaxWidth()
.clickable { onClick(); },
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(icon), contentDescription = title + "icon",
Modifier
.padding(16.dp, 12.dp, 16.dp, 12.dp)
.size(24.dp)
)
Text(title, Modifier.weight(1f), TextColor, fontSize = 15.sp)
Icon(
painterResource(R.drawable.right_arrow_3),
contentDescription = "more",
Modifier.padding(0.dp, 0.dp, 16.dp, 0.dp)
)
}
}

@Composable
fun RowList() {
var context = LocalContext.current;
Column() {
RowItem(icon = R.drawable.ic_pay, title = "支付") { onItemClick(context, "payment") }
Divider(
Modifier
.fillMaxWidth()
.height(8.dp), GrayBg
)
RowItem(icon = R.drawable.ic_collections, title = "收藏") {
onItemClick(context, "收藏")
}
Divider(
Modifier
.fillMaxWidth()
.padding(56.dp, 0.dp, 0.dp, 0.dp)
.height(0.2.dp), GrayBg
)
RowItem(icon = R.drawable.ic_photos, title = "相册") {
onItemClick(context, "相册")
}
Divider(
Modifier
.fillMaxWidth()
.padding(56.dp, 0.dp, 0.dp, 0.dp)
.height(0.2.dp), GrayBg
)
RowItem(icon = R.drawable.ic_cards, title = "卡包") {
Toast.makeText(context, "payment", Toast.LENGTH_SHORT).show()
}
Divider(
Modifier
.fillMaxWidth()
.padding(56.dp, 0.dp, 0.dp, 0.dp)
.height(0.2.dp), GrayBg
)
RowItem(icon = R.drawable.ic_stickers, title = "表情") {
Toast.makeText(context, "payment", Toast.LENGTH_SHORT).show()
}
Divider(
Modifier
.fillMaxWidth()
.height(8.dp), GrayBg
)
RowItem(icon = R.drawable.ic_settings, title = "设置") {
Toast.makeText(context, "payment", Toast.LENGTH_SHORT).show()
}
}
}

fun onItemClick(context: Context, data: String) {
Toast.makeText(context, data, Toast.LENGTH_SHORT).show()
}

View中嵌Compose

var view = LinearLayout(this)
view.addView(ComposeView(this).apply {
setContent {
PersonalCenter();
}
})

Compose中嵌View

@Compose
fun RowList() {
...
AndroidView({View(context)}, Modifier.width(20.dp).height(20.dp).background(Color.Green)){}
...
}

总结

  • Compose使用了一套新的布局,渲染机制, 它里面的元素和我们以前写的各种View是有区别的,比如Compose里面的Text并不是我们以前认识的TextView或其它的原生控件, 它采用了更底层的api来实现
  • 数据的自动订阅(完成双向绑定)
  • 声明式UI: compose通过自动订阅机制来完成UI的自动更新
  • compose和现有的原生View混用

0 个评论

要回复文章请先登录注册