Android View动画实践

Android View动画实践

前言

前面写了下Drawable的实践,这篇文章来写下View动画的实践,不过现在谁还用View动画啊,这里就随便写下吧,最近也忙了些,没太多时间更新博客了。

一般使用

View动画只有四种基本变化,比较简单,注意下一些属性的使用就行:

res/anim/anim_animation_set.xml

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1500"
    android:fillBefore="true"
    android:fillAfter="true"
    android:repeatMode="reverse"
    android:startOffset="100"
    android:shareInterpolator="true">
    <!--  fillBefore,fillAfter,动画结束后保持开始或结束状态  -->

    <scale
        android:fromXScale="0.25"
        android:fromYScale="0.25"
        android:toXScale="1"
        android:toYScale="1"
        android:pivotX="50%"
        android:pivotY="50%"
        />

    <rotate
        android:fromDegrees="0"
        android:toDegrees="360"
        android:pivotX="50%"
        android:pivotY="50%"
        />

    <translate
        android:fromXDelta="0"
        android:fromYDelta="0"
        android:toXDelta="50%p"
        android:toYDelta="0"
        />

    <alpha
        android:fromAlpha="0.25"
        android:toAlpha="1"
        />

</set>

layout使用:

xml 复制代码
<TextView
    android:id="@+id/viewAnimation"
    android:text="ViewAnimation(click)"
    android:background="@drawable/ic_launcher"
    android:layout_width="wrap_content"
    android:layout_height="80dp"
    android:layout_margin="5dp"
    android:clickable="true"
    android:focusable="true"
    tools:ignore="HardcodedText"
    />

代码使用:

java 复制代码
// View动画
val animation = AnimationUtils.loadAnimation(requireContext(), R.anim.anim_animation_set)
binding.viewAnimation.startAnimation(animation)
binding.viewAnimation.setOnClickListener {
    showToast("perform click!")
}

实际效果:

这里要注意下动画改变view位置后,点击的地方是不会变的,和animator不一样。

自定义View动画

本来不想给view动画写一篇文章的,只是看到书上,可以通过Camera搞出3D效果,直接写了一个,所以还是来写了一篇文章。

我这自定义了一个Custom3dIkunAnimation,它有四组变化,唱跳rap篮球,呃,说错了,是X轴平移、Y轴平移、Z轴平移及旋转,比较简单,对于熟悉自定义view动画、Camera、Matrix也算够了,下面看下代码:

java 复制代码
import android.content.Context
import android.graphics.Camera
import android.util.AttributeSet
import android.view.animation.Animation
import android.view.animation.Transformation

class Custom3dIkunAnimation @JvmOverloads constructor(
    val context: Context,
    attrs: AttributeSet? = null
) : Animation(context, attrs) {

    // 背景属性
    private var mWidth: Int = 0
    private var mHeight: Int = 0
    private var mDensity: Float = 1f
    private lateinit var mCamera: Camera

    override fun initialize(width: Int, height: Int, parentWidth: Int, parentHeight: Int) {
        super.initialize(width, height, parentWidth, parentHeight)
        mWidth = width
        mHeight = height
        mCamera = Camera()
        mDensity = context.resources.displayMetrics.density
    }

    override fun applyTransformation(interpolatedTime: Float, t: Transformation) {
        super.applyTransformation(interpolatedTime, t)

        // 图形矩阵
        val matrix = t.matrix

        // 唱
        val toZ = -100 + 200 * interpolatedTime
        // 跳
        val toY = -50 + 100 * interpolatedTime
        // rip
        val toX = -100 + 200 * interpolatedTime
        // 篮球
        val toDegree = 0 + 360f * interpolatedTime

        // 保存状态
        mCamera.save()

        // 执行操作,只支持平移和旋转
        mCamera.translate(toX, toY, toZ)
        mCamera.rotateY(toDegree)
        // 取得变换后的矩阵
        mCamera.getMatrix(matrix)

        // 恢复camera状态
        mCamera.restore()

        // 修复旋转时弹出屏幕问题
        val mValues = floatArrayOf(0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f)
        matrix.getValues(mValues)
        mValues[6] = mValues[6] / mDensity
        matrix.setValues(mValues)

        // 这两行代码的目的是在旋转动画中将矩阵的原点(坐标系的原点)移动到旋转轴心点的位置,
        // 然后在旋转完成后将原点移回到原来的位置,以确保旋转动画的正确性。(缺一不可,前面改变轴点,后者稳定位置)
        //
        // pre是把数据矩阵放前面和变化矩阵相乘,preTranslate方法的效果是在应用其他变换之前改变坐标系的原点位置。
        matrix.preTranslate(-mWidth/2f, -mHeight/2f)
        // post是把数据矩阵放后面和变化矩阵相乘,postTranslate方法的效果是在应用其他变换之后改变坐标系的原点位置。
        matrix.postTranslate(mWidth/2f, mHeight/2f)
    }
}

