《Jetpack Compose系列学习》-17 Compose自定义View

1,243 阅读12分钟

自定义View在Android开发中很常见,对于一些复杂的业务需求我们需要继承现有的基础View去实现更复杂的View。Google想尽办法让Compose中的自定义View简单方便些,首先我们从这几个点去学习自定义View:

  • 了解Compose中的Canvas
  • 在Compose中绘制文字、曲线、矩形和路径等
  • 使用混合模式————BlendMode

我们今天先学习:点Point的绘制

了解Compose中的Canvas

我们先学习Canvas的基本概念和工作流程,在Android View中我们自定义View中的绘制方式最常见的就是重写其onDraw方法,我们先看看实例代码:

class CustomView @JvmOverloads constructor(
    context: Context, 
    attrs: AttributeSet? = null, 
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        // 绘制操作
    }
}

先继承一个View,然后在onDraw方法中进行绘制操作,里面有一个Canvas参数,它可以绘制点、线、圆等,还可以使用不同的控制方法来绘制内容的覆盖关系。

通过上面的内容我们知道Canvas在自定义View中扮演者关键的角色,同样,在Compose中也是非常重要,来看看Compose中应该怎么使用Canvas:

@Composable
fun CustomViewTest() {
    Canvas(modifier = Modifier.fillMaxSize()) {
        // 省略
    }
}

可以看到在Compose中使用Canvas和在Android View中使用Canvas的方式完全不同,其实Compose中的Canvas也是一个可组合项。在Compose中万物皆可组合项,来看看Canvas源码:

@Composable
fun Canvas(modifier: Modifier, onDraw: DrawScope.() -> Unit) =
    Spacer(modifier.drawBehind(onDraw))

可以看到Canvas有两个参数,一个是我们很熟悉的修饰符Modifier,另外一个是onDraw,类型是DrawScope块(DrawScope类型的函数引用),和之前的LazyListScope有点像儿,我们之前学过的LazyColumn,想要使用DSL描述列表的内容就离不开其参数LazyListScope块。这里的DrawScope块的作用与之相同,先看看DrawScope接口的定义:

@DrawScopeMarker
interface DrawScope : Density {

    /**
     * 当前的DrawContext,包含创建绘图上下文环境所需的依赖项
     */
    val drawContext: DrawContext

    /**
     * 绘制上下文环境当前边界的中心
     */
    val center: Offset
        get() = drawContext.size.center

    /**
     * 当前绘制上下文环境的大小(可以通过size获取当前Canvas的宽高)
     */
    val size: Size
        get() = drawContext.size

    /**
     * 绘制的方向
     */
    val layoutDirection: LayoutDirection

    /**
     * 绘制线
     */
    fun drawLine(
        brush: Brush,
        start: Offset,
        end: Offset,
        strokeWidth: Float = Stroke.HairlineWidth,
        cap: StrokeCap = Stroke.DefaultCap,
        pathEffect: PathEffect? = null,
        /*FloatRange(from = 0.0, to = 1.0)*/
        alpha: Float = 1.0f,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
    )

    /**
     * 绘制线
     */
    fun drawLine(
        color: Color,
        start: Offset,
        end: Offset,
        strokeWidth: Float = Stroke.HairlineWidth,
        cap: StrokeCap = Stroke.DefaultCap,
        pathEffect: PathEffect? = null,
        /*FloatRange(from = 0.0, to = 1.0)*/
        alpha: Float = 1.0f,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
    )

    /**
     * 绘制矩形
     */
    fun drawRect(
        brush: Brush,
        topLeft: Offset = Offset.Zero,
        size: Size = this.size.offsetSize(topLeft),
        /*FloatRange(from = 0.0, to = 1.0)*/
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
    )

    /**
     * 绘制矩形
     */
    fun drawRect(
        color: Color,
        topLeft: Offset = Offset.Zero,
        size: Size = this.size.offsetSize(topLeft),
        /*@FloatRange(from = 0.0, to = 1.0)*/
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
    )

    /**
     * 绘制图片
     */
    fun drawImage(
        image: ImageBitmap,
        topLeft: Offset = Offset.Zero,
        /*@FloatRange(from = 0.0, to = 1.0)*/
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
    )

