Android Compose 属性动画使用探索详解

目录
  • 前言
  • 使用探索
    • ObjectAnimator 使用探索
    • ValueAnimator 使用探索
    • Compose 函数中使用属性动画
  • 实战
    • 上传开始动画
    • 上传进度动画
    • 上传完成动画
  • 最后

前言

Jetpack Compose(简称 Compose )是 Google 官方推出的基于 Kotlin 语言的 Android 新一代 UI 开发框架,其采用声明式的 UI 编程特性使得 Android 应用界面的编写和维护变得更加简单。

本专栏将详细介绍在使用 Compose 进行 UI 开发中如何实现炫酷的动画效果。动画效果在 App 使用中至关重要,它使得 App 的交互更加自然流畅,用户使用体验更加良好。

在传统的 Android 开发中有古老的 View 动画和目前流行的属性动画,如今 View 动画几乎已被广大开发者所抛弃,属性动画因其可以作用于任何对象的灵活和强大特性而被开发者所拥抱。既然属性动画这么强大,那么它是否能用在 Compose 开发中呢?如果能那跟传统 UI 开发中使用又有什么区别呢?本篇就带领你来探索一下在 Compose 中属性动画的使用。

使用探索

在传统 Android 开发中,属性动画使用得最多的是 ObjectAnimatorValueAnimator,接下来就探索一下在 Compose 中如何使用它们来实现动画效果。

ObjectAnimator 使用探索

首先看一下在传统 Android 开发中如何使用属性动画,比如使用属性动画实现竖直方向向下移动的动画:

val animator = ObjectAnimator.ofFloat(view, "translationY", 10f, 100f)
animator.start()

通过 ObjectAnimator作用于 View 的 translationY属性,不断改变 translationY 的值从而实现动画效果,一个很简单的属性动画,这里就不贴运行效果了。

那在 Compose 中能否使用 ObjectAnimator 呢?

下面使用 Compose 在界面上显示一个 100dp*100dp 的蓝色正方形方块,代码如下:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Box(Modifier.padding(start = 10.dp, top = 10.dp)
                .size(100.dp)
                .background(Color.Blue)
            )
        }
    }
}

运行效果如下:

现在要同样实现一个竖直方向移动的动画效果,让方块从上往下移动。在上面的属性动画实现中 ObjectAnimator是作用于 View 组件上的,按照这个思路在这里 ObjectAnimator 就应该作用于 Box 上,但实际上我们这里压根拿不到 Box 的实例,因为这里的 Box 实际是一个函数且没有返回值,看一下 Box 的源码:

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

既然作用于 Box 上不行,那能不能作用于 State 上呢,Compose 是数据驱动 UI 刷新,通过数据状态改变重组 UI 实现界面的刷新,把上面的 top 提取为一个 State 再通过 ObjectAnimator 去改变是否可行呢?改造代码实验一下:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val topPadding:MutableState<Int> = mutableStateOf(10)

        val animator = ObjectAnimator.ofInt(topPadding, "value", 10, 100)
        animator.duration = 1000

        setContent {
            Box(Modifier.padding(start = 10.dp, top = topPadding.value.dp)
                .size(100.dp)
                .background(Color.Blue)
                // 添加点击事件
                .clickable {
                    // 启动动画
                    animator.start()
                }
            )
        }
    }
}

改造如下:

  • 将之前 top 的固定值提取成了一个 State 变量 topPadding,当 topPadding 的值发生改变时会重组界面从而让界面刷新
  • 声明了 ObjectAnimator 的 animator 变量,作用于 topPadding 的 value 属性上,并设置动画值从 10 到 100,动画时长 1000ms
  • 给 Box 添加点击监听事件启动动画

实际上写完这段代码,编辑器就已经有报错提示了,提示如下:

说没有找到带 Int 参数的 setValue方法,那来看看 MutableState是否有 setValue 方法:

interface MutableState<T> : State<T> {
    override var value: T
    operator fun component1(): T
    operator fun component2(): (T) -> Unit
}

可以发现 MutableState 中是有一个 var 修饰的 value 变量的,说明是有 setValue 方法的,但是错误提示是找不到带 Int 参数的 setValue 方法,实际上 MutableState 的 setValue 的定义应该是这样的:

