陈小缘之前用view实现了一遍,自己打算延用其思路,用compose 复刻一下这个效果。
陈小缘之前用view实现的效果链接点这 Android实现圆弧滑动效果之FanLayout篇
要实现这个效果我们得掌握几个compoe的几个基础知识点:
- 事件分发以及手势处理
- 自定义layout 或者叫自定义viewgroup也可以
compose的事件分发优先级有PointerEventPass.Final , PointerEventPass.Main ,PointerEventPass.Initial,我们需要在down的时候消费事件并把事件优先级设为final , 不然触摸在子view上无法滑动,关键代码如下:
@Composable
fun FanLayout() {
Surface(modifier = Modifier.fillMaxSize()) {
val angle = remember {
Animatable(0f)
}
Layout(
content = {
List(10) {
Row(modifier = Modifier
.fillMaxWidth()
.requiredHeight(40.dp)
.graphicsLayer {
transformOrigin = TransformOrigin(0f, 0f)
rotationZ = (360 / 10).toFloat() * it + (angle.value % 360)
}
.padding(start = 100.dp)
.clickable {
Toast.makeText(this@MainActivity, "click $it", Toast.LENGTH_SHORT).show()
}
.background(Color((0xff000000..0xffffffff).random()))) {
repeat(10){
Image(
painter = painterResource(id = R.mipmap.ic_launcher),
contentDescription = "",
)
}
}
}
},
modifier = Modifier
.fillMaxSize()
.background(Color.Gray)
.drawWithContent {
drawContent()
drawCircle(
Color.Green,
center = Offset(20.dp.toPx(), size.height / 2),
radius = 50.dp.toPx()
)
}
.pointerInput(Unit) {
val decay = splineBasedDecay<Offset>(this)
coroutineScope {
while (true) {
val firstDownPointerId =
awaitPointerEventScope {
awaitFirstDown(
requireUnconsumed = false,
pass = PointerEventPass.Final
).id
}
val velocityTracker = VelocityTracker()
angle.stop()
awaitPointerEventScope {
drag(firstDownPointerId) { change ->
velocityTracker.addPosition(
change.uptimeMillis,
change.position,
)
launch {
angle.snapTo(
angle.value + getAngle(change),
)
}
}
}
if (angle.value % 36 < 2) {
angle.snapTo(
angle.value - angle.value % 36,
)
} else {
angle.snapTo(
angle.value + 36 - angle.value % 36,
)
}
}
}
}) { measurables, constraints ->
// 对 children 进行测量和放置
val placeables = measurables.mapIndexed { index, measurable ->
measurable.measure(constraints.copy(minWidth = 0, minHeight = 0))
}
layout(constraints.maxWidth, constraints.maxHeight) {
// 摆放每个 child
placeables.forEachIndexed { index, placeable ->
placeable.placeRelative(
0,
constraints.maxHeight / 2
)
}
}
}
}
}
本来还想着处理下fling , 但无论怎么处理体验都不如不处理好,所以你懂得。