    /**
     * 绘制图片
     */
    fun drawImage(
        image: ImageBitmap,
        srcOffset: IntOffset = IntOffset.Zero,
        srcSize: IntSize = IntSize(image.width, image.height),
        dstOffset: IntOffset = IntOffset.Zero,
        dstSize: IntSize = srcSize,
        /*@FloatRange(from = 0.0, to = 1.0)*/
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
    )

    /**
     * 绘制圆角矩形
     */
    fun drawRoundRect(
        brush: Brush,
        topLeft: Offset = Offset.Zero,
        size: Size = this.size.offsetSize(topLeft),
        cornerRadius: CornerRadius = CornerRadius.Zero,
        /*@FloatRange(from = 0.0, to = 1.0)*/
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
    )

    /**
     * 绘制圆角矩形
     */
    fun drawRoundRect(
        color: Color,
        topLeft: Offset = Offset.Zero,
        size: Size = this.size.offsetSize(topLeft),
        cornerRadius: CornerRadius = CornerRadius.Zero,
        style: DrawStyle = Fill,
        /*@FloatRange(from = 0.0, to = 1.0)*/
        alpha: Float = 1.0f,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
    )

    /**
     *绘制圆
     */
    fun drawCircle(
        brush: Brush,
        radius: Float = size.minDimension / 2.0f,
        center: Offset = this.center,
        /*@FloatRange(from = 0.0, to = 1.0)*/
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
    )

    /**
     * 绘制圆
     */
    fun drawCircle(
        color: Color,
        radius: Float = size.minDimension / 2.0f,
        center: Offset = this.center,
        /*@FloatRange(from = 0.0, to = 1.0)*/
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
    )

    /**
     * 绘制椭圆
     */
    fun drawOval(
        brush: Brush,
        topLeft: Offset = Offset.Zero,
        size: Size = this.size.offsetSize(topLeft),
        /*@FloatRange(from = 0.0, to = 1.0)*/
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
    )

    /**
     * 绘制椭圆
     */
    fun drawOval(
        color: Color,
        topLeft: Offset = Offset.Zero,
        size: Size = this.size.offsetSize(topLeft),
        /*@FloatRange(from = 0.0, to = 1.0)*/
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
    )

    /**
     * 绘制圆弧
     */
    fun drawArc(
        brush: Brush,
        startAngle: Float,
        sweepAngle: Float,
        useCenter: Boolean,
        topLeft: Offset = Offset.Zero,
        size: Size = this.size.offsetSize(topLeft),
        /*@FloatRange(from = 0.0, to = 1.0)*/
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
    )

    /**
     * 绘制圆弧
     */
    fun drawArc(
        color: Color,
        startAngle: Float,
        sweepAngle: Float,
        useCenter: Boolean,
        topLeft: Offset = Offset.Zero,
        size: Size = this.size.offsetSize(topLeft),
        /*@FloatRange(from = 0.0, to = 1.0)*/
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
    )

    /**
     * 绘制路径
     */
    fun drawPath(
        path: Path,
        color: Color,
        /*@FloatRange(from = 0.0, to = 1.0)*/
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
    )

    /**
     * 绘制路径
     */
    fun drawPath(
        path: Path,
        brush: Brush,
        /*@FloatRange(from = 0.0, to = 1.0)*/
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
    )

    /**
     * 绘制多个点
     */
    fun drawPoints(
        points: List<Offset>,
        pointMode: PointMode,
        color: Color,
        strokeWidth: Float = Stroke.HairlineWidth,
        cap: StrokeCap = StrokeCap.Butt,
        pathEffect: PathEffect? = null,
        /*@FloatRange(from = 0.0, to = 1.0)*/
        alpha: Float = 1.0f,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
    )

    /**
     * 绘制多个点
     */
    fun drawPoints(
        points: List<Offset>,
        pointMode: PointMode,
        brush: Brush,
        strokeWidth: Float = Stroke.HairlineWidth,
        cap: StrokeCap = StrokeCap.Butt,
        pathEffect: PathEffect? = null,
        /*@FloatRange(from = 0.0, to = 1.0)*/
        alpha: Float = 1.0f,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
    )

