Android Compose 分享
文章目录
目标
- 认识 Compose
- 学习 Compose 的基本布局
- Compose 的状态管理
- 自定义 Compose 的 View
Compose 的 Column、Row、Box 基本布局
这个三种基本布局的示意如图
代码示范
Column
@Preview("column")
@Composable
fun ColumnLayout() {
Column(
modifier = Modifier
.background(Color(0xFF063142))
.padding(16.dp)
.size(150.dp)
) {
Text(
text = "", modifier = Modifier
.fillMaxWidth()
.height(50.dp)
.background(Color(0xFFFFFF00))
)
Text(
text = "", modifier = Modifier
.fillMaxWidth()
.weight(1f, true)
.background(Color(0xFF4385F4))
)
Text(
text = "", modifier = Modifier
.fillMaxWidth()
.weight(1f, true)
.background(Color(0xFF3CDB85))
)
}
}
-
16.dp
是之前布局中的dp,这个的转换必须在有context的内容下才能进行转换为px等
Row
@Preview("row")
@Composable
fun RowLayout() {
Row(
modifier = Modifier
.size(200.dp)
.background(Color(0xFF063142))
) {
Text(
text = "", modifier = Modifier
.fillMaxHeight()
.width(50.dp)
.background(Color(0xFFFFFF00))
)
Text(
text = "", modifier = Modifier
.fillMaxHeight()
.weight(1f, true)
.background(Color(0xFF4385F4))
)
Text(
text = "", modifier = Modifier
.fillMaxHeight()
.width(50.dp)
.background(Color(0xFF3CDB85))
)
}
}
Box
@Preview("box")
@Composable
fun BoxLayout() {
Box(modifier = Modifier.size(200.dp)) {
Text(
text = "", modifier = Modifier
.background(Color(0xFF063142))
.fillMaxSize()
)
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "",
modifier = Modifier
.size(
100.dp, 100.dp
)
.background(Color.Yellow)
)
}
}
}
Modifier 的使用
Compose的一个重要的样式属性:修饰符。类似于Style,常用的属性大小,布局,行为和外观。因为Compose是函数式的编程语言,所以这个属性非常灵活。需要注意的
- 有序
- 手势(点击、滚动、拖动,滑动,多点触控,旋转、缩放)
列表
列表我们这边来实现一个简易的需求,一个通讯录的个人信息展示。
实现 Item 布局
@Preview
@Composable
fun ContactListItem(
@PreviewParameter(ContactDefault::class) contact: Contact
) {
val context = LocalContext.current
Row(
modifier = Modifier
.background(Color(0xFFF6F6F6))
.padding(10.dp)
.height(60.dp)
.fillMaxWidth()
.background(Color.White, RoundedCornerShape(10.dp))
.clickable {
Toast
.makeText(context, "contact:${contact.toString()}", Toast.LENGTH_SHORT)
.show()
}
.padding(6.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "",
modifier = Modifier
.size(40.dp, 40.dp)
.background(color = contact.userIcon, shape = CircleShape),
)
Column(Modifier.padding(10.dp, 0.dp, 0.dp, 0.dp)) {
Text(text = contact.userName, fontSize = 16.sp)
Text(text = contact.email, fontSize = 10.sp, color = Color(0x52030303))
}
}
}
拼接 Item 成为 List
@Preview
@Composable
fun ContactList() {
val dataSource = mockData()
MyComposeTheme() {
LazyColumn() {
dataSource.forEach { contact ->
item {
ContactListItem(contact)
}
}
}
}
}
总结 List 的实现
通过上面两步发现整体的编码过程只考虑了 Item 和 DataSource 进行遍历即可。LazyColumn进行懒加载和复用,很明显比 RecycleView 和 ListView 高效明了,如果需要加载不同的 Item 只需要在遍历进行判断,加载不一样的 Item 即可。需要注意的是
@PreviewParameter(ContactDefault::class)
对于预览函数的使用,这个主要是结合 AndroidStudio 进行预览的方法,这个方法的接口为
PreviewParameterProvider<T>
,重写values方法,返回mock的数据。
Compose 的状态管理
在 Android 的以往的 xml 的布局中,ViewModel 是使用 Observable 对数据进行封装,根据 xml 生产监听的代码。在 Compose 中使用
remember {mutableStateOf()}
的值,当值变化后,就会执行触发改变。例子
@Preview("state_manager")
@Composable
fun StateManagerView() {
var name by rememberSaveable { mutableStateOf("") }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Text(text = "Hello")
OutlinedTextField(value = name,
onValueChange = { name = it },
label = { Text(text = "name") }
)
}
}
Compose 自定义 View
实现一个手动划线的 DrawView
需求:实现一个划线的View,支持撤销,手指按下后,进行滑动,画出线段,抬起手结束。
@SuppressLint("UnrememberedMutableState")
@Preview("drawView2")
@Composable
fun DrawView2() {
var pathSave: SnapshotStateList<Pair<Boolean, Pair<Float, Float>>?> = mutableStateListOf(null)
Box(modifier = Modifier.fillMaxSize()) {
Canvas(
modifier = Modifier
.fillMaxSize()
.pointerInteropFilter {
when (it.action) {
MotionEvent.ACTION_DOWN -> {
pathSave.add(Pair(true, Pair(it.x, it.y)))
}
MotionEvent.ACTION_MOVE -> {
pathSave.add(Pair(false, Pair(it.x, it.y)))
}
MotionEvent.ACTION_UP -> {
pathSave.add(Pair(false, Pair(it.x, it.y)))
}
else -> false
}
true
}
) {
drawPath(
path = pathSave.toPath(),
color = Color.Yellow,
alpha = 1f,
style = Stroke(4.dp.toPx())
)
}
Text(
modifier = Modifier
.padding(20.dp)
.background(Color(0xFF00ffff), RoundedCornerShape(12.dp))
.padding(10.dp)
.clickable {
pathSave.retract()
},
text = "回撤"
)
}
}
private fun SnapshotStateList<Pair<Boolean, Pair<Float, Float>>?>.retract(): SnapshotStateList<Pair<Boolean, Pair<Float, Float>>?> {
var lastStart = 0;
forEachIndexed { index, item ->
if (item != null && item.first) {
lastStart = index
}
}
if (lastStart != 0) {
removeRange(lastStart, size)
removeLast()
} else {
clear()
}
return this
}
fun SnapshotStateList<Pair<Boolean, Pair<Float, Float>>?>.toPath(): Path {
val path = Path()
forEach {
if (it != null) {
if (it.first) {
path.moveTo(it.second.first, it.second.second)
} else {
path.lineTo(it.second.first, it.second.second)
}
}
}
return path
}
-
pointerInteropFilter
进行触摸事件处理和原来的差不多 -
Pair<Boolean, Pair<Float, Float>>
用于记录坐标,按下的为 true ,其他为false,false 连在一起
实现一个进度条的 LevelView
需求:实现绘制弧形,进度显示
@Preview("levelView")
@Composable
fun LevelView(singleAngle: Float = 30F, levelSize: Int = 8, angleValue: Float = 45F) {
val size = 300.dp
val lineSize = 20.dp
//计算出旋转的角度
val ration = ration(levelSize * singleAngle)
val slipSize = 360 / singleAngle / 2
val slipWidth = 5.dp
Canvas(
modifier = Modifier
.size(size)
) {
drawIntoCanvas { canvas ->
val lineWidth = lineSize.toPx()
rotate(ration, center) {
canvas.saveLayer(
Rect(
Offset.Zero,
Size(size.toPx(), size.toPx())
),
Paint()
)
drawArc(
color = Color.Yellow,
startAngle = 0F, sweepAngle = levelSize * singleAngle,
useCenter = false,
topLeft = Offset(lineWidth / 2, lineWidth / 2),
size = Size(
size.toPx() - lineWidth,
size.toPx() - lineWidth,
),
alpha = 1.0f,
style = Stroke(lineWidth),
)
drawArc(
color = Color.Red,
startAngle = 0F, sweepAngle = angleValue,
useCenter = false,
topLeft = Offset(lineWidth / 2, lineWidth / 2),
size = Size(
size.toPx() - lineWidth,
size.toPx() - lineWidth,
),
alpha = 1.0f,
style = Stroke(lineWidth),
)
//划线
for (index in 0..slipSize.toInt()) {
rotate(index * singleAngle, center) {
drawLine(
Color.Yellow,
Offset(0f, size.toPx() / 2),
Offset(size.toPx(), size.toPx() / 2),
slipWidth.toPx(),
blendMode = BlendMode.DstOut
)
}
}
canvas.restore()
}
}
}
}
fun ration(maxAngle: Float): Float {
return if (maxAngle <= 90f) {
270F - (maxAngle / 2)
} else if (maxAngle > 90F && maxAngle <= 180F) {
180F - (maxAngle / 2)
} else if (maxAngle > 180F && maxAngle <= 270F) {
180F - (maxAngle + 180 - 360) / 2
} else {
90F + (450F - (maxAngle + 90)) / 2
}
}
-
rotate(ration, center) {}
选择函数,如果是以往的是
view.rotate(ration,center)
用括号包裹的内容是作用的对象区域,在使用时要加以区别 - 其他绘制和原来的区别不大
Compose 是什么
进过以上的实操和体验后,我们来看看Android Compose的定义和说明:
用于构建原生界面的新款 Android 工具包。它可简化并加快 Android 上的界面开发。使用更少的代码、强大的工具和直观的 Kotlin API,快速让应用生动而精彩。有以下的特点
- 声明性编程,减少了遍历树,内部管理状态
- 简单的组合函数
- 动态内容
总结
Compose 和之前的 View 比较
View | Compose |
---|---|
命令式编程 | 声明式编程 |
树形结构的遍历查找设置 | 组件内部进行变更 |
基于View和ViewGroup衍生 | 基于更为底层的Canvas绘制 |
面向对象的继承 | 组合与函数化 |
xml相对静态 | 动态组合 |
支持预览 | 支持丰富的预览,包括点击触摸 |
上手体验
先跟随文档进行简易的编码,有了整体的认知后,进行小需求去实现,看看自己理解的盲区在哪里,之后去修复。
学习资料
名字 | 推荐原因 | 网站 |
---|---|---|
Jetpack Compose | 官方出品,全面详尽 | https://developer.android.google.cn/jetpack/compose |
compose-samples | 有详尽的代码,方便手动编译和改动 | https://github.com/android/compose-samples |