Android 入门(四)动画和手势

本文聚焦于Android开发,介绍了视图动画和手势操作。视图动画包括补间动画和逐帧动画,补间动画有scale、rotate等标签,还可通过代码生成;逐帧动画用animation - list标签。手势操作涵盖普通手势和缩放手势,普通手势可借助GestureDetector简化处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

知识点摘要:掌握 视图动画、属性动画、布局动画、手势。

在 Android 中动画分为属性动画(Property Animation)和视图动画(View Animation)。

View 动画

视图动画框架支持补间和逐帧动画,它们都可以用XML声明。

补间动画(Tween animation)

补间动画就是以 XML 格式定义的动画,用于执行图形上的旋转,淡入淡出,移动和拉伸等过渡。

在 Android 中的补间动画有四种 rotate、alpha、translate、scale,使用方法也很简单,我们只需要在 res/anim 创建一个对应的 xml 文件。如:scaleanim.xml

所有的补间动画都是继承自 Animation,想要用好动画,当然需要掌握 Animation 的重要的属性:

  • duration: 动画的持续时间,单位 ms;
  • fillAfter: 保持动画结束的状态;
  • fillBefore: 还原初始化的状态;
  • fillEnable: 与 fillBefore 一样;
  • repeatCount: 动画循环次数,包括初始的那一次,如设置为 3,实际会播放四次动画;
  • repeatMode: 动画的重复模式,只有在设置了 repeatCount 才有效果。提供了两个值,reverse 表示倒序播放动画;restart 表示顺序播放动画;
  • interpolator: 插值器,指定 view 需要执行的操作,可以取的值有如下这些:
    • AccelerateDecelerateInterpolator:在动画开始与结束的地方速率改变比较慢,在中间的时候加速;
    • AccelerateInterpolator:在动画开始的地方速率改变比较慢,然后开始加速;
    • AnticipateInterpolator: 开始的时候向后,然后向前甩;
    • AnticipateOvershootInterpolator:开始的时候向后,然后向前甩一定值后返回最后的值;
    • BounceInterpolator:动画结束的时候弹起;
    • CycleInterpolator:动画循环播放特定的次数,速率改变沿着正弦曲线;
    • DecelerateInterpolator: 在动画开始的地方快然后慢;
    • LinearInterpolator: 以常量速率改变;
    • OvershootInterpolator: 向前甩一定值后, 再回到原来位置。

学习了从 Animation 继承的属性之后,我当然要学习自身的属性,下面分别进行介绍。

scale 标签

scale 标签是缩放动画,可以实现动态调节控件尺寸的效果。主要有下面几个属性:

  • fromXScale: 起始相对 x 方向的长度的缩放比例;
  • fromYScale: 起始相对 y 方向的长度的缩放比例;
  • toXScale: 结束相对 x 方向的长度的缩放比例;
  • toYScale: 结束相对 y 方向的长度的缩放比例;
  • pivotX: 起始点的 x 坐标,可以是 数值、百分数和百分数p。如 50,50%,50%p。其中 50%p 为当前控件的左上角坐标加上父控件的宽度的 50%,其值作为起始坐标。
  • pivotY: 起始点的 y 坐标。

scale 标签的使用方法也很简单,在 res/anim 下创建 scaleanim.xml 文件

<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="500"
    android:fillEnabled="true"
    android:fromXScale="0.0"
    android:fromYScale="0.0"
    android:interpolator="@android:anim/overshoot_interpolator"
    android:pivotX="50%"
    android:pivotY="50%"
    android:repeatCount="0"
    android:repeatMode="reverse"
    android:toXScale="1.4"
    android:toYScale="1.4" />
复制代码

创建方式相当简单,但是我们要怎么使用呢?先用 AnimationUtils.loadAnimatio 方法解析 xml 文件,然后在控件上设置 startAnimation,如:

mScaleAnimation = AnimationUtils.loadAnimation(this, R.anim.scaleanim)
text_view.startAnimation(mScaleAnimation)
复制代码

最后的效果应该是以中间点进行缩放,并且会放大到比 1.4 倍大,然后再回到 view 的本来大小。最好自己进行测试。

rotate 标签

rotate 标签是旋转动画,可以实现旋转控件的效果。主要有一下几个属性:

  • fromDegrees: 开始时旋转的角度的位置,负值为逆时针旋转;
  • toDegrees: 结束时旋转的角度的位置;
  • pivotX: 缩放中心点的 x 坐标,值可以为数值、百分数、百分数p;
  • pivotY: 缩放中心点的 y 坐标。

在 res/anim 下创建 rotateanim.xml 文件

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:fillAfter="true"
    android:fromDegrees="0"
    android:interpolator="@android:anim/bounce_interpolator"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toDegrees="360" />
复制代码

其效果是沿着中心点顺时针旋转 360 度,然后在动画结束的时候会回弹。

alpha 标签

alpha 标签是淡入淡出动画,可以实现控件淡入淡出的效果。属性如下:

  • fromAlpha: 动画开始时的透明度,其中 0.0 为完全透明,1.0 为完全不透明
  • toAlpha: 动画结束时的透明度

我们在 res/anim 下创建 alphaanim.xml 文件

<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="3000"
    android:fillBefore="true"
    android:fromAlpha="1.0"
    android:interpolator="@android:anim/overshoot_interpolator"
    android:repeatCount="0"
    android:repeatMode="reverse"
    android:toAlpha="0.1" />
复制代码

其效果是控件从完全不透明渐变成透明度为 0.1,然后由于插值器的作用会降到完全透明。

translate 标签

translate 标签是移动动画,能实现控件移动的效果。主要的属性有:

  • fromXDelta: 起始点 x 坐标,相对于当前坐标的偏移量(如果是负值就代表向左向上)。其值可以是数值、百分数、百分数p。。
  • fromYDelta: 起始点 y 坐标
  • toXDelta: 结束点 x 坐标
  • toYDelta: 结束点 y 坐标

在 res/anim 创建 translateanim.xml 文件

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="2000"
    android:fillAfter="true"
    android:fromXDelta="0"
    android:interpolator="@android:anim/overshoot_interpolator"
    android:fromYDelta="0"
    android:toXDelta="50%"
    android:toYDelta="50%" />
复制代码

最后的效果是这样,首先控件从当前位置移动到左下偏移半个控件的距离,然后由于插值器的作用会多移动一点距离,最后返回到偏移半个控件的位置。

Translate 标签

set 标签

最后还有一个 set 标签,这个标签其实就是将上面学习的四种标签组合起来,实现更复杂的效果。

创建 res/anim/setanim.xml 文件

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="3000"
    android:fillAfter="true">

    <alpha
        android:fromAlpha="0.1"
        android:toAlpha="1.0" />

    <scale
        android:fromXScale="0"
        android:fromYScale="0.0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toXScale="1.4"
        android:toYScale="1.4" />

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

</set>
复制代码

很简单吧,看到上面的代码你能说出来最终的效果吗?

set 标签

代码生成补间动画

我们知道 xml 代码只是一种标记语言,而最后所有带都需要编译成机器代码,而 xml 代码最后都会通过 Java 代码解析成计算机能看懂的代码。那么四种补间动画肯定有对应的 Java 类。

  1. scale 标签对应的类是 ScaleAnimation;
  2. alpha 标签对应的是 AlphaAnimation;
  3. rotate 标签对应的是 RotateAnimation;
  4. translate 标签对应的是 TranslateAnimation;

ScaleAnimation 的构造函数如下:

  • ScaleAnimation(Context context, AttributeSet attrs) 从XML文件加载动画,基本用不到
  • ScaleAnimation(float fromX, float toX, float fromY, float toY)
  • ScaleAnimation(float fromX, float toX, float fromY, float toY, float pivotX, float pivotY)
  • ScaleAnimation(float fromX, float toX, float fromY, float toY, int pivotXType, float pivotXValue, int pivotYType, float pivotYValue)

其实我们可以从第一个构造函数看出端倪,我们前面写的 scaleanim.xml 在这个类中被解析,既然可以通过解析 xml 得到想要的效果,那我们也可以通过其他的构造函数在代码中设置动画效果。

我举例一些用法,剩下的靠读者自己去学习。

    /**
     * 使用代码生成动画
     */
    mScaleAnimation = ScaleAnimation(
    0.0f, 1.4f, 0f, 1.4f,
        Animation.RELATIVE_TO_SELF, 0.5f,
        Animation.RELATIVE_TO_SELF, 0.5f
    )
    mScaleAnimation.duration = 1000
    mScaleAnimation.interpolator = CycleInterpolator(5f)

    mAlphaAnimation = AlphaAnimation(0.1f, 1.0f)
    mAlphaAnimation.duration = 1000
    mAlphaAnimation.interpolator = AnticipateInterpolator()

    mRotateAnimation = RotateAnimation(0f,230f)
    mRotateAnimation.duration = 1000
    mRotateAnimation.interpolator = AnticipateInterpolator()
复制代码

逐帧动画(Frame Animation)

以XML格式定义的动画,按顺序显示一系列图像(如电影)。

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="true">
    <item
        android:drawable="@drawable/a"
        android:duration="500" />
    <item
        android:drawable="@drawable/b"
        android:duration="500" />
    <item
        android:drawable="@drawable/c"
        android:duration="500" />
    <item
        android:drawable="@drawable/d"
        android:duration="500" />
</animation-list>
复制代码

逐帧动画用到的标签是 animation-list,主要属性有 oneshot 接收一个 bool 类型的变量,当值为 true 时,动画只播放一边,为 false 动画一直循环播放。子元素是 item 标签,表示单帧动画,主要属性有 drawable 表示当前单帧动画的图片资源,duration 则表示这一帧动画播放多久。

动画准备好了之后,使用方法也很简单。首先逐帧动画的载体是 ImageView,然后使用的方法如下:

// kotlin 代码
val rocketImage: ImageView = findViewById(R.id.rocket_image)
rocketImage.setBackgroundResource(R.drawable.rocket)

val rocketAnimation = rocketImage.background
if (rocketAnimation is Animatable) {
    rocketAnimation.start()
}
复制代码

最后的效果就是这样

Frame Animation

手势

普通手势

首先我们来学习一下监听 View 的 Touch 事件,其实很简单对于可以修改源码的直接重写 onTouchEvent() 方法即可,而对于我们不能修改源码的,其实也只要实现 setOnTouchListener() 方法。

// 重新实现 Activity 的 Touch 事件
override fun onTouchEvent(event: MotionEvent): Boolean {
    // Touch 事件的简单检测,只处理 DOWN、MOVE、UP 这种
    val action: Int = MotionEventCompat.getActionMasked(event)
    return when (action) {
        MotionEvent.ACTION_DOWN -> {
            Log.d(TAG, "Action was DOWN")
            true
        }
        MotionEvent.ACTION_MOVE -> {
            Log.d(TAG, "Action was MOVE")
            true
        }
        MotionEvent.ACTION_UP -> {
            Log.d(TAG, "Action was UP")
            true
        }
        MotionEvent.ACTION_CANCEL -> {
            Log.d(TAG, "Action was CANCEL")
            true
        }
        MotionEvent.ACTION_OUTSIDE -> {
            Log.d(TAG, "Movement occurred outside bounds of currentscreen element")
            true
        }
        else -> super.onTouchEvent(event)
    }
}
复制代码
// 处理控件的 Touch 事件
text_view.setOnTouchListener { v, event ->
    val action: Int = MotionEventCompat.getActionMasked(event
    when (action) {
        MotionEvent.ACTION_DOWN -> {
            Log.d(TAG, "Action was DOWN")
            true
        }
        MotionEvent.ACTION_MOVE -> {
            Log.d(TAG, "Action was MOVE")
            true
        }
        MotionEvent.ACTION_UP -> {
            Log.d(TAG, "Action was UP")
            true
        }
        MotionEvent.ACTION_CANCEL -> {
            Log.d(TAG, "Action was CANCEL")
            true
        }
        MotionEvent.ACTION_OUTSIDE -> {
            Log.d(TAG, "Movement occurred outside bounds of currenscreen element")
            true
        }
        else -> super.onTouchEvent(event)
    }
}
复制代码

由代码可以看出来,我们是处理了 View 的 onTouchEvent 事件中 MotionEvent,而 MotionEvent 类封装了 DOWN(按下)、MOVE(移动)、UP(抬起)等,所以我们只要在对应的动作中处理逻辑即可。

按照上面那种方法处理还有一个不方便的地方,比如我想监听用户双击了 button,这个时候我们就得再加入一个变量来记录两次点击(DOWN + UP 动作),而这两次的间隔时间也不能太长,这个时间多长合适呢?这些都是我们需要考虑的。这还只是一个简单的双击事件,如果我还想三击、四击,或者长按呢?那就更复杂。

是不是感觉一个头两个大,不过不用担心,官方已经为我们考虑过这个问题了,所以就有个 GestureDetector(手势检测器) 这个类,这个类封装了各种日常会用到的手势操作,如:双击、长按、滚动等。

用法:

// 1. 实现一个监听回调
private class MyGestureListener :GestureDetector.SimpleOnGestureListener() {
    override fun onDown(e: MotionEvent?): Boolean {
        Log.d(TAG, "onDown: $e")
        return true
    }
    override fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX:Float, velocityY: Float): Boolean {
        Log.d(TAG, "onFling: $e1 $e2")
        return true
    }
}

// 2. 创建一个手势检测器,GestureDetectorCompat 是 GestureDetector 的一种实现
private lateinit var mDetector: GestureDetectorCompat
mDetector = GestureDetectorCompat(this, MyGestureListener())

// 3. 重写 onTouchEvent 方法
override fun onTouchEvent(event: MotionEvent): Boolean {
    return if (mDetector.onTouchEvent(event)) {
        true
    } else {
        super.onTouchEvent(event)
    }
}
// 或者实现
view.setOnTouchListener { v, event ->
    mDetector.onTouchEvent(event)
}
复制代码

上面代码实现了 SimpleOnGestureListener 的子类,它是 GestureDetector 对外提供的接口,能让我们处理各种手势操作。还提供了另外几种接口,如下表:

官方提供的手势解释器

解释一下 SimpleOnGestureListener 常用的原因,OnGestureListener 等其他接口需要我们必须实现很多方法,这就导致了本来有些方法我不关心,但是还是要实现。而 SimpleOnGestureListener 将这些方法进行了空实现,如果我们对某个方法感兴趣再重写就好了,这样我们的代码就会比较简洁。

各种接口更为详细的介绍可以参考 安卓自定义View进阶-手势检测(GestureDetector)

缩放手势

参考 安卓自定义View进阶-缩放手势检测(ScaleGestureDecetor)

转载于:https://juejin.im/post/5c61140c51882561dd7b5016

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值