    /**
     * 偏移操作
     */
    private fun Size.offsetSize(offset: Offset): Size =
        Size(this.width - offset.x, this.height - offset.y)

    companion object {

        /**
         * 用于每个绘图操作的默认混合模式,可以确保将内容绘制在目标中的像素上方
         */
        val DefaultBlendMode: BlendMode = BlendMode.SrcOver
    }

有些绘制方法具体使用会在下面介绍,先看看DrawScope,它是一个接口,并用@DrawScopeMarker注解修饰(一个DSL标记,用于区分绘图操作和画布转换操作);它继承自Density接口(其中定义了Dp、Px、Int和TextUnit之间的转换);接口内含有4个全局变量;伴生对象中有一个变量,表示绘制操作默认的混合模式,其中混合模式就是在画布上绘制时使用的算法。

Canvas绘制点

任何画面都是由点、线、面组成,其中点是重中之重,也是绘制的基础。在Compose中Canvas可组合项可以使用DSL来绘制,点同样可以使用DSL绘制。绘制点的方法也在DrawScope中定义,如下面所示:

fun drawPoints(
        points: List<Offset>, // 点的集合
        pointMode: PointMode, // 点的绘制方式
        color: Color, // 点的颜色
        strokeWidth: Float = Stroke.HairlineWidth, // 宽度
        cap: StrokeCap = StrokeCap.Butt, // 处理线段末端
        pathEffect: PathEffect? = null, // 该点的可选效果或图案
        /*@FloatRange(from = 0.0, to = 1.0)*/
        alpha: Float = 1.0f, // 透明度
        colorFilter: ColorFilter? = null, // 颜色效果
        blendMode: BlendMode = DefaultBlendMode // 混合模式
    )

一个9个参数,有3个是必须填写的,剩下6个都有默认值,先看看这三个必要参数

  • points points是点的集合,类型为List,Offest用来表示一个坐标点,在构造方法中给其传入横纵坐标即可。

  • pointMode pointMode的意思是点的绘制方式,该参数的类型为PointMode,这个类之前没有见过,先看看它的源码:

@Suppress("INLINE_CLASS_DEPRECATED", "EXPERIMENTAL_FEATURE_WARNING")
@Immutable
inline class PointMode internal constructor(@Suppress("unused") private val value: Int) {
    companion object {
        /**
         * 点
         */
        val Points = PointMode(0)

        /**
         * 线
         */
        val Lines = PointMode(1)

        /**
         * 面
         */
        val Polygon = PointMode(2)
    }

    override fun toString() = when (this) {
        Points -> "Points"
        Lines -> "Lines"
        Polygon -> "Polygon"
        else -> "Unknown"
    }
}

里面一个伴生对象,有三个属性Points、Lines和Polygon。Points表示分别绘制每个点;Lines表示将两个点的每个序列绘制为线段,如果点为奇数,则忽略最后一个点;Polygon表示将整个点序列绘制为一条线。

