你一定会喜欢的 Compose 形变动画

如果你想在 Compose 上实现两个几何形状之间做形变,那么 Morph,你一定不要错过!

24 年八月份的时候,Androidx 发布了 androidx.graphics:graphics-shapes 的第一个正式版本,该库可让开发者创建由多边形构成的图形。

虽然多边形图形仅包含直线边和尖角,但该库支持圆角。

传统意义上在任意图形之间进行变形较为困难,当然,工具是一方面,另一方面,设计的表达很多时候也不够清晰。

该库通过在具有相似多边形结构的图形之间进行变形,使这一过程变得简单。

闲言少叙,我们立即开始体验。

创建多边形

下面的代码片段展示了如何创建一个简单的多边形:

Kotlin 复制代码
Box(
    modifier = Modifier
        .size(200.dp)
        .drawWithCache {
            val roundedPolygon = RoundedPolygon(
                numVertices = 5,
                radius = size.minDimension / 2,
                centerX = size.width / 2,
                centerY = size.height / 2
            )
            val roundedPolygonPath = roundedPolygon.toPath().asComposePath()
            onDrawBehind {
                drawPath(roundedPolygonPath, color = Color.Blue)
            }
        }
)

上述代码,我们会创建一个简单的五边形,

如果你喜欢圆角,可以这样做:

Kotlin 复制代码
//...
val roundedPolygon = RoundedPolygon(
    numVertices = 5,
    radius = size.minDimension / 2,
    centerX = size.width / 2,
    centerY = size.height / 2,
    rounding = CornerRounding(40f) // 在这里设置圆角
)
//...

如果仔细查看 CornerRounding,你会发现它还有第二个参数,smoothing------平滑度。

Kotlin 复制代码
@FloatRange(from = 0.0, to = 1.0) val smoothing: Float = 0f

平滑度决定角的圆角部分如何过渡到边缘。它的设置返回从 0.01.0

这里五边形不够明显,我们使用三角形举例子:

Kotlin 复制代码
val roundedPolygon = RoundedPolygon(
    numVertices = 3,
    radius = size.minDimension / 2,
    centerX = size.width / 2,
    centerY = size.height / 2,
    rounding = CornerRounding(60f, smoothing = 0f) // 默认的 0 平滑度
)
Kotlin 复制代码
val roundedPolygon = RoundedPolygon(
    numVertices = 3,
    radius = size.minDimension / 2,
    centerX = size.width / 2,
    centerY = size.height / 2,
    rounding = CornerRounding(60f, smoothing = 1f)// 最大的平滑度
)

仔细查看圆角的过渡部分,还是有区别的。smoothing 值越大,感觉圆角更加钝一点,整个图像也更加的圆滑。

形状裁剪

如果形状只能用来显示,那确实鸡肋。我们可以使用形状,对现有的 UI 元素进行裁剪。

为了更好的使用新装,我们先来优化一下代码:

Kotlin 复制代码
val roundedPolygon = RoundedPolygon(
    numVertices = 3,
    radius = size.minDimension / 2,
    centerX = size.width / 2,
    centerY = size.height / 2,
    rounding = CornerRounding(60f, smoothing = 1f)
)

这段代码使我们不得不使用当前元素的尺寸信息,导致我们一开始不能定义形状,而必须在 drawWithCache 或者 graphicsLayer 等能够获取尺寸的作用域中使用。

为了更加广泛的使用形状,我们定义一个形状转换的方式,能够将 RoundedPolygon 这样的形状应用在任何尺寸的元素上。

Kotlin 复制代码
class PathShape(
    private val path: android.graphics.Path, // 接受一个 path
    private var matrix: Matrix = Matrix() // 自定义 matrix
) : Shape {
    private var composePath = androidx.compose.ui.graphics.Path()
    override fun createOutline(
        size: Size,
        layoutDirection: LayoutDirection,
        density: Density
    ): Outline {
        composePath.rewind()
        composePath = path.asComposePath() // path 转换,内部逻辑非常简单
        matrix.reset()
        matrix.translate(size.width / 2, size.height / 2) // 移动到元素的中间
        matrix.scale(size.minDimension, size.minDimension) // 按照元素的最小尺寸进行缩放

        composePath.transform(matrix) // 应用变换
        return Outline.Generic(composePath) // 创建尺寸
    }
}

现在,我们展示一段代码,让形状可以作用在一个 Image 元素上:

Kotlin 复制代码
val roundedPolygon = remember {
    RoundedPolygon(
        numVertices = 3,
        radius = 0.5f, // 这里不再使用具体尺寸,而是使用元素大小的比例
        rounding = CornerRounding(radius = 0.12f, smoothing = 1f)// 同样,radius 也是比例
    )
}

val clipShape = remember(roundedPolygon) {
    PathShape(roundedPolygon.toPath())
}

Image(
    painter = painterResource(R.drawable.plant), modifier = Modifier
        .size(200.dp)
        .graphicsLayer {
            clip = true
            shape = clipShape // 应用形状
        },
    contentDescription = null
)

