你一定会喜欢的 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,还有许多其他用法等待开发者们去探索。

相关推荐
阿巴斯甜11 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker12 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952713 小时前
Andorid Google 登录接入文档
android
黄林晴14 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android