  • color color作为必须填写的参数其实很好理解,我们需要告诉系统绘制点的颜色,否则无法进行绘制,好,看看实际场景中我们该怎么使用:
@Composable
fun CustomViewTest() {
    val points = arrayListOf(
        Offset(100f, 100f),
        Offset(300f, 300f),
        Offset(500f, 500f),
        Offset(700f, 700f),
        Offset(900f, 900f)
    )
    Canvas(modifier = Modifier.size(360.dp)) {
        drawPoints(
            points = points,
            pointMode = PointMode.Points,
            color = Color.Blue,
            strokeWidth = 30f
        )
    }
}

画布大小为360dp,绘制5个点,颜色设置为蓝色,绘制宽度为30f, 效果如下图:

image.png

下面再看看绘制线段,我们用PointMode.Lines:

Canvas(modifier = Modifier.size(360.dp)) {
        drawPoints(
            points = points,
//            pointMode = PointMode.Points,
            pointMode = PointMode.Lines,
            color = Color.Blue,
            strokeWidth = 30f
        )
    }

image.png

我们只是改了绘制方式,可以看到将每两个点绘制为线段,因为5个点,所以最后一个点呗忽略。

最后我们把绘制方式改成PointMode.Polygon:

Canvas(modifier = Modifier.size(360.dp)) {
        drawPoints(
            points = points,
//            pointMode = PointMode.Points,
//            pointMode = PointMode.Lines,
            pointMode = PointMode.Polygon,
            color = Color.Blue,
            strokeWidth = 30f
        )
    }

image.png

从上图可以看到,将整个点序列集合绘制成一条线。

绘制点可选的参数一共6个:strokeWidth、cap、pathEffect、alpha、colorFilter和blendmode。strokeWidth上面用过了,就是点的宽度;alpha是透明度,0~1之间的值,Float类型;colorFilter在学习Image的时候也用过,可以改变图片的颜色;剩下只有cap、pathEffect和blendmode,blendmode比较复杂,后面会学习,今天先主要看cap和pathEffect。

cap

cap参数类型是StrokeCap,用来处理线段的末端样式,默认值为StrokeCap.Butt。StrokeCap的源码如下:

@Suppress("INLINE_CLASS_DEPRECATED", "EXPERIMENTAL_FEATURE_WARNING")
@Immutable
inline class StrokeCap internal constructor(@Suppress("unused") private val value: Int) {
    companion object {
        
        val Butt = StrokeCap(0)
        
        val Round = StrokeCap(1)

        val Square = StrokeCap(2)
    }

    override fun toString() = when (this) {
        Butt -> "Butt"
        Round -> "Round"
        Square -> "Square"
        else -> "Unknown"
    }
}

和PointMode一样,StrokeCap内也有一个伴生对象,里面有三个属性:Butt表示线段末端轮廓的起始点和结束点带有平缓的边缘,没有延伸;Round表示线段末端以半圆开始和结束的轮廓;Square表示线段末端将每个轮廓延伸笔触宽度的一半。

我们通过一个例子来看看它们三种的具体差别,我们修改上面的例子代码,strokeWidth加宽到50f,设置cap为StrokeCap.Round:

Canvas(modifier = Modifier.size(360.dp)) {
        drawPoints(
            points = points,
//            pointMode = PointMode.Points,
//            pointMode = PointMode.Lines,
            pointMode = PointMode.Polygon,
            color = Color.Blue,
            strokeWidth = 50f,
            cap = StrokeCap.Round
        )
    }

image.png

我们可以看到线段的两端变成了半圆。

现在我们把cap修改为StrokeCap.Square,看看效果:

val points = arrayListOf(
        Offset(100f, 100f),
        Offset(300f, 300f),
        Offset(500f, 500f),
        Offset(700f, 700f),
        Offset(900f, 900f)
    )
    val points2 = arrayListOf(
        Offset(900f, 100f),
        Offset(700f, 300f),
        Offset(500f, 500f),
        Offset(300f, 700f),
        Offset(100f, 900f)
    )
    Canvas(modifier = Modifier.size(360.dp)) {
        drawPoints(
            points = points,
//            pointMode = PointMode.Points,
//            pointMode = PointMode.Lines,
            pointMode = PointMode.Polygon,
            color = Color.Blue,
            strokeWidth = 30f,
            cap = StrokeCap.Butt
        )
        drawPoints(
            points = points2,
//            pointMode = PointMode.Points,
//            pointMode = PointMode.Lines,
            pointMode = PointMode.Polygon,
            color = Color.Blue,
            strokeWidth = 30f,
            cap = StrokeCap.Square
        )
    }

image.png

左上到右下的线段cap为StrokeCap.Butt,右上到左下的cap为StrokeCap.Square,可以看出后者的线段比前者稍微长一点儿,因为Square会将线段的末端延伸笔触宽度的一半。

pathEffect

pathEffect类型是PathEffect,适用于该点的可选效果或图案,默认为null,PathEffect源码:

interface PathEffect {
    companion object {

        /**
         * 将线段之间的锐角替换为指定半径的圆角
         */
        fun cornerPathEffect(radius: Float): PathEffect = actualCornerPathEffect(radius)

        /**
         * 给定间距绘制一系列虚线,并将其偏移到指定间距数组中
         */
        fun dashPathEffect(intervals: FloatArray, phase: Float = 0f): PathEffect =
            actualDashPathEffect(intervals, phase)

        /**
         * 创建将内部效果应用于路径的PathEffect,然后将外部效果应用于内部效果的结果
         */
        fun chainPathEffect(outer: PathEffect, inner: PathEffect): PathEffect =
            actualChainPathEffect(outer, inner)

        /**
         * 通过指定一些特定的形状,并将其标记来绘制路径,这仅适用于笔触形状,填充形状将被忽略
         */
        fun stampedPathEffect(
            shape: Path,
            advance: Float,
            phase: Float,
            style: StampedPathEffectStyle
        ): PathEffect = actualStampedPathEffect(shape, advance, phase, style)
    }
}

可以看到PathEffect是一个接口,接口中只有一个伴生对象,有四个方法,这些方法的返回值都是PathEffect。所以想要苟安PathEffect,直接使用这四个方法即可。在实际需求选用合适的方法,我们来看看几种场景:

  1. 使用Brush绘制渐变 在DrawScope中绘制点其实有两个方法,上面讲到的其中一个,下面还有另外一种方法:
fun drawPoints(
        points: List<Offset>,
        pointMode: PointMode,
        brush: Brush, // 刷子
        strokeWidth: Float = Stroke.HairlineWidth,
        cap: StrokeCap = StrokeCap.Butt,
        pathEffect: PathEffect? = null,
        /*@FloatRange(from = 0.0, to = 1.0)*/
        alpha: Float = 1.0f,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
    )

可以看到这个绘制点的方法和上面讲过的参数基本一致,只是将上面的Color改为了这里的Brush。来猜一下,可以替换Color的参数,肯定也和颜色有关,看看Brush源码:

@Immutable
sealed class Brush {
    abstract fun applyTo(size: Size, p: Paint, alpha: Float)

    companion object {

        /**
         * 使用给定的开始坐标和结束坐标,使用提供的颜色创建线性渐变
         * 颜色分散在色标对中定义的偏移处
         */
        @Stable
        fun linearGradient(
            vararg colorStops: Pair<Float, Color>,
            start: Offset = Offset.Zero,
            end: Offset = Offset.Infinite,
            tileMode: TileMode = TileMode.Clamp
        ): Brush = LinearGradient(
            colors = List<Color>(colorStops.size) { i -> colorStops[i].second },
            stops = List<Float>(colorStops.size) { i -> colorStops[i].first },
            start = start,
            end = end,
            tileMode = tileMode
        )

        /**
         * 使用给定的开始坐标和结束坐标,使用提供的颜色创建线性渐变
         */
        @Stable
        fun linearGradient(
            colors: List<Color>,
            start: Offset = Offset.Zero,
            end: Offset = Offset.Infinite,
            tileMode: TileMode = TileMode.Clamp
        ): Brush = LinearGradient(
            colors = colors,
            stops = null,
            start = start,
            end = end,
            tileMode = tileMode
        )

        /**
         * 创建一个水平渐变,给定的颜色均匀分散在渐变中
         */
        @Stable
        fun horizontalGradient(
            colors: List<Color>,
            startX: Float = 0.0f,
            endX: Float = Float.POSITIVE_INFINITY,
            tileMode: TileMode = TileMode.Clamp
        ): Brush = linearGradient(colors, Offset(startX, 0.0f), Offset(endX, 0.0f), tileMode)

        /**
         * 创建一个水平渐变,给定的颜色分散在色标对中定义的偏移处
         */
        @Stable
        fun horizontalGradient(
            vararg colorStops: Pair<Float, Color>,
            startX: Float = 0.0f,
            endX: Float = Float.POSITIVE_INFINITY,
            tileMode: TileMode = TileMode.Clamp
        ): Brush = linearGradient(
            *colorStops,
            start = Offset(startX, 0.0f),
            end = Offset(endX, 0.0f),
            tileMode = tileMode
        )

        /**
         * 创建一个垂直渐变,给定的颜色均匀地分散在渐变中
         */
        @Stable
        fun verticalGradient(
            colors: List<Color>,
            startY: Float = 0.0f,
            endY: Float = Float.POSITIVE_INFINITY,
            tileMode: TileMode = TileMode.Clamp
        ): Brush = linearGradient(colors, Offset(0.0f, startY), Offset(0.0f, endY), tileMode)

        /**
         * 在Pair参数中定义的偏移处创建具有给定颜色的垂直渐变
         */
        @Stable
        fun verticalGradient(
            vararg colorStops: Pair<Float, Color>,
            startY: Float = 0f,
            endY: Float = Float.POSITIVE_INFINITY,
            tileMode: TileMode = TileMode.Clamp
        ): Brush = linearGradient(
            *colorStops,
            start = Offset(0.0f, startY),
            end = Offset(0.0f, endY),
            tileMode = tileMode
        )

        /**
         * 在色标对中定义的偏移处创建具有给定颜色的径向渐变
         */
        @Stable
        fun radialGradient(
            vararg colorStops: Pair<Float, Color>,
            center: Offset = Offset.Unspecified,
            radius: Float = Float.POSITIVE_INFINITY,
            tileMode: TileMode = TileMode.Clamp
        ): Brush = RadialGradient(
            colors = List<Color>(colorStops.size) { i -> colorStops[i].second },
            stops = List<Float>(colorStops.size) { i -> colorStops[i].first },
            center = center,
            radius = radius,
            tileMode = tileMode
        )

        /**
         * 创建一个径向渐变,给定的颜色均匀地分散在渐变中
         */
        @Stable
        fun radialGradient(
            colors: List<Color>,
            center: Offset = Offset.Unspecified,
            radius: Float = Float.POSITIVE_INFINITY,
            tileMode: TileMode = TileMode.Clamp
        ): Brush = RadialGradient(
            colors = colors,
            stops = null,
            center = center,
            radius = radius,
            tileMode = tileMode
        )

        /**
         * 创建给定颜色围绕中心散布的扫描渐变,并在每个色标对中定义偏移量
         * 扫描从3点钟方向开始,然后顺时针继续,直到再次到达起始位置
         */
        @Stable
        fun sweepGradient(
            vararg colorStops: Pair<Float, Color>,
            center: Offset = Offset.Unspecified
        ): Brush = SweepGradient(
            colors = List<Color>(colorStops.size) { i -> colorStops[i].second },
            stops = List<Float>(colorStops.size) { i -> colorStops[i].first },
            center = center
        )

        /**
         * 创建给定颜色围绕中心散布的扫描渐变
         * 扫描从3点钟方向开始,然后顺时针继续,直到再次到达起始位置
         */
        @Stable
        fun sweepGradient(
            colors: List<Color>,
            center: Offset = Offset.Unspecified
        ): Brush = SweepGradient(
            colors = colors,
            stops = null,
            center = center
        )
    }
}

上面Brush的代码是经过删减的,可以看到删减后的代码依然很多,下面使用下Brush:

Canvas(modifier = Modifier.fillMaxSize()) {
    drawPoints(
        points = points,
        pointMode = PointMode.Polygon,
        brush = Brush.linearGradient(
            colors = arrayListOf(
                Color.Red,
                Color.Green,
                Color.Blue
            )
        ),
        strokeWidth = 30f,
    )
}

代码中使用了常用的线性渐变,通过List设置了红色、绿色和蓝色,看看效果:

image.png

这样线性渐变就绘制完成了,但是颜色是均匀分布的。我们如果需要更加精准的设置每一段颜色,线性渐变也可以做到:

Canvas(modifier = Modifier.size(360.dp)) {
    drawPoints(
        points = points,
        pointMode = PointMode.Polygon,
        brush = Brush.linearGradient( // 精准渐变
            0.0f to Color.Red,
            0.3f to Color.Green,
            0.6f to Color.Yellow,
            1.0f to Color.Blue

        ),
        strokeWidth = 30f,
    )
}

我们同样使用了线性渐变,只不过设置颜色的方式和刚才不同,更加精准地设置每段的渐变颜色,看下效果:

image.png

大家在实际项目中使用渐变的时候,可以根据自己的需求实际需要,选择对应的渐变方法,好了,后面会介绍Canvas绘制线、矩形等,代码已上传:github.com/Licarey/com…