fun setValue(value:T){
    this.value = value
}

这里参数类型是泛型 T,而 ObjectAnimator 找的是明确的 Int 类型参数的方法,所以找不到。那怎么办呢?是不是就意味着在 Compose 中无法使用 ObjectAnimator 了呢?

直接使用确实是不行,那我们能不能对其进行封装,不是找不到对应的 setValue 方法嘛,那我封装一下提供一个 setValue 方法不就行了。定义一个 IntState类,再提供一个 mutableIntStateOf方法:

class IntState(private val state: MutableState<Int>){
    var value : Int = state.value
        get() = state.value
        set(value) {
            field = value
            state.value = value
        }
}
fun mutableIntStateOf(value: Int, policy: SnapshotMutationPolicy<Int> = structuralEqualityPolicy()) : IntState{
    val state = mutableStateOf(value, policy)
    return IntState(state)
}

IntState构造方法传入一个 MutableState 类型的 state 参数,然后提供一个 value 变量,get 方法返回 state.value ,set 方法将传入值设置给 state.value,这样 IntState 就有了一个明确的 setValue(value:Int) 的方法。

为了便于使用,封装一个 mutableIntStateOf方法,实现里先采用 Compose 提供的 mutableStateOf 方法获取一个 MutableState ,然后用其构建一个 IntState 进行返回。

再改造一下上面动画实现代码将 mutableStateOf替换成 mutableIntStateOf

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 替换为 mutableIntStateOf
        val topPadding = mutableIntStateOf(10)
        // 创建 ObjectAnimator 目标为 topPadding,作用属性为 value,值从 10 变化到 100
        val animator = ObjectAnimator.ofInt(topPadding, "value", 10, 100)
        // 设置动画时长 1s
        animator.duration = 1000

        setContent {
            Box(Modifier.padding(start = 10.dp, top = topPadding.value.dp)
                .size(100.dp)
                .background(Color.Blue)
                // 添加点击事件
                .clickable {
                    // 启动动画
                    animator.start()
                }
            )
        }
    }
}

现在不报错了,运行一下看看是否有动画效果:

效果符合预期,说明这种办法是可行,也说明 ObjectAnimator 在 Compose 中也是可以使用的,只是不能像传统 Android 开发那样直接作用于 View 组件上,而是需要进行二次封装后使用。

ValueAnimator 使用探索

ObjectAnimator 使用探索完了,那么 ValueAnimator能否使用呢?Compose 以声明式的方式通过数据驱动界面刷新,而ValueAnimator主要用于数据的改变,好像很契合的样子,使用 ValueAnimator 不断改变 State 的值理论上就可以实现动画效果。还是上面的例子,改造成使用 ValueAnimator来实现:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 使用 mutableStateOf 创建 topPadding 的 State
        var topPadding by mutableStateOf(10)
        // 创建 ValueAnimator 从 10 变化到 100
        val animator = ValueAnimator.ofInt(10, 100)
        // 动画时长 1s
        animator.duration = 1000
        // 设置监听,当动画改变时动态修改 topPadding 的值
        animator.addUpdateListener {
            topPadding = it.animatedValue as Int
        }
        setContent {
            Box(Modifier.padding(start = 10.dp, top = topPadding.dp)
                .size(100.dp)
                .background(Color.Blue)
                .clickable {
                    animator.start()
                }
            )
        }
    }
}

是否有效果呢?运行一下看看效果:

跟上面使用 ObjectAnimator 实现的效果一致,说明 ValueAnimator 在 Compose 中实现动画是可行的,只是需要手动去监听 ValueAnimator 值的变化然后去动态更新 State 的值,稍微麻烦了一点,实际上我们也可以对其进行封装简化其使用。

通过上面的代码发现,如果要在 Compose 中使用 ValueAnimator 来实现动画,对动画数值的改变进行监听并动态更新 State 的值是必不可少的一步,那么我们就可以将其提取进行封装。

/**
 * @param state 动画作用的目标 State
 * @param values 动画的变化值,可变参数
 */
fun animatorOfInt(state:MutableState<Int>, vararg values: Int) : ValueAnimator{
    // 创建 ValueAnimator ,参数为传入的 values
    val animator = ValueAnimator.ofInt(*values)
    // 添加监听
    animator.addUpdateListener {
        // 更新 state 的 value 值
        state.value = it.animatedValue as Int
    }
    return animator
}

然后将上面的创建动画替换成使用 animatorOfInt 创建:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val topPadding = mutableStateOf(10)
        // 使用封装的 animatorOfInt 方法创建动画
        val animator = animatorOfInt(topPadding, 10, 100)
        animator.duration = 1000
        setContent {
            Box(Modifier.padding(start = 10.dp, top = topPadding.value.dp)
                .size(100.dp)
                .background(Color.Blue)
                .clickable {
                    animator.start()
                }
            )
        }
    }
}

使用是不是要简单很多,不需要手动去处理动画值变化的监听了,有点使用 ObjectAnimator 的感觉,只是不需要指定目标属性。运行效果跟上面一致就不贴图了。

Compose 函数中使用属性动画

前面在 Compose 中使用的动画都是创建在 Compose 函数外面的,如果我们想把这个组件封装成一个独立的 Compose 组件就需要将动画的创建放到 Compose 函数里面,比如将上面的效果封装成一个 AnimationBox组件:

@Composable
fun AnimationBox(){
    val topPadding = mutableStateOf(10)
    val animator = animatorOfInt(topPadding, 10, 100)
    animator.duration = 1000
    Box(modifier = Modifier.padding(start = 10.dp, top = topPadding.value.dp)
        .size(100.dp)
        .background(Color.Blue)
        .clickable {
            animator.start()
        })
}

首先 mutableStateOf 会报错:

意思是在组合过程中创建 state 需要使用 remember,原因是当 state 里的值发生变化时 Compose 会进行重组导致函数重新执行,如果 mutableStateOf 不加 remember则会每次重组都重新创建 state,导致 UI 上使用的值每次都是初始值而得不到刷新。

既然报错那就给他加上 remember:

@Composable
fun AnimationBox(){
    val topPadding = remember { mutableStateOf(10) }
    ...
}

然后在使用的地方直接使用 AnimationBox() 即可:

setContent {
    AnimationBox()
}

运行后发现效果跟之前一样,那是不是就可以了呢?

实际上上面的代码是还存在问题的,前面说在 Compose 重组时会重新执行 Compose 组件的代码,也就是在界面刷新时会多次重复创建动画对象,我们在 animatorOfInt 函数里添加一个日志再看看运行时的日志输出:

fun  animatorOfInt(state:MutableState<Int>, vararg values: Int) : ValueAnimator{
    println("-------call animatorOfInt--------")
  ...
}

输出结果:

I/System.out: -------call animatorOfInt--------
I/System.out: -------call animatorOfInt--------
I/System.out: -------call animatorOfInt--------
I/System.out: -------call animatorOfInt--------
I/System.out: -------call animatorOfInt--------
I/System.out: -------call animatorOfInt--------
I/System.out: -------call animatorOfInt--------
I/System.out: -------call animatorOfInt--------
I/System.out: -------call animatorOfInt--------
I/System.out: -------call animatorOfInt--------
I/System.out: -------call animatorOfInt--------
I/System.out: -------call animatorOfInt--------
I/System.out: -------call animatorOfInt--------
I/System.out: -------call animatorOfInt--------
I/System.out: -------call animatorOfInt--------

日志确实输出了多次,意味着动画确实创建了多次,那怎么解决呢?

前面说了 remember可以解决重组时重复创建的问题,所以只需在创建动画上套上 remember即可,如下:

val animator = remember { animatorOfInt(topPadding, 10, 100) }

修改后再看日志,发现就只在第一次进行了创建,动画执行过程中并没有再次创建。

为了方便使用,可以再封装一个 rememberAnimatorOfInt方法:

@Composable
fun rememberAnimatorOfInt(state:MutableState<Int>, vararg values: Int) : ValueAnimator{
   return remember { animatorOfInt(state, *values) }
}