非常棒!

形变动画

如果只能定义形状,还不足以吸引开发者。

真正让我们感到惊叹的其实是动画------从形状到另一个形状。

首先,我们定义两个形状:

Kotlin 复制代码
// 五边形
val startPolygon = remember {
    RoundedPolygon(
        numVertices = 5,
        radius = 0.5f,
        rounding = CornerRounding(1f / 6f)
    )
}
// 十边形
val endPolygon = remember {
    RoundedPolygon(
        numVertices = 10,
        radius = 0.5f,
        rounding = CornerRounding(1f / 6f)
    )
}

然后,定义形变:

Kotlin 复制代码
val morph = remember {
    Morph(startPolygon, endPolygon)
}

此时,我们就拥有了从五边形变化到十边形的能力。

然后定义一个 android.graphics.Path(),用于存储形变后的 Path,也就是路径。

Morph 在形变的时候,需要使用 toPath 方法。该方法第一个参数是进度------progress,它是一个 [0-1] 的浮点数类型,0 就表示开始的形状,1 表示结束的形状,中间的任何值,表示开始形状到结束形状的过渡。

形变的基本要素已经到齐了,接下来,开始你的表演:

Kotlin 复制代码
val startPolygon = remember {
    RoundedPolygon(
        numVertices = 5,
        radius = 0.5f,
        rounding = CornerRounding(1f / 6f)
    )
}

val endPolygon = remember {
    RoundedPolygon(
        numVertices = 10,
        radius = 0.5f,
        rounding = CornerRounding(1f / 6f)
    )
}

val morph = remember {
    Morph(startPolygon, endPolygon)
}

val androidPath = remember {
    android.graphics.Path()
}

val interactionSource = remember { MutableInteractionSource() }

val pressed by interactionSource.collectIsPressedAsState() // 使用按下作为触发机制

val progress by animateFloatAsState(if (pressed) 1f else 0f) // 按下的时候变成十边形,默认状态是五边形

Image(
    painter = painterResource(R.drawable.plant), modifier = Modifier
        .size(200.dp)
        .graphicsLayer {
            clip = true
            shape = PathShape(morph.toPath(progress, androidPath)) // 形变
        }
        .clickable(enabled = true, indication = null, interactionSource = interactionSource) { },
    contentDescription = null
)

当我们按下的时候,该图片会变成十边形,当我们松开的时候,会回到五边形。

一个简单的录制按钮

参考上面的用法,我们可以简单的做一个拍照功能的录像按钮。

按钮默认状态是圆形,当开始录制的时候,是四边形。

Kotlin 复制代码
// 圆形
val startPolygon = remember {
    RoundedPolygon.circle(numVertices = 12, radius = 0.4f)
}
// 四边形
val endPolygon = remember {
    RoundedPolygon.rectangle(0.28f,0.28f, rounding = CornerRounding(radius = 0.1f))
}
// 形变
val morph = remember {
    Morph(startPolygon, endPolygon)
}

val androidPath = remember {
    android.graphics.Path()
}

// 记录当前状态是否在播放
var recording by remember { mutableStateOf(false) }

// 形变进度
val progress by animateFloatAsState(if (recording) 1f else 0f)

Spacer(
    modifier = Modifier.graphicsLayer {
            clip = true
            shape = PathShape(morph.toPath(progress, androidPath))
        }
        .size(200.dp)
        .background(Brush.linearGradient(0f to Color(red = 137, green = 247, blue = 197), 1f to Color(red = 192, green = 255, blue = 122)))// 添加一个渐变色,让图形更好看
        .clickable {
        recording = !recording
    }
)

总结

全新的图形库为安卓系统带来了一系列全新的形状应用可能性。在本文的示例中,我们通过基本形状,展示了裁剪以及形变动画。

借助这些创建形状和形变的新 API,还有许多其他用法等待开发者们去探索。

相关推荐
叽哥42 分钟前
Kotlin学习第 1 课:Kotlin 入门准备:搭建学习环境与认知基础
android·java·kotlin
风往哪边走1 小时前
创建自定义语音录制View
android·前端
用户2018792831671 小时前
事件分发之“官僚主义”?或“绕圈”的艺术
android
用户2018792831671 小时前
Android事件分发为何喜欢“兜圈子”?不做个“敞亮人”!
android
QuZhengRong3 小时前
【数据库】Navicat 导入 Excel 数据乱码问题的解决方法
android·数据库·excel
zhangphil4 小时前
Android Coil3视频封面抽取封面帧存Disk缓存,Kotlin(2)
android·kotlin
程序员码歌11 小时前
【零代码AI编程实战】AI灯塔导航-总结篇
android·前端·后端
书弋江山12 小时前
flutter 跨平台编码库 protobuf 工具使用
android·flutter
来来走走15 小时前
Flutter开发 webview_flutter的基本使用
android·flutter