安卓动画组合拳:创意示例带您掌握动画效果开发技巧

引言

作为一个开发安卓app的程序员,经常需要在app界面作出各式各样的动画效果,所以如何让我们开发的动画效果尽可能的贴近产品给出的需求(样例)是一件蛮基础但又很重要的事情,接下来,我会通过一个创意示例来展示这一开发过程,希望通过这个创意示例分享开发安卓动画效果的实践经验,激发读者的创意思维,并期待读者提出宝贵的意见和建议。

准备开发动画效果的前期工作

其实,最重要的也就是这一步,我们拿到了需求,看了要求与效果样例后,第一件事不应该是急匆匆的写代码,当然,如果需求很简单直接写代码也无妨,所以在这达成一认知后,我们来看看本文的示例是否可以直接写代码(其实也是基于作者本身对动画效果的组合使用不是很熟练这一情况来的,如果开发时间长一些,很多需求还是可以直接上手写代码了)。

首先,我们看看需求,这里有效果示意图:

WeChat_20230625173956.gif

这一看就知道一定是一段动画效果组合而成,除非是个切图做成Lottie的GIF。

拿到需求后,我们需要分析一下效果的实现过程,也就是拆分实现步骤:

  1. 需要两个图标,且放置位置不同
  2. 点击下面消息样式图标(以下简称“消息”)的右上角“删除”图标(以下简称“删除”)时候,删除会消失
  3. 删除消失的同时,消息也会迅速边移动到铃铛图标(以下简称“铃铛”)位置边缩小直到消失
  4. 消息消失时铃铛开始闪烁一下接着摇动几下

以上就是将一整个功能拆分的过程,这样做的好处就是可以更快的写出合适的代码,也就是将所谓的动画组合拳(最后的动画效果)进行招式拆分,逐步学习理解,最后才能融会贯通,形成一套行云流水。

进入正式开发工作

前面进行了拆分,那就需要为这些拆分好的步骤找到合适的动画效果方法代码,为其“点睛”。

首先第一步,我们需要把布局整理好,即做好对应未出现动画时的UI界面:
代码如下:
XML布局如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">


    <androidx.appcompat.widget.AppCompatImageView
        android:id="@+id/vAlarm"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_gravity="end"
        android:layout_marginEnd="64dp"
        android:layout_marginTop="16dp"
        app:srcCompat="@drawable/vector_alarm_test"/>
    <LinearLayout
        android:id="@+id/vLlMessageButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="end"
        android:layout_marginEnd="16dp"
        android:layout_marginTop="500dp"
        android:orientation="vertical">
        <androidx.appcompat.widget.AppCompatImageView
            android:id="@+id/vMessageButtonClose"
            android:layout_width="20dp"
            android:layout_height="20dp"
            android:layout_gravity="end"
            app:srcCompat="@drawable/vector_alarm_delete_test"/>
        <androidx.appcompat.widget.AppCompatImageView
            android:id="@+id/vMessageButtonImage"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:layout_gravity="end"
            android:layout_marginEnd="16dp"
            android:layout_marginTop="10dp"
            app:srcCompat="@drawable/vector_alarm_message_test"/>
    </LinearLayout>
</LinearLayout>

image.png
首先我们采用LinearLayout线性布局,在当中放入三张图进行布局,组合出对应的界面效果,其中三个关键的view是vAlarm,vLlMessageButton,与vMessageButtonClose,我们在代码中为其添加事件和动画效果。

Activity代码:

public class MainActivity extends AppCompatActivity {




    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        AppCompatImageView vMessageButtonClose = findViewById(R.id.vMessageButtonClose);

        vMessageButtonClose.post(new Runnable() {
            @Override
            public void run() {
                clickMessageButtonCloseAnimation();
            }
        });



    }


}

我们在Activity中的onCreate()方法中增加了vMessageButtonClose这个view的post方法,并在post里面的Runnable对象里调用了我们的动画实现事件函数clickMessageButtonCloseAnimation(),这样做的好处就是这个函数执行时我们的UI元素已经被绘制出来了,一些测量的数据不会出现变成0的情况。

我们的clickMessageButtonCloseAnimation()函数内容(MyTest/app/src/main/java/com/example/mytest/MainActivity.java at main · ObliviateOnline/MyTest · GitHub,内容比较长,就不直接附在此处了,可以到作者Github这个地址看看)就是本篇的重点。这个函数内容很复杂,因为我将动画效果主体实现过程都放到这个方法里面了,所以我们下面就从各方面逐步解析它。

安卓动画效果知识基础

在解析这段函数的代码前我们要看看安卓中写添加动画效果要使用哪些知识。

一·补间动画和属性动画的差别

安卓中主要有两种实现动画的方法,分别是补间动画和属性动画,粗略的说,补间动画比较简单,是通过改变View的属性值,并生成一系列中间状态来实现动画效果;而属性动画就复杂一些,通过改变Object的属性值,并生成一系列中间状态来实现动画效果。

除此以外,通过一些资料上说的,补间动画的兼容性没那么好,也没属性动画实现的流畅,总之就是属性动画有点各方面碾压补间动画的意思。

二·补间动画的实现方法

我们在安卓中可以通过Animation类及其子类主要用于实现补间动画,其原理是通过改变View的属性值,从初始状态到最终状态的过程中,逐步地生成一系列中间状态,让View在这些中间状态之间平滑过渡,从而形成动画效果。

我们常用的有如下几种Animation类子类:

  1. RotateAnimation:是Android中用于实现旋转动画效果的一个子类,它可以让一个View或Drawable对象沿着中心点或指定轴线进行旋转。
  2. ScaleAnimation:用于实现缩放动画效果,可以让一个View或Drawable对象沿着中心点或指定轴线进行放大或缩小。
  3. TranslateAnimation:用于实现平移动画效果,可以让一个View或Drawable对象沿着指定的轴线进行平移。
  4. AlphaAnimation:用于实现透明度动画效果,可以让一个View或Drawable对象的透明度进行渐变。
    示例:
// 获取需要旋转的View对象
View view = findViewById(R.id.myView);




// 创建RotateAnimation对象
RotateAnimation animation = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);



// 设置动画属性
animation.setDuration(1000); // 持续时间为1秒
animation.setRepeatCount(Animation.INFINITE); // 无限循环


// 启动动画
view.startAnimation(animation);

三·属性动画的实现方法

在安卓中一般通过Animator类实现属性动画,比如AnimatorSet和ObjectAnimator的使用。与补间动画不同,属性动画会通过平滑过渡来实现动画效果,显得更加自然和流畅。

AnimatorSet是一个用于组合多个动画效果的类,它可以同时播放、顺序播放、并行播放多个属性动画,一般就是用来组合我们需要的动画效果。

而ObjectAnimator可以改变指定对象的指定属性值,并生成一系列中间状态,通过平滑过渡来实现动画效果。例如,可以通过ObjectAnimator来实现View的平移、旋转、缩放等动画效果,也可以通过ObjectAnimator来实现自定义属性的动画效果,例如背景颜色、字体大小等,这是用来具体实现效果的。
示例:
就和本篇的例子一样,我们可以通过使用AnimatorSet来组合多个ObjectAnimator从而实现复杂的动画效果

// 获取需要动画的View对象
View view = findViewById(R.id.myView);




// 创建ObjectAnimator对象
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 1.5f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 1.5f);
ObjectAnimator rotate = ObjectAnimator.ofFloat(view, "rotation", 0f, 360f);

// 创建AnimatorSet对象
AnimatorSet animatorSet = new AnimatorSet();

// 设置动画属性
animatorSet.play(scaleX).with(scaleY).before(rotate);
animatorSet.setDuration(1000); // 持续时间为1秒
animatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); // 加速度和减速度插值器



// 启动动画
animatorSet.start();

以上就是我们需要提前了解的知识,其实只要好用或者方便,使用哪种都行,混合着用也没啥,只要最后效果能完美实现就行,而且这些类的实现也不是绝对对应着属性动画和补间动画的,一些实现属性动画的类也可以去实现补间动画。

了解完了基础的知识部分,下面就开始正式分析,这里不从代码顺序去解析,因为这个顺序其实不是代码逻辑顺序,我们是从事件触发顺序来叙述:

事件一:vMessageButtonClose按钮点击事件

首先我们先看最后几行代码,这里是我们在点击删除按钮时候事件触发的代码,我们在点击时将这个删除按钮隐藏并启动相应动画事件。

这也就对应了这几行代码:

 // 按钮点击事件
    vMessageButtonClose.setOnClickListener(new View.OnClickListener() {
        @Override

        public void onClick(View v) {
            vMessageButtonClose.setVisibility(View.GONE);
            // 启动动画
            animatorSet.start();
        }
    });

,animatorSet.start()也表明我们采用的是属性动画,这里考虑的是让动画整体更流畅,因为我们的动画是组合而成,相对还是比较复杂的,使用属性动画虽然麻烦但是最终效果更好。

事件二:动画组合事件注册

我们在上一个事件中有启动动画这行代码,其中animatorSet对象需要先去创建好相应活动步骤:

// 创建 AnimatorSet,将两个动画组合起来
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(firstAnimatorSet).before(secondScaleAnimatorX).before(secondScaleAnimatorY);

这段代码就在事件一上面,这是创建AnimatorSet对象来组合后面需要的动画的,让动画按照我们想要的顺序来执行,上面代码动画执行顺序是:
firstAnimatorSet –> secondScaleAnimatorX 和 secondScaleAnimatorY 

before() 方法实际上是设置了动画之间的依赖关系,指定了一个动画(play)在另一个动画(before)执行之前播放,也就是在play()方法之后执行。

事件三:分步骤写动画效果

这一块内容多一些,也是最主要的部分,动画效果的实现,这里还要分成两部分firstAnimatorSet动画 与secondScaleAnimatorXsecondScaleAnimatorY 动画

firstAnimatorSet动画实现

首先我们来看最麻烦的部分,这一步就是我们看到最主体的动画效果,也就是点击删除按钮后消息移动到铃铛的过程,这里可以再细分为位移与缩放。

位移

位移是需要我们进行计算的,也就是如下代码:

 // 获取当前位置的 View 中心点坐标
    int[] startLocation = new int[2];
    vLlMessageButton.getLocationOnScreen(startLocation);
    float startX = startLocation[0] +vLlMessageButton.getWidth() / 2f;
    float startY = startLocation[1] + vLlMessageButton.getHeight() / 2f;



    // 获取目标位置的 View 中心点坐标,并考虑到缩放因子
    int[] endLocation = new int[2];
    vAlarm.getLocationOnScreen(endLocation);
    float endX = endLocation[0] + vAlarm.getWidth() * sc;
    float endY = endLocation[1] + vAlarm.getHeight() * sc / 2f;

    // 计算需要移动的距离
    float deltaX = endX - startX;
    float deltaY = endY - startY;



    // 创建第一个动画,将 View 移动到目标位置并同时进行缩放
    ObjectAnimator moveAnimatorX = ObjectAnimator.ofFloat(vLlMessageButton, "translationX", 0f, deltaX);
    ObjectAnimator moveAnimatorY = ObjectAnimator.ofFloat(vLlMessageButton, "translationY", 0f, deltaY);

这里最重要的是要考虑到我们两个图标大小因为不一样,位移过程需要缩放对位移坐标产生的影响,很容易移动偏了,不是简单的获取一下两个view组件的本身坐标就行的。

在计算好位移距离后,我们采用ObjectAnimator对象的translationX和translationY记录xy坐标的移动动画moveAnimatorXmoveAnimatorY,这样,我们的位移就做好了,接下来写缩放和组合小动画。

缩放和拼接动画

这块还是比较简单了,代码如下:

ObjectAnimator scaleAnimatorX = ObjectAnimator.ofFloat(vLlMessageButton, "scaleX", sc);
    ObjectAnimator scaleAnimatorY = ObjectAnimator.ofFloat(vLlMessageButton, "scaleY", sc);
    AnimatorSet firstAnimatorSet = new AnimatorSet();
    firstAnimatorSet.playTogether(moveAnimatorX, moveAnimatorY, scaleAnimatorX, scaleAnimatorY);
    firstAnimatorSet.setDuration(500);
    firstAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());

我们这里使用scaleX和scaleY缩放动画再创建两个ObjectAnimator对象,同时我们用AnimatorSet(firstAnimatorSet)去拼接这些小的动画效果步骤,使用playTogether()方法去让这些动画步骤同时进行,使用AccelerateDecelerateInterpolator插值器(setInterpolator)以及设置对应动画时间(setDuration)去实现我们的第一部分动画效果。

接着我们还得在第一部分动画效果中加点东西,让另一个View组件铃铛也要有动画效果,从而与前面消息的动画效果衔接起来,因为firstAnimatorSet对象中我们可以监听其开始和结束等活动,所以我们需要在这个对象的动画结束监听里增加一个新的动画效果(针对铃铛的缩放以及后续的摇动动画),代码如下:

//监听动画结束,设置alarm图标缩放动画
    firstAnimatorSet.addListener(new Animator.AnimatorListener() {
        @Override

        public void onAnimationStart(Animator animation) {
        }



        @Override
        public void onAnimationEnd(Animator animation) {
            //与此同时,alarm图标动画开始: 1.缩小到消失(和悬浮图标同步)2.再次显示,alarm图标进行摇动
            Animation scale = new ScaleAnimation(1f, 0, 1f, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
            scale.setDuration(100);
            AnimationSet animSet = new AnimationSet(true);
            animSet.addAnimation(scale);
            vAlarm.startAnimation(animSet);
            animSet.setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {


                }

                @Override
                public void onAnimationEnd(Animation animation) {
                    vLlMessageButton.setVisibility(View.GONE);

                    //alarm图标动画: 1.缩小到消失(和悬浮图标同步)2.再次显示,alarm图标进行摇动
                    Animation shake = new RotateAnimation(-3, 3, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0f);
                    shake.setInterpolator(new CycleInterpolator(3));
                    shake.setDuration(1000);
                    vAlarm.startAnimation(shake);
                }

                @Override
                public void onAnimationRepeat(Animation animation) {

                }
            });
        }

        @Override
        public void onAnimationCancel(Animator animation) {

        }

        @Override
        public void onAnimationRepeat(Animator animation) {

        }
    });

这块代码有点套娃的感觉,因为本身就是在消息图标的动画对象中监听的,结果创建新的动画对象(铃铛图标缩放)后最后又创建一个新的(铃铛图标摇动),所以总共三层了,但是理解也很容易,效果就是: 第一部分动画效果结束(消息位移到铃铛),铃铛动画开始:1.缩小到消失(和消息图标同步)2.再次显示 ,铃铛缩放动画结束,铃铛摇动动画进行。

这里的缩放和上面的创建不是一样的,这里我们采用了补间动画,我们创建的Animation对象的子对象ScaleAnimation用来缩放,后面摇铃也是子类对象RotateAnimation用来控制旋转来起到摇动的效果(还利用到查找器CycleInterpolator来实现周期动画效果的),当然,这其中的参数都要根据需要的实际效果来设置,时间也要对应准确,不然效果会显得很突兀,这方面只要通过经常练习才能比较好把握其中的度。

到这里,我们最复杂的动画部分结束了,下面是消息图标的二次缩放。

消息图标的二次缩放动画

这里要说的就是secondScaleAnimatorXsecondScaleAnimatorY 动画,用来控制消息图标位移到铃铛位置后缩小到消失这段动画过程的,其实时间上正好和之前说的铃铛缩放动画同步,这里是通过设置对应长度的动画时间来确定的,不是什么神奇的技巧。

这一部分很简单,我们先看看代码:

  // 创建第二个动画,将 View 缩放至 0
    ObjectAnimator secondScaleAnimatorX = ObjectAnimator.ofFloat(vLlMessageButton, "scaleX", 0f);
    ObjectAnimator secondScaleAnimatorY = ObjectAnimator.ofFloat(vLlMessageButton, "scaleY", 0f);
    secondScaleAnimatorX.setDuration(100);
    secondScaleAnimatorY.setDuration(100);
    secondScaleAnimatorX.setInterpolator(new AccelerateDecelerateInterpolator());
    secondScaleAnimatorY.setInterpolator(new AccelerateDecelerateInterpolator());

就是ObjectAnimator创建两个缩放动画即可,然后我们再用相同的参数去设置这两个动画以保持一致,以防止缩放的xy坐标比例不协调。

这样我们的整体动画效果就完成了,效果就是文章开始的效果图,主要的要求就是要能够正确的拆分好动画的每一步然后给组合好,其实也肯定有其他的组合方法,这不是唯一的。

结语

这里仅仅是开放过程的一例,当然还会有更加麻烦的动画效果组合,但是如果能按照这种方法依次去拆分好每一步,再去一一组合起来,其实还是十分简单的,最多不过是代码的长度增加了而已,希望这个例子能对读者有所启发,也欢迎掘友们指正!

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MY9R0ySv' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片