layout代码:

xml 复制代码
<TextView
    android:id="@+id/custom3dIkunAnimation"
    android:text="Custom3dIkunAnimation(click)"
    android:background="@drawable/ic_launcher"
    android:layout_width="wrap_content"
    android:layout_height="80dp"
    android:layout_gravity="center"
    android:layout_margin="5dp"
    android:clickable="true"
    android:focusable="true"
    tools:ignore="HardcodedText"
    />

代码使用:

java 复制代码
// View动画
val ikunAnimation = Custom3dIkunAnimation(requireContext())
ikunAnimation.duration = 10000
ikunAnimation.repeatCount = 1
ikunAnimation.repeatMode = Animation.REVERSE
var isPlaying = false
ikunAnimation.setAnimationListener(object : Animation.AnimationListener {
    override fun onAnimationStart(animation: Animation?) {
        isPlaying = true
    }

    override fun onAnimationEnd(animation: Animation?) {
        isPlaying = false
    }

    override fun onAnimationRepeat(animation: Animation?) { }
})
binding.custom3dIkunAnimation.startAnimation(ikunAnimation)
binding.custom3dIkunAnimation.setOnClickListener {
    if (!isPlaying) {
        binding.custom3dIkunAnimation.startAnimation(ikunAnimation)
    }
}

实际效果:

看起来还是挺炫酷的,就是Z轴平移看不出来好像。

帧动画

上一篇文章讲了drawable,我还说animation-list慢慢讲呢,后面发现这家伙就是帧动画啊,醉了醉了,哪上一篇的颜色渐变改了下:

res/drawable/ic_animation_drawable.xml

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:visible="true"
    android:oneshot="true"
    >
    <item android:duration="200">
        <shape><solid android:color="#000000FF"/><stroke android:color="@color/green"/></shape>
    </item>
    <item android:duration="200">
        <shape><solid android:color="#220000FF"/> <stroke android:color="@color/green"/></shape>
    </item>
    <item android:duration="200">
        <shape><solid android:color="#440000FF"/> <stroke android:color="@color/green"/></shape>
    </item>
    <item android:duration="200">
        <shape><solid android:color="#660000FF"/> <stroke android:color="@color/green"/></shape>
    </item>
    <item android:duration="200">
        <shape><solid android:color="#880000FF"/> <stroke android:color="@color/green"/></shape>
    </item>
    <item android:duration="200">
        <shape><solid android:color="#AA0000FF"/> <stroke android:color="@color/green"/></shape>
    </item>
    <item android:duration="200">
        <shape><solid android:color="#CC0000FF"/> <stroke android:color="@color/green"/></shape>
    </item>
    <item android:duration="200">
        <shape><solid android:color="#EE0000FF"/> <stroke android:color="@color/green"/></shape>
    </item>
    <item android:duration="200">
        <shape><solid android:color="#FF0000FF"/> <stroke android:color="@color/green"/></shape>
    </item>
</animation-list>

layout代码:

xml 复制代码
<TextView
    android:id="@+id/frameAnimation"
    android:text="FrameAnimation(click)"
    android:background="@drawable/ic_animation_drawable"
    android:layout_width="wrap_content"
    android:layout_height="80dp"
    android:layout_margin="5dp"
    android:clickable="true"
    android:focusable="true"
    tools:ignore="HardcodedText"
    />

代码实现:

java 复制代码
// 帧动画
val frameAnimation = binding.frameAnimation.background as AnimationDrawable
frameAnimation.isOneShot = true
binding.frameAnimation.setOnClickListener {
    frameAnimation.start()
}

实际效果:

比较简单,不多说了。

LayoutAnimation

比较有意思的是ViewGroup都能实现一个layoutAnimation,可以在XML中实现,也可以在代码里面设置,效果不错:

res/anim/anim_layout.xml

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
    android:delay="15%"
    android:animationOrder="normal"
    android:animation="@anim/anim_layout_item"
    />
<!--  单个元素的延迟时间,delay时间全部是300ms,o.5就是150ms,第一个150ms,第二个300ms  -->

layout实现,这里用CardView配合View实现了几个圆来作为child:

xml 复制代码
<LinearLayout
    android:id="@+id/layoutAnimation"
    android:background="@color/gray"
    android:layout_width="match_parent"
    android:layout_height="80dp"
    android:orientation="horizontal"
    android:gravity="center"
    android:layoutAnimation="@anim/anim_layout"
    >

    <androidx.cardview.widget.CardView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:cardCornerRadius="25dp"
        android:layout_margin="5dp">
        <View
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:background="@color/red"
            />
    </androidx.cardview.widget.CardView>

    <androidx.cardview.widget.CardView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:cardCornerRadius="25dp"
        android:layout_margin="5dp">
        <View
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:background="@color/green"
            />
    </androidx.cardview.widget.CardView>

    <androidx.cardview.widget.CardView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:cardCornerRadius="25dp"
        android:layout_margin="5dp">
        <View
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:background="@color/yellow"
            />
    </androidx.cardview.widget.CardView>

    <androidx.cardview.widget.CardView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:cardCornerRadius="25dp"
        android:layout_margin="5dp">
        <View
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:background="@color/purple"
            />
    </androidx.cardview.widget.CardView>

</LinearLayout>

代码实现:

java 复制代码
// LayoutAnimation
val layoutAnimation = AnimationUtils.loadAnimation(requireContext(), R.anim.anim_layout_item)
val control = LayoutAnimationController(layoutAnimation)
control.delay = 0.15f
control.order = LayoutAnimationController.ORDER_NORMAL
binding.layoutAnimation.layoutAnimation = control
// 点击刷新动画
binding.layoutAnimation.setOnClickListener {
    // 注意每次播放要加上标记,配合invalidate才有动画
    binding.layoutAnimation.scheduleLayoutAnimation()
    binding.layoutAnimation.postInvalidate()
}
// 长按删除第一个
binding.layoutAnimation.setOnLongClickListener {
    if (binding.layoutAnimation.childCount > 0) {
        binding.layoutAnimation.scheduleLayoutAnimation()
        binding.layoutAnimation.removeView(binding.layoutAnimation.getChildAt(0))
        return@setOnLongClickListener true
    }
    return@setOnLongClickListener false
}

实际效果:

看起来操作比较麻烦,实际上,要触发一次只需要把FLAG_RUN_ANIMATION加上。

overridePendingTransition

overridePendingTransition可以给activity加上进入和退出动画,估计基本都用过,虽然现在Android14源码已经把它废弃了,换成overrideActivityTransition了,不过就差不多,加了个参数。

下面简单实践下:

res/anim/anim_enter_activity.xml、anim_exit_activity.xml

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    >
    <translate
        android:fromXDelta="100%p"
        android:toXDelta="0%p"
        android:interpolator="@android:anim/decelerate_interpolator"
        />
</set>

这里加了个默认的interpolator,这个后面animator再讲,不过可以点进这个目录去看看,里面还有很多自带的interpolator。

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    >
    <translate
        android:fromYDelta="0%p"
        android:toYDelta="100%p"
        android:interpolator="@android:anim/accelerate_interpolator"
        />
    <!--  @android:anim中包含很多animation动画,及几个插值器  -->
</set>

这里使用百分号p还是挺舒服的,注意下正负号和坐标的关系。

代码实现:

java 复制代码
// Activity动画
binding.activityAnimation.setOnClickListener {
    startActivity(AnimationActivity::class.java)
    requireActivity().overridePendingTransition(R.anim.anim_enter_activity, R.anim.anim_exit_activity)
}

binding.button.setOnClickListener {
    finish()
    overridePendingTransition(R.anim.anim_enter_activity, R.anim.anim_exit_activity)
}

实际效果:

小结

View动画还是很经典的,配合平移、旋转、缩放、渐变的组合可以实现很炫酷的效果,而且实现起来非常简单。

相关推荐
千里马学框架2 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台2 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone2 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc2 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo2 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077002 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼3 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone3 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen3 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone3 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui