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<String> = _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混用

参考

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享