Compose:自定义绘制

文章探讨了Compose与原生Android在自定义视图绘制上的差异,Compose提供了DrawModifier简化绘制过程,如drawBehind和drawWithContent,以及如何使用Box和Canvas进行复杂绘制。在某些复杂场景,如三维旋转,Compose可能需要降级到原生Canvas来实现。
摘要由CSDN通过智能技术生成

Compose 的绘制和原生的差异

原生的 View 绘制我们都很清楚可以在自定义 View 的 onDraw() 通过 Canvas 实现:

class CustomView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
	private val paint = Paint()
	
	override fun onDraw(canvas: Canvas?) {
		super.onDraw(canvas)
		paint.color = Color.Yellow
		canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
	}
}

Compose 的绘制底层其实还是使用的 Canvas,不过相比原生的自定义绘制,Compose 提供了更上层的 API,这套 API 能让我们更简单更直接实现和原生一样的效果。

我们在讲解 Modifier 时就有提及 DrawModifier 并对其进行了详细的分析,所有的绘制步骤包括组件自身的绘制、手写的自定义绘制都是在 DrawModifier。我们可以用 DrawModifier 实现不同的绘制顺序。

比如 Modifier.drawBehind() 就比较适合绘制背景

@Preview
@Composable
fun CustomText() {
	Text("test", Modifier.drawBehind {
		// 相当于原生绘制把 drawRect() 和 drawColor() 合并的效果
		// 并且不再需要提供 Paint 控制参数,参数配置都分配到独立的组件
		drawRect(Color.Yellow)
	})
}

// 绘制效果

上面是一个文本,然后使用 Modifier.drawBehind() 绘制黄色的背景。可以发现使用 Compose 绘制和原生不同的是,我们不需要指定绘制范围,默认就是文本范围。这其实是 Compose 默认提供的就是组件的范围:

DrawScope.kt

fun drawRect(
	color: Color,
	topLeft: Offset = Offset.Zero, // 矩形左上角
	size: Size = this.size.offsetSize(topLeft), // 矩形范围
	alpha: Float = 1.0f,
	style: DrawStyle = Fill,
	colorFilter: ColorFilter? = null,
	blendMode: BlendMode = DefaultBlendMode
)

我们在讲解 DrawModifier 时有提到 Compose 提供了 Modifier.drawWithContent() 让我们可以自定义绘制的顺序:

@Preview
@Composable
fun CustomText() {
	Text("test", Modifier.drawWithContent {
		// 原有内容绘制前的处理...
		drawRect(Color.Yellow)
		drawContent()  // 绘制原有内容
		// 原有内容绘制后的处理...
		drawLine(Color.Red, Offset(0f, size.height / 2), Offset(size.width, size.height / 2), 2.dp.toPx())
	})
}

在这里插入图片描述

如果想要完全自定义的绘制,可以使用 Box() 加上 Modifier.drawBehind(),或者用 Compose 提供的 Canvas,两者的效果是等价的:

setContent {
	// 两种方式是等价的,只是用 Canvas 更直观一些
	Box(Modifier.size(80.dp).drawBehind {
	
	})
	
	Canvas(Modifier.size(80.dp)) {
	
	}
}

Box.kt

@Composable
fun Box(modifier: Modifier) {
    Layout({}, measurePolicy = EmptyBoxMeasurePolicy, modifier = modifier)
}

Canvas.kt

@Composable
fun Canvas(modifier: Modifier, onDraw: DrawScope.() -> Unit) =
    Spacer(modifier.drawBehind(onDraw)) // Spacer 的处理和 Box 几乎一致

Spacer.kt

@Composable
fun Spacer(modifier: Modifier) {
    Layout({}, modifier) { _, constraints ->
        with(constraints) {
            val width = if (hasFixedWidth) maxWidth else 0
            val height = if (hasFixedHeight) maxHeight else 0
            layout(width, height) {}
        }
    }
}

简单使用 Canvas 实现绘制一个图片旋转的效果:

@Preview
@Composable
fun CustomImage() {
	val image = ImageBitmap.imageResource(R.drawable.avatar)
	Canvas(Modifier.size(100.dp)) {
		// 旋转相关的都在 rotate() 范围,不需要手动代码旋转回来
		// 如果是原生需要你手动处理旋转后再恢复
		// 平面旋转
		rotate(30f) {
			drawImage(image, dstSize = IntSize(size.width.roundToInt(), size.height.roundToInt()))	
		}
	}
}

在这里插入图片描述

所以 Compose 自定义绘制主要有三种方式:

  • Modifier.drawBehind():适用于背景绘制

  • Modifier.drawWithContent():适用于需要控制绘制顺序的场景

  • Box() 和 Modifier.drawBehind() 或 Canvas():适用于需要处理复杂绘制的场景

Compose 复杂绘制降回原生 Canvas

如果是围绕一条坐标轴旋转的,Compose 可以用 Modifier.graphicsLayer() 间接的方式实现:

@Preview
@Composable
fun CustomImage() {
	val image = ImageBitmap.imageResource(R.drawable.avatar)
	// graphicsLayer 控制沿着纵轴或横轴旋转
	// 但是一起设置的话就不是我们想要的效果,所以它只能设置沿着一个轴向旋转
	Canvas(Modifier.size(100.dp).graphicsLayer { rotationX = 45f }) {
		drawImage(image, dstSize = IntSize(size.width.roundToInt(), size.height.roundToInt()))	
	}
}

在这里插入图片描述

但少数情况下 Compose 没有提供的相应的 API,比如说三维旋转效果,沿着多条方向的 Compose 就没有提供 API 了,需要我们自己降回原生 API 即使用 Canvas 实现:

@Preview
@Composable
fun CustomImage() {
	val image = ImageBitmap.imageResource(R.drawable.avatar)
	val paint by remember { mutableStateOf(Paint()) }
	val camera by remember { mutableStateOf(Camera()) }
	val rotateAnimatable = remember { Animatable(0f) }
	LaunchedEffect(Unit) {
		rotateAnimatable.animateTo(360f, infiniteRpeatable(tween(2000))
	}
	Canvas(Modifier.size(100.dp)) {
		// 使用 Compose 的 Canvas 实现复杂的绘制效果
		drawIntoCanvas {
			// it.translate() 配合的 camera.rotateX() 实现三维 x 轴旋转正确
			// it.rotate() 实现沿着对角线旋转
			it.translate(size.width / 2, size.height / 2)
			it.rotate(-45f)
			
			camera.save()
			camera.rotateX(rotationAnimable.value)
			// nativeCanvas 下沉到原生的 Canvas
			camera.applyToCanvas(it.nativeCanvas)
			camera.restore()
			
			it.rotate(-45f)
			it.translate(-size.width / 2, -size.height / 2)
			
			it.drawImageRect(image, dstSize = IntSize(size.width.roundToInt(),
				size.height.roundToInt()), paint = paint)
		}
	}
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值