在 animatorOfInt 上套了一个 remember,这样使用时就可以直接使用 rememberAnimatorOfInt 方法:

val animator = rememberAnimatorOfInt(topPadding, 10, 100)

remember 是 Compose 提供的在 Compose 函数中缓存状态的方法,解决在 Compose 重组时重复创建的问题,关于 remember 更多使用大家可以自行查询相关资料,本专栏主要讲解动画的使用就不过多赘述。

实战

前面介绍了属性动画在 Compose 中的运用,那在实际开发中到底好不好用呢?接下来我们通过一个实例来看看。

先看一下最终实现的效果:

一个上传按钮的动画效果,动画主要分为三阶段:

  • 上传开始
  • 按钮从圆角矩形变成圆形
  • 按钮颜色从蓝色变成中间白色,边框灰色
  • 文字逐渐消失
  • 上传进度
  • 边框根据进度变为蓝色
  • 上传完成
  • 按钮从圆形变成圆角矩形
  • 按钮颜色变成红色
  • 文字逐渐显示,且文字变为 “Success”

上传开始动画

先把按钮的初始状态使用 Compose 实现:

@Composable
fun UploadButton() {
    Box(
        modifier = Modifier
            .padding(start = 10.dp, top = 10.dp)
            .width(180.dp),
        contentAlignment = Alignment.Center
    ) {
        Box(
            modifier = Modifier
                .clip(RoundedCornerShape(24.dp))
                .background(Color.Blue)
                .size(180.dp, 48.dp),
            contentAlignment = Alignment.Center,
        ) {
            Text("Upload", color = Color.White)
        }
    }
}

运行效果如下:

下面就为这按钮添加动画,前面讲了动画主要作用于 State 上,所以需要先将使用到的数据提取成对应的状态:

@Composable
fun UploadButton() {
    val originWidth = 180.dp
    val circleSize = 48.dp
    var text by remember { mutableStateOf("Upload") }
    val textAlpha = remember { mutableStateOf(1.0f) }
    val backgroundColor = remember { mutableStateOf(Color.Blue) }
    val boxWidth = remember { mutableStateOf(originWidth) }
    Box(
        modifier = Modifier
            .padding(start = 10.dp, top = 10.dp)
            .width(originWidth),
        contentAlignment = Alignment.Center
    ) {
        Box(
            modifier = Modifier
                .clip(RoundedCornerShape(height/2))
                .background(backgroundColor.value)
                .size(boxWidth.value, height),
            contentAlignment = Alignment.Center,
        ) {
            Text(text, color = Color.White, modifier = Modifier.alpha(textAlpha.value))
        }
    }
}

创建开始上传的动画:

@Composable
fun UploadButton() {
  ...
    val uploadStartAnimator = remember {
        // 创建 AnimatorSet
        val animatorSet = AnimatorSet()
        // 按钮宽度变化动画
        val widthAnimator = animatorOfDp(boxWidth, arrayOf(originWidth, circleSize))
        // 文字消失动画
        val textAnimator = animatorOfFloat(textAlpha, 1f, 0.0f)
        // 按钮颜色动画
        val colorAnimator = animatorOfColor(backgroundColor, arrayOf(Color.Blue, Color.Gray))
        // 动画添加到 AnimatorSet
        animatorSet.playTogether(widthAnimator, textAnimator, colorAnimator)
        animatorSet
    }
    Box(...) {
        Box(
            modifier = Modifier
                ...
                .clickable {
                    // 点击执行动画
                    uploadStartAnimator.start()
                },
            ...
        )
    }
}

分别创建按钮宽度、按钮颜色和文字 alpha 值变化的动画,因需同时执行多个动画,这里使用 AnimatorSet 进行同时执行,然后在按钮上添加点击事件进行动画执行。

上面的 animatorOfDpanimatorOfFloatanimatorOfColor都是自定义封装的函数,封装方法与上面介绍的 animatorOfInt基本相同,源码可通过文章最后附的源码地址进行查看。

运行效果如下:

好像还差点,中间应该是白色的,在 Box 下再添加一个白色圆形的 Box,默认 alpha 是 0,上传开始时 alpha 从 0 变成 1 :

@Composable
fun UploadButton() {
    ...
    val progressAlpha = remember { mutableStateOf(0.0f) }
    val uploadStartAnimator = remember {
        ...
        // 中间白色透明度变化动画
        val centerAlphaAnimator = animatorOfFloat(progressAlpha, 0.0f, 1f)
        animatorSet.playTogether(widthAnimator, textAnimator, colorAnimator, centerAlphaAnimator)
        animatorSet
    }
    Box(...) {
        Box(...) {
            // 白色圆形
            Box(
                modifier = Modifier.size(40.dp).clip(RoundedCornerShape(20.dp))
                    .alpha(progressAlpha.value).background(Color.White)
            )
            Text(text, color = Color.White, modifier = Modifier.alpha(textAlpha.value))
        }
    }
}

运行效果如下:

上传进度动画

这里通过自定义 clip 的一个弧形的 shape 来实现进度,自定义代码如下:

class ArcShape(private val progress: Int) : Shape {
    override fun createOutline(
        size: Size,
        layoutDirection: LayoutDirection,
        density: Density
    ): Outline {
        val path = Path().apply {
            moveTo(size.width / 2f, size.height / 2f)
            arcTo(Rect(0f, 0f, size.width, size.height), -90f, progress / 100f * 360f, false)
            close()
        }
        return Outline.Generic(path)
    }
}

传入一个进度值(0-100),然后根据进度值算出一个绘制的弧度,使用这个自定义的 ArcShape 代码如下:

 Box(Modifier.size(48.dp).clip(ArcShape(30)).background(Color.Blue))

效果:

所以只需动态改变 ArcShape 的 progress 参数的值就能实现上传进度效果,修改代码如下:

@Composable
fun PreviewUploadButton() {
    ...
    val progress = remember { mutableStateOf(0) }
    //上传进度动画
    val progressAnimator = remember {
        val animator = animatorOfInt(progress, 0, 100)
        animator.duration = 1000
        animator
    }
    val uploadStartAnimator = remember {
        ...
        // 添加动画监听,完成后执行进度动画
        animatorSet.addListener(onEnd = {
            progressAnimator.start()
        })
        animatorSet
    }
    Box(...) {
        Box(...) {
            // 进度 Box
            Box(
                modifier = Modifier.size(height).clip(ArcShape(progress.value))
                    .alpha(progressAlpha.value).background(Color.Blue)
            )
            ...
        }
    }
}

运行效果:

上传完成动画

最后是上传完成动画就很简单了,基本就是开始动画的反向,只是按钮颜色从蓝色变成了红色,动画在上传进度动画完成时执行:

@Composable
fun PreviewUploadButton() {
    ...

    val endAnimatorSet = remember {
        val animatorSet = AnimatorSet()
        val widthAnimator = animatorOfDp(boxWidth, arrayOf(circleSize, originWidth))
        val centerAnimator = animatorOfFloat(progressAlpha, 1f, 0f)
        val textAnimator = animatorOfFloat(textAlpha, 0f, 1f)
        val colorAnimator = animatorOfColor(backgroundColor, arrayOf(Color.Blue, Color.Red))
        animatorSet.playTogether(widthAnimator, centerAnimator, textAnimator, colorAnimator)
        animatorSet.addListener(onStart = {
            text = "Success"
        })
        animatorSet
    }
    val progressAnimator = remember {
        val animator = animatorOfInt(progress, 0, 100)
        animator.duration = 1000
        animator.addListener(onEnd = {
            endAnimatorSet.start()
        })
        animator
    }
    ...
}

最终效果:

最后

通过本篇文章的探索可以发现属性动画在 Compose 中确实是可以使用的,虽然跟传统 UI 开发中使用属性动画有所区别,但确实能用,而且通过一个简单的实战示例发现好像还挺好用的。好了,我已经学会 Compose 的动画开发了,什么?Compose 还单独提供了一套动画 API ?

属性动画这不是挺好使的么,这不是多此一举么,难道 Compose 的动画 API 比属性动画还好用、还强大?如果感兴趣请关注本专栏,从下一篇开始带你真正走进 Compose 的动画世界。

