如何在Jetpack Compose中绘制可旋转的3D立方体等3D对象?
Jetpack Compose 3D旋转立方体入门实现
实现思路
不用依赖第三方3D库,直接使用Compose原生Canvas配合图形变换、基础3D坐标投影即可实现,核心逻辑不到100行代码,上手门槛极低。
完整可运行示例代码
import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.tween import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.drawscope.withTransform import androidx.compose.ui.unit.dp // 立方体8个顶点的3D坐标,中心为原点,边长200.dp private val cubeVertices = listOf( floatArrayOf(-100f, -100f, -100f), floatArrayOf(100f, -100f, -100f), floatArrayOf(100f, 100f, -100f), floatArrayOf(-100f, 100f, -100f), floatArrayOf(-100f, -100f, 100f), floatArrayOf(100f, -100f, 100f), floatArrayOf(100f, 100f, 100f), floatArrayOf(-100f, 100f, 100f), ) // 立方体6个面对应的顶点索引 private val cubeFaces = listOf( listOf(0, 1, 2, 3), // 背面 listOf(4, 5, 6, 7), // 正面 listOf(0, 4, 7, 3), // 左面 listOf(1, 5, 6, 2), // 右面 listOf(0, 1, 5, 4), // 顶面 listOf(3, 2, 6, 7), // 底面 ) // 每个面的填充色 private val faceColors = listOf( Color.Red, Color.Green, Color.Blue, Color.Yellow, Color.Cyan, Color.Magenta ) @Composable fun RotatingCube() { // 无限旋转动画 val infiniteTransition = rememberInfiniteTransition(label = "cube_rotation") val rotationX by infiniteTransition.animateFloat( initialValue = 0f, targetValue = 360f, animationSpec = infiniteRepeatable(tween(4000)), label = "rotation_x" ) val rotationY by infiniteTransition.animateFloat( initialValue = 0f, targetValue = 360f, animationSpec = infiniteRepeatable(tween(6000)), label = "rotation_y" ) Canvas(modifier = Modifier.fillMaxSize()) { val centerX = size.width / 2 val centerY = size.height / 2 val focalLength = 300f // 透视焦距,数值越小透视效果越强 withTransform({ rotateX(rotationX, pivot = Offset(centerX, centerY)) rotateY(rotationY, pivot = Offset(centerX, centerY)) }) { cubeFaces.forEachIndexed { faceIndex, vertexIndices -> val path = Path() vertexIndices.forEachIndexed { index, vertexIndex -> val (x, y, z) = cubeVertices[vertexIndex] // 简单透视投影计算 val scale = focalLength / (focalLength + z) val screenX = centerX + x * scale val screenY = centerY + y * scale if (index == 0) { path.moveTo(screenX, screenY) } else { path.lineTo(screenX, screenY) } } path.close() drawPath(path, color = faceColors[faceIndex]) } } } }
关键逻辑说明
- 顶点与面定义:提前定义立方体8个顶点的三维坐标和6个面的顶点索引,避免重复计算
- 动画实现:用
rememberInfiniteTransition生成循环的X、Y轴旋转角度,实现自动旋转效果,也可以替换成触摸手势控制的状态变量实现手动旋转 - 透视投影:通过简单的焦距公式把3D坐标转换成2D屏幕坐标,实现近大远小的3D视觉效果,调整
focalLength数值可以修改透视强度 - 绘制逻辑:遍历每个面用Path连接投影后的顶点,填充不同颜色方便区分立方体的不同面
扩展优化方向
- 可以增加面的描边,提升立体感
- 增加背面剔除逻辑,避免绘制被遮挡的面提升性能
- 增加z轴排序,保证远处的面先绘制、近处的面后绘制,避免显示顺序错误
内容的提问来源于stack exchange,提问作者Nurseyit Tursunkulov
相关产品推荐
相关产品推荐

