Android Animation Made Easy

news2024/11/28 4:41:30

原文链接 Android Animation Made Easy

动画在任何一个GUI系统中都是一个非常重要的设计元素,它可以让交互变得优雅,让界面变得炫酷,让操作变得更加的舒畅,让状态过渡变得更加的顺滑,对视觉效果有极大的提升,时而提升用户体验,特别是对于移动应用来说,更是如此。就好比水果平台,最为吸引人的地方就在于其炫酷流畅的动画效果。早期的Android,在动画这一块确实差,不过,近些年,随着谷歌不断的加大力度在提升,现在来说安卓在动画这一块已经跟水果差不多了。今天就来聊一聊关于动画的话题。

动画的种类

一般来说动画分为二个种类:

逐帧动画(Frame Animation)也叫做Drawable Animation

也就是电影胶片式的,一张张不同的画连在一起播放,比较简单,只需要准备足够帧数(数量)的图片,就可以了。缺点也比较明显,需要比较多的资源(图片,存储空间,内存空间以及CPU资源)。并且灵活性非常的差,不能让普通的一段文字或者一个按扭进行动画。
针对某些特别简单的动画可以用此方式来实现,比如像简单的进度条,或者滑动引导提示等,具体的方式就是:

<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="true">    
    <item android:drawable="@drawable/rocket_thrust1" android:duration="200" />
    <item android:drawable="@drawable/rocket_thrust2" android:duration="200" />
    <item android:drawable="@drawable/rocket_thrust3" android:duration="200" />
</animation-list>

补间动画(Tween Animation)

补间动画比帧动画就要高级一些,因为充分利用了计算机的特性,只需要告诉起始状态和结束状态,然后让计算机去计算中间的状态,再不用把每一帧都告诉计算机了。在安卓中就是View Animation,以及后来的强大的Property Animation。

动画的基本原理