源码地址:ComposeAnimationDemo

以上就是Android Compose 属性动画使用探索详解的详细内容,更多关于Android Compose 属性动画的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android开发Compose集成高德地图实例

    目录 正文 高德地图官网开发者建议 初始化MapView并添加到AndroidView里面 MapView增加一个管理地图生命周期的扩展 给MapView添加生命周期观察者 添加MapView的生命周期控制 正文 Compose中我们应该怎么使用地图呢?像之前我们在xml里面创建MapView,都是在Activity里面,管理MapView生命周期,和其他的监听器,Compose里面怎么搞? 下面我们以高德地图为例,在Compose中创建地图MapView,然后用AndroidView添加Map

  • Android Compose Column列表不自动刷新问题

    目录 1. 背景 2. 解决方案 3. 原因 4. 结论 4.1 解决方案一 4.2 解决方案二 5.自己实现一个mutableStateOf() 1. 背景 我们都知道,Compose可以使用mutableStateOf和UI进行绑定,改变值之后,就可以改变UI. var value by remember { mutableStateOf(0) } var imageVisible by remember { mutableStateOf(true) } Column { Text(text

  • Android View与Compose互相调用实例探究

    目录 1. 前言 2. Android传统View调用Compose 2.1 新建传统View体系的Android项目 2.2 项目添加Compose配置 2.2.1 在android代码块添加 2.2.2 在dependencies中添加依赖 2.3 定义Compose函数 2.4 修改xml文件 2.5 关联Compose函数 2.6 运行项目 3. Compose中调用Android View 3.1 调用传统View的日历 3.1.1 使用AndroidView 3.1.2 显示效果如下

  • Android开发Compose框架使用开篇

    目录 Compose的诞生 Compose好处 Compose 架构 @Composable的背后 智能重组真的那么智能吗 最后 Compose的诞生 在2019年的谷歌IO大会上,Compose作为Android新一代UI开发亮相,因为声明式开发越来越流行了,对标IOS开发SwiftUi,Compose的立项也为Android开发新加了声明式ui的开发选项,在2021年7月1.0正式版本的诞生,也意味着Compose即将进入生产环节,国际app巨头Twitter就首当其冲,在新页面上用上了Co

  • Android compose气泡升起和水滴下坠动画实现示例

    目录 摘要 知识点 解析 代码实现 动画绘制 结构 circle to bubble 摘要 今天用compose来构建一个气泡上升粘连动画和水滴下坠动画,Github源码点击这里 知识点 compose动画 贝塞尔曲线 缓动函数 compose canvas 解析 compose动画使用updateTransition,理由是: updateTransition可以管理多个动画作为子项,并且可以在多个状态间同时运行 这个动画恰巧需要维护两个状态,自定义状态数据Circle和Bubble 贝塞尔曲

  • Android Compose衰减动画Animatable使用详解

    目录 前言 animateDecay splineBasedDecay rememberSplineBasedDecay exponentialDecay 实战 最后 前言 之前介绍了 Animatable 动画以及其 animateTo和 snapTo两个开启动画 api 的使用,实际上 Animatable 除了这两个 api 以外还有一个 animateDecay即本篇要介绍的衰减动画. 什么是衰减动画呢?就是动画速度由快到慢最后停止,最常见的应用场景就是惯性动画,比如滑动列表时手指松开后

  • Jetpack Compose自定义动画与Animatable详解

    目录 AnimationSpec 1.spring 2.tween 3.keyframes 4.repeatable 5.snap Animatable 本篇主要是自定义动画与Animatable. AnimationSpec 上一篇中,出现了多次animationSpec属性,它是用来自定义动画规范的.例如: fun Modifier.animateContentSize( animationSpec: FiniteAnimationSpec<IntSize> = spring(), fin

  • Android 逐帧动画创建实例详解

    Android 逐帧动画创建实例详解 前言: 我们看早期电影的时候,电影通常是一张一张播放,用我们现在专有名词来说,就是一帧帧来,安卓同样有这样动画效果的编排形式. 那么我们先定义逐帧动画xml文件 <?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" an

  • Android Compose状态改变动画animateXxxAsState使用详解

    目录 前言 animateXxxAsState 基础使用 动画监听 使用示例 animateFloatAsState animateIntAsState animateColorAsState animateSizeAsState/animateIntSizeAsState animateOffsetAsState/animateIntOffsetAsState animateRectAsState 实战 最后 前言 上一篇文章我们探索了 Compose 中属性动画的使用,发现属性动画确实是可以

  • Android Flutter实现3D动画效果示例详解

    目录 前言 AnimatedWidget 简介 3D 旋转动画的实现 总结 前言 上一篇我们介绍了 Animation 和 AnimationController 的使用,这是最基本的动画构建类.但是,如果我们想构建一个可复用的动画组件,通过外部参数来控制其动画效果的时候,上一篇的方法就不太合适了.在 Flutter 中提供了 AnimatedWidget 组件用于构建可复用的动画组件.本篇我们用 AnimatedWidget 来实现组件的3D 旋转效果,如下图所示. AnimatedWidge

  • Android 帧动画的实例详解

    Android 帧动画的实例详解 对于 Android 帧动画 大体上可以理解成 一张张图片 按一定顺序切换, 这样当连续几张图是一组动画时,就可以连起来了看成是一个小电影,你懂得 好得,比就装到这里,下面开始进入正题,由于产品需求 需要做一个 声音喇叭动态切换的样式,我特么第一就想到是帧动画切换,然后就百度了一些资料,发现 真的, 现在这个网上太多的资料是 copy粘贴过来的, 一错全错,对于这种情况我只想说,made,一群垃圾, 所以今天我将带你们走进Android 正确帧动画地址. 第一步

  • ​​​​​​​Android H5通用容器架构设计详解

    目录 背景 术语对齐 探索 如何优雅地提供接口调用? 怎样封装多个不同类型的H5容器容器? 整体架构 通用容器 框架容器 基础组件 这样的架构能带来什么样的好处? 背景 大家如果经历过Hybrid项目的开发,即项目中涉及到H5与Native之间的交互,那么很有可能会遇到各种各样的H5容器.为什么会有那么多各种各样的容器呢...这也是轮子多的通病了,轮子多到业务方不知道选哪个.当然,也有可能大家压根就不会使用到H5容器,直接用系统WebView就完事儿了,比如我的前东家就是这样做的.那这篇文章的主

  • Android自定义View实现体重表盘详解流程

    目录 效果视频 分析 起始角度 圆弧 指针 代码 初始化属性 画布 绘制内圆弧 绘制外圆弧 绘制中间指针 绘制中间文字 绘制左右两边文字 动画 全部代码 下载链接 效果视频 分析 起始角度 如下图所示,起点角度为150,终点角度为240 圆弧 白色圆弧为整个圆弧范围,蓝色圆弧为根据数据变动而覆盖白色圆弧,蓝色圆弧比白色圆弧大一点,突出显示 InnerArcPaint.setStrokeWidth( Width * (float)0.1 ); OuterArcPaint.setStrokeWidt

  • Android View的事件体系教程详解

    目录 一.什么是View?什么是ViewGroup? 二.View的位置 三.View的触摸事件 1.MotionEvent 2.TouchSlop 3.VelocityTracker 5.Scroller 四.View的滑动 1)使用Scroll 2)通过动画 3)使用延时策略 五.View的事件分发机制 六.View的滑动冲突问题 View的滑动冲突常见可以简单分为三种: 滑动冲突的处理规则 滑动冲突的解决方法 一.什么是View?什么是ViewGroup? View是Android中所有控

  • Android TabLayout 自定义样式及使用详解

    目录 基本使用 XML静态设置TabItem 联动ViewPager2动态设置TabItem 1. Activity布局代码 2. 创建三个Fragment给ViewPager2设置 3. Fragment对应XML布局 4. 绑定起来 最终效果 根据数据源动态生成TabItem 1.Activity布局代码 2. Activity代码 最终效果 修改TabLayout背景颜色 修改indicator layer-list 制作圆形的indicator 制作圆角矩形indicator 修改边距

随机推荐