动画,其实就是一组快速播放的幻灯片,每一张(每一帧)的状态略有不同,快速连起来播放,由于人的眼睛有视觉残留效应,这就形成了动画。对于计算机程序来说,一般的动画就是给定对象的初始和终末状态,在一定时间内,不断的计算中间过程,并以视觉的方式展示出来,这就是动画。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ujh0qqTO-1691408239181)(https://developer.android.com/images/animation/animation-linear.png)]

动画的关键要素

一个动画必须要有以下关键的要素:

  • 时长(Duration),也就 说播放动画的总时长,系统默认是300ms
  • 时间插值器(Interpolation),就是动画的关键参数随时间要如何变化
  • 重播(Repeat),包括重播的次数以及方式,次数好理解,方式的意思是,可以顺序的一遍一遍的播,也是可以反着播
  • 延迟(Delay),动画启动的延时,通常用在动画组合里面。

View Animation

这是从安卓一开始就支持的动画方式,仅能对View对象生效,使用起来也比较方便和简单,通过组合和自定义插值器,足以实现常用的视觉变幻(如渐变,旋转,缩放和位移)。

主要有四种位移(translation),透明度(alpha),旋转(rotation)和缩放(scaling)。还可以以集合的方式来把几个动画合在一起播放。可以设置动画的时长(duration),速率(interpolator),重复和事件的监听。
一些使用建议:

  • 所有这些东西都是放在android.view.animation包下面的,所以这些东西只能用于View对象。其实绝大多数时候,这也不是问题,除了View,还有啥要做动画呢。

  • TranslateAnimation只能是直线运动,如果要曲线,就得自定义,可以参考这个。

  • scale可以实现水平或者垂直翻转。

  • 动画的触发是当View需要invalidate的时候就会触发setAnimation指定的动画。所以,如下代码会正常触发:

    TextView title; // 原来是GONE的
    title.setVisibility(View.VISIBLE);
    title.setAnimation(new AlphaAnimation(0.3f, 1f);
    

    不一定非要startAnimation

  • 要注意动画前后View的状态。这个比较难受。因为动画过程仅是放一遍电影(动画过程中仅是在View的绘制的时候对Canvas做变幻),对View本身并没有影响,通常的做法是给View Animation加上Listener,在onAnimationEnd的时候去设置目标状态。

总的来说,View animation简单易用,大部分场景是可以满足需求的,早期版本确实有一些缺陷,内部状态在动画过程中会有问题,但是最近新的Android版本上面,已没有大问题,所以当能满足需求时,使用也没有问题。

属性动画(Property Animation)

就像名字暗示那样,从3.0开始一套新的动画API出现了,可以描述为在一段时间内以一定的方式来改变某一个属性,是这样的方式来做动画。所以,它也可以做动画以外的事情。这套API的核心思想是在一段时间内,让某些属性随着时间改变(有点像中学的物理题)。

属性动画就是根据时间来改变某一对象(不一定非要是View)的某一个属性,至于某一时刻属性变化的值所产生的后果,由使用者自定义,因此你可以把它应用于任何对象。

它也与View一样,可以组合,可以设置事件监听。

与View动画最大的区别在于,View动画仅是按要求放一遍电影,不会对View的实际属性产生影响,因此,动画过程中以及完成后View仍是在原来的位置,属性也不会变化。而属性动画则不是,它会直接改变View的属性,所以有些时候这个优势会很方便,比如实现收起与展开的动画时Property动画会明显的优势:

比如,对于一个可以收起和弹出的动画,就可以这样来实现:

收起动画:

private void animateCollapse() {
        AnimatorSet set = new AnimatorSet();
        ObjectAnimator translate = ObjectAnimator.ofFloat(mStatusPanel, "translationY", 0f, mTranslationY);
        ObjectAnimator alpha = ObjectAnimator.ofFloat(mStatusPanel, "alpha", 1f, 0.75f);
        set.setDuration(250);
        set.setInterpolator(new AccelerateDecelerateInterpolator());
        set.playTogether(translate, alpha);
        set.start();
    }

弹出动画:

private void animateExpansion() {
        AnimatorSet set = new AnimatorSet();
        ObjectAnimator translate = ObjectAnimator.ofFloat(mStatusPanel, "translationY", mTranslationY, 0);
        ObjectAnimator alpha = ObjectAnimator.ofFloat(mStatusPanel, "alpha", 0.75f, 1f);
        set.setDuration(250);
        set.setInterpolator(new AccelerateDecelerateInterpolator());
        set.playTogether(translate, alpha);
        set.start();
    }

如果要使用View animation,也许也可以实现同样的效果,但估计会很难,因为要注意设置View的属性。比如说收起时并不是全hide,而是半折叠状态,就需要在AnimationListener#onAnimationEnd时去设置特殊的位置状态.

使用时候的建议:

  • 属性动画是post layout的,所以所有属性的初始状态就是你在布局中指定的值,动画是以此为基础开始的。

  • 比较难使用的是translationX和translationY属性,它们的定义是相对于left和top的值。或者理解为相对于layout之后的在父布局中的位置的左边和右边。比如:

    ObjectAnimator.ofFloat(mBar, "translationY", 0, height);
    

    这个就是进入的动画,一个View从其上头滑入。反过来:

    ObjectAnimator.ofFloat(mBar, "translationY", height, 0);
    

    就是滑出。

ValueAnimator

这是属性动画的核心类,其实它很好的诠释了什么是动画,它就是把某个值在duration内,按照插值器指定的方式从一个值变化到另一个值。看到这个类,就可以感知到动画跟View其实一点关系都没有,动画就是一个随时间变化 的数值而已。

ValueAnimator animation = ValueAnimator.ofFloat(0f, 100f);
animation.setDuration(1000);
animation.start();

这意思就是让一个浮点数变量,在1秒内,从0,变化 到100。至于这个有什么具体的效果,要看你如何应用这个随时间变化 的浮点数变量,比如用于控制进度,一般情况下都会将变化的数值用于改变View的视觉变幻形态,但并不局限于此,这里只是为了说明这个动画数值可以用于任何地方:

        animation.addUpdateListener(anim -> {
            float t = (float) anim.getAnimatedValue();
            mStatusPanel.setText(String.format("Temperature: %04.1f", t));
        });

ObjectAnimator

它是ValueAnimator的一个子类,增强了点功能,它的作用是针对 给定的对象,对其指定的某个属性做动画插值,动画计算与前面提到的ValueAnimator是一样的,只不过说它可以对某个对象的指定的属性做计算,并改变这个属性:

ObjectAnimator animation = ObjectAnimator.ofFloat(textView, "translationX", 100f);
animation.setDuration(1000);
animation.start();

这意思就是说,把动画计算出来的数值应用于一个textView的translationX属性上面。它与下面的代码,用ValueAnimator来实现,是完全等效的:

ValueAnimator animation = ValueAnimator.ofFloat(0f, 100f);
animation.setDuration(1000);
animation.start();

animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator updatedAnimation) {
        float animatedValue = (float)updatedAnimation.getAnimatedValue();
        textView.setTranslationX(animatedValue);
    }
});

由此可见ObjectAnimator就是多做了一层封装,方便来操作而已。需要注意的是,对象的属性必须要有setter和getter,因为这里会用传进来的属性名字用反射去调用,所以必须要有属性对应的settter和getter方法。

ViewPropertyAnimator

因为大多数情况下是对View做动画,所以又封装出了一个专门用于View的属性动画工具,也即ViewPropertyAnimator,可以非常方便进行属性动画。用一个实例就会相当明了。

比如说想对某个View进行位置,用ObjectAnimator,就需要这么写:

ObjectAnimator animX = ObjectAnimator.ofFloat(myView, "x", 50f);
ObjectAnimator animY = ObjectAnimator.ofFloat(myView, "y", 100f);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(animX, animY);
animSetXY.start();

但如果用ViewPropertyAnimator就会非常简洁:

myView.animate().x(50f).y(100f);

AnimatorSet

用于创建组合,前面的例子已经可以看出来它怎么使用的了。当需要同时实现多个变幻时,就可以把多个Animator用AnimatorSet来组合起来。这个类非常的灵活,可以设置不同的时长,延迟和重复。

插值器

插值器(Interpolators)用以调节数值与时间变化 的关系,因为动画是有时长的,是在duration内,从某个数值变化 到另一数值,而具体随时间怎么变,则由插值器决定。默认是线性的,比如250ms,0f到100,那么就是匀速运动。也可以加速的,减速的,先加速后减速,先减速后加速。

android.view.animation内定义了大量的插值器可供使用。

在XML中来声明动画

与布局类似,动画也是支持在XML中来声明的,这样可以减少代码量,加强复用。方式与方法与写代码差不多,只不过是放在了XML里面,如:

<set android:ordering="sequentially">
    <set>
        <objectAnimator
            android:propertyName="x"
            android:duration="500"
            android:valueTo="400"
            android:valueType="intType"/>
        <objectAnimator
            android:propertyName="y"
            android:duration="500"
            android:valueTo="300"
            android:valueType="intType"/>
    </set>
    <objectAnimator
        android:propertyName="alpha"
        android:duration="500"
        android:valueTo="1f"/>
</set>

这就声明了一个AnimatorSet,是一个位移和渐变动画,使用时用AnimatorInflater 来加载load一下就可以了:

AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
    R.animator.property_animator);
set.setTarget(myObject);
set.start();

支持的根节点有三个,AnimatorSet,ObjectAnimator和ValueAnimator:

  • ValueAnimator - <animator>
  • ObjectAnimator - <objectAnimator>
  • AnimatorSet - <set>

对于XML中使用ValueAnimator也是一样的,定义好,然后加载出来就可以用了,其实跟前面用代码写是一样的:

<animator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:valueType="floatType"
    android:valueFrom="0f"
    android:valueTo="-100f" />
ValueAnimator xmlAnimator = (ValueAnimator) AnimatorInflater.loadAnimator(this,
        R.animator.animator);
xmlAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator updatedAnimation) {
        float animatedValue = (float)updatedAnimation.getAnimatedValue();
        textView.setTranslationX(animatedValue);
    }
});

xmlAnimator.start();

注意:有一点需要注意的是,因为View Animation也是支持用XML方式来定义的,且是放在了res/anim下面。所以如果使用Property Animation 时要放在res/animator下面,这个一定要注意。

View状态变化动画

从安卓一开始,对于一些View的状态变化就可以设置不同的Drawable,以给用户视觉上的交互 反馈,最常见的比如按扭,常规状态,Focused状态和按压状态,以及Disabled的状态(不可点击)可以设置不同的Drawable(如icon或者颜色等)以告诉用户。这个是叫做StateListDrawable。

现如今,也可以针对 View的不同状态设置不同的动画了,通过StateListAnimator来实现,它的语法与前面提到的StateListDrawable类似,亦是通过一个selector,只不过其中的每个item都是animator,而非drawable,比如:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- the pressed state; increase x and y size to 150% -->
    <item android:state_pressed="true">
        <set>
            <objectAnimator android:propertyName="scaleX"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1.5"
                android:valueType="floatType"/>
            <objectAnimator android:propertyName="scaleY"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1.5"
                android:valueType="floatType"/>
        </set>
    </item>
    <!-- the default, non-pressed state; set x and y size to 100% -->
    <item android:state_pressed="false">
        <set>
            <objectAnimator android:propertyName="scaleX"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1"
                android:valueType="floatType"/>
            <objectAnimator android:propertyName="scaleY"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1"
                android:valueType="floatType"/>
        </set>
    </item>
</selector>

这意思就是当点击的时候进行缩放动画,把其保存在res/xml/animate_scale.xml,这里需要注意,StateListDrawable是可以直接保存在res/drawable/下面的,但动画毕竟不是drawable,是不可以放在res/drawable下面。

通过android:stateListAnimator添加给指定的View,如:

<Button android:stateListAnimator="@xml/animate_scale"
        ... />

如果不在XML中设置,用代码也可以,先用AnimatorInflater,把它加载出来,然后调用View#setStateListAnimator即可。

设计与实现要符合标准

一个不争的事实是,在安卓的早期版本的时候对动画支持并不友好,因此当时很多GUI的设计都是采用水果平台的规范,导致大量的头部app,GUI交互,特别是动画这一块都是尽可能 的去模仿水果平台。

但时代不一样了,现在在谷歌加大了对安桌的支持力度后,特别是当Material Design出来了以后,从Android 5.0 Lollipop开始,伟大的Google就发布了专门针对UED的设计语言Material Design它不再单单是设计规范了,而是一个非常详细的设计语言,具体到Icon怎么画,动画怎么做。那么,安桌的GUI交互设计与实现,就要符合Material Design的规范了,这样不但体验更符合安桌的风格,实现起来也更加的顺手,因为大量的标准库,AndroidX的库和风格主题动画等等都是以Material Design为标准的,开发人猿在实现的时候有更多的资源可以复用,不用再重复的去造轮子。

Anyway,官方的东西我们还是要学习的并尽可能的遵守的,特别是关于Material Design和Animation。

参考资料

  • Animations and Transitions
  • https://github.com/lgvalle/Material-Animations
  • Implementing Material Design in Your Android app
  • 如何学习 Android Animation

原创不易,打赏点赞在看收藏分享 总要有一个吧

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/843936.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Jmeter添加cookie的两种方式

jmeter中添加cookie可以通过配置HTTP Cookie Manager&#xff0c;也可以通过HTTP Header Manager&#xff0c;因为cookie是放在头文件里发送的。 实例&#xff1a;博客园点击添加新随笔 https://i.cnblogs.com/EditPosts.aspx?opt1 如果未登录&#xff0c;跳转登录页&#xf…

自制免费 SQL 闯关自学网,代码开源!

大家好&#xff0c;我是鱼皮。 相信很多学编程的同学都学习过 SQL 吧&#xff1f;SQL 作为数据库查询语言&#xff0c;实在是太重要了&#xff0c;可以说是程序员、产品经理、数据分析同学的必备技能。 为了帮助大家自学 SQL&#xff0c;这段时间&#xff0c;我一个人做了个 …

陈述式资源管理方法详解

目录 一&#xff1a;陈述式资源管理方法 二&#xff1a; 基本信息查看 1、查看信息 2、创建 3、删除 4、service 的 type 类型 三&#xff1a;项目实例 1、创建 kubectl create命令 2、发布 kubectl expose命令 3、在 node 节点上操作&#xff0c;查看负载均衡端口…

机器学习笔记之优化算法(十一)凸函数铺垫:梯度与方向导数

机器学习笔记之优化算法——凸函数铺垫&#xff1a;梯度与方向导数 引言回顾&#xff1a;偏导数方向余弦方向导数方向导数的几何意义方向导数的定义 方向导数与偏导数之间的关联关系证明过程 梯度 ( Gradient ) (\text{Gradient}) (Gradient) 引言 本节作为介绍凸函数的铺垫&a…

深度学习和OpenCV的对象检测(MobileNet SSD视频流实时识别)

上期文章,我们分享了如何使用opencv 与MobileNet SSD模型来检测给定的图片,有网友反馈能否提供一下视频流的实时检测代码,其实我们在分享人脸识别的时候,分享了如何使用cv2.videoCpature 类来从视频中实时提取视频中的图片,进行人脸的识别,视频流的对象检测跟opencv的人脸…

rhel7安装Oracle 19C RAC

一、安装环境准备 1.1./dev/shm作为tmpfs挂载 --查看大小 df -h /dev/shm/--写入/etc/fstab vi /etc/fstab tmpfs /dev/shm tmpfs defaults,size3.9G 0 01.2.关闭防火墙 systemctl stop firewalld.service systemctl disable firewalld.service1.3.关闭s…

微服务——RestClient查询文档

快速入门 返回结果直接把json风格的结果封装为SearchReponse对象返回 public class HotelSearchTest {private RestHighLevelClient client;Testvoid testMatchAll() throws IOException {//1.准备requestSearchRequest request new SearchRequest("hotel");//2.准…

嵌入式基础知识-存储管理

上篇介绍了存储器的相关知识&#xff0c;偏重的是硬件结构&#xff0c;本篇介绍存储管理的相关知识&#xff0c;偏重的是软件管理。 1 存储管理概念 操作系统&#xff0c;包括嵌入式系统&#xff0c;通常利用存储管理单元MMU&#xff08;Memory Management Unit&#xff09;来…

OB数据库基础知识(学习记录)

目录 OB业务场景 公司使用理由&#xff1a; 常见 bootstrap 失败原因 常见OBD 部署 失败原因 Grafana 查看集群资源由各个节点的聚合情况 OB创建租户 表分组的场景 mysqldump到处数据库schema&#xff0c;数据库数据&#xff0c;表数据 数据同步框架 DATAX obdumper…

支付总架构解析

一、支付全局分层 一笔支付以用户为起点&#xff0c;经过众多支付参与者之后&#xff0c;到达央行的清算账户&#xff0c;完成最终的资金清算。那么我们研究支付宏观&#xff0c;可以站在央行清算账户位置&#xff0c;俯视整个支付金字塔&#xff0c;如图1所示&#xff1a; 图…

Java课题笔记~6个重要注解参数含义

1、[掌握]Before 前置通知-方法有 JoinPoint 参数 在目标方法执行之前执行。被注解为前置通知的方法&#xff0c;可以包含一个 JoinPoint 类型参数。 该类型的对象本身就是切入点表达式。通过该参数&#xff0c;可获取切入点表达式、方法签名、目标对象等。 不光前置通知的方…

乍得ECTN(BESC)申请流程

根据TCHAD/CHAD乍得法令&#xff0c;自2013年4月1日起&#xff0c;所有运至乍得的货物都必须申请ECTN(BESC)电子货物跟踪单。如果没有申请&#xff0c;将被视为触犯乍得的条例&#xff0c;并在目的地受到严厉惩罚。ECTN是英语ELECTRONIC CARGO TRACKING NOTE的简称&#xff1b;…

EFLFK——ELK日志分析系统+kafka+filebeat架构

环境准备 node1节点192.168.40.16elasticsearch2c/4Gnode2节点192.168.40.17elasticsearch2c/4GApache节点192.168.40.170logstash/Apache/kibana2c/4Gfilebeat节点192.168.40.20filebeat2c/4G https://blog.csdn.net/m0_57554344/article/details/132059066?spm1001.2014.30…

oracle的管道函数

Oracle管道函数(Pipelined Table Function)oracle管道函数 1、管道函数即是可以返回行集合&#xff08;可以使嵌套表nested table 或数组 varray&#xff09;的函数&#xff0c;我们可以像查询物理表一样查询它或者将其赋值给集合变量。 2、管道函数为并行执行&#xff0c;在…

【数据结构与算法】十大经典排序算法-冒泡排序

&#x1f31f;个人博客&#xff1a;www.hellocode.top &#x1f3f0;Java知识导航&#xff1a;Java-Navigate &#x1f525;CSDN&#xff1a;HelloCode. &#x1f334;掘金&#xff1a;HelloCode &#x1f31e;知乎&#xff1a;HelloCode ⚡如有问题&#xff0c;欢迎指正&#…

VSCode中如何修改代码字体

通过「File」→「Preferences」→「Settings」→「Text Editor」→「Font」→「Font Family」中&#xff0c;修改对应的字体即可。因为比较喜欢 JetBrains Mono&#xff0c;所以设置的字体是这个。 其中Jetbrains Mono字体需要自己在Jetbrains官网下载&#xff0c;然后中文字体…

09. Docker Compose

目录 1、前言 2、安装Docker Compose 2.1、Docker Compose版本 2.2、下载安装 3、初试Docker Compose 3.1、传统方案部署应用 3.2、使用编排部署应用 3.3、其他命令 3.3.1、ps 3.3.2、images 3.3.3、depends_on 3.3.4、scale 4、小结 1、前言 随着应用架构的不段…

Scala按天写入日志文件

如果希望把每天出错的信息写入日志文件&#xff0c;每天新建一个文件。 package test.scala import java.io.{File, FileWriter} import java.text.SimpleDateFormat import java.util.{Calendar, Date} import scala.concurrent.ExecutionContext.Implicits.global import sc…

Linux(CentOS7)系统磁盘分区及挂载

新购买的阿里云服务器&#xff0c;默认硬盘容量肯定不够用&#xff0c;需要额外购买硬盘&#xff0c;购买后需要对硬盘进行分区及挂载操作&#xff0c;下面是硬盘分区及挂载操作步骤&#xff1a; 1、查看未挂载的硬盘&#xff08;名称为/dev/vdb&#xff09;, 执行命令 fdisk …

SpringBoot运行流程源码分析------阶段二(run方法核心流程)

run方法核心流程 在分析和学习整个run方法之前&#xff0c;我们可以通过以下流程图来看下SpringApplication调用的run方法处理的核心操作包含哪些。 从上面的流程图中可以看出&#xff0c;SpringApplication在run方法中重点做了以下几步操作 获取监听器和参数配置打印banner…