animator_animator动画速度 - CSDN
精华内容
参与话题
  • Unity 动画系统:Animator

    千次阅读 2019-06-03 17:44:23
    Animator组件 Controller:关联到物体的Animator控制器。 Avatar:物体的Avatar。 Apply Root Motion:是使用动画本身还是使用脚本来控制角色的位置。 Update Mode:动画的更新模式。 Normal:同步更新,...

    Animator组件

       

    Controller:关联到物体的Animator控制器。

    Avatar:物体的Avatar。

    Apply Root Motion:是使用动画本身还是使用脚本来控制角色的位置。

    Update Mode:动画的更新模式。

    • Normal:同步更新,动画速度与运行速度相匹配,运行速度慢,动画慢。
    • Animate Physics:动画是有物理的相互作用时,用此模式。
    • Unscaled Time:不成比例的时间,动画忽略当前的运行速度。

    Culling Mode:动画的裁剪模式。

    • Always Animate:总是启用动画,不进行裁剪。
    • Cull Update Transforms:更新裁切。
    • Cull Completely:完全裁切。

    Animator Controller视图

    (Animation States)动画状态

       

    Motion 当前状态下的动画片段
    Speed 动画的默认速度
    Mirror 镜像
    Foot IK 是否使用Foot IK
    是否使用Foot IK 是否对没有动画的属性写回默认值
    Transitions 由当前状态出发的过渡条件列表
    • 黄色显示的状态未默认状态,指状态机首次激活时所进入的状态。可以在其他状态上右击选择Set as Layer Default State命令改变默任状态。
    • 每个状态机在检视视图中Transitions内的Solo(优先)和Mute(禁用)选择框用于控制动画在预览时的行为表现。
    • 在视图空白处右击选择Create State ---> Empty命令可以创建空动画状态。
    • 选中某状态右击,选中Copy或Delete,可以对状态进行复制或删除操作。
    • 将一段动画(Animation)拖入视图,可将动画添加到状态机中。
    • Any State:任意状态,为所有动画状态添加公共出口状态的便捷方式。但不能作为一种独立的目标状态。

    Animation Transitions(动画过渡)

    在某一动画状态右击后选择Make Transition,然后选择过渡到的下一动画状态,即可建立过渡联系。

       

    Animation Parameters(动画参数)

       

    参数值的四种基本类型:

    • Float:浮点数
    • Int:整数
    • Bool:返回布尔值,通过复选框来选择True或者False
    • Trigger:触发一个布尔值,复位控制器时消耗一个转变,由一个圆按钮表示。

    Animation Layers(动画层)

    实现同一时刻进行多种动画状态播放。

    Blend Tree(混合树)

    对两个或更多个相似的运动进行混合。

    制作:在视图右击空白处后选择Create State ---> From New Blend Tree,双击进入。

    示例:

      

    Blend Type(混合类型)

    1D混合:通过唯一的一个参数来控制子动画的混合。

    2D Simple Directional(2D简单定向模式)适用于所有动画都具有一定的运动方向、其中任何两段动画的运动方向都不相同的情形。

    2D Freeform Directional(2D自由定向模式)适用于所有动画都具有一定的运动方向,但同一方向上可以存在多段动画。

    2D Free Cartesian(2D自由笛卡尔模式)适用于动画不具有确定运动确定运动方向的情形。

    Direct:直接。让用户直接控制每个节点的权重。

     

     

     

    展开全文
  • Unity动画机制 AnimatorAnimator Controller教程

    万次阅读 多人点赞 2019-05-08 11:51:52
    Unity动画机制 AnimatorAnimator Controller教程 助力快速完成 Animator 动画的添加与控制 为初学者节省宝贵的时间,避免采坑! unity 中为我们提供了而全面的动画设计解决方案,用来完成游戏对象的动态效果的控制...

    Chinar blog www.chinar.xin

    Unity动画机制 Animator Animation


    本文提供全流程,中文翻译。

    Chinar 的初衷是将一种简单的生活方式带给世人

    使有限时间 具备无限可能

    Chinar —— 心分享、心创新!

    助力快速完成 Animator 动画的添加与控制

    为初学者节省宝贵的时间,避免采坑!

    Chinar 教程效果:
    这里写图片描述



    全文高清图片,点击即可放大观看 (很多人竟然不知道)


    1

    Animator —— 动画


    Unity 中为我们提供了而全面的动画设计解决方案,用来完成游戏对象的动态效果的控制和创建

    我们想要一个人物模型动起来,非常简单

    只需要在人物的游戏对象上添加 Animator 组件

    然后创建一个 Animator Controller 来控制对应的游戏对象的动画 Animator 即可
    举个栗子黑白88
    这里写图片描述
    这里写图片描述


    2

    Animator Controller —— 动画控制器面板


    选中 Animator Controller文件,点击 Open 即可打开控制器面板

    新创建一个 Animator Controller 默认是没有任何状态的

    需要自己创建空状态,并进行相应设置
    举个栗子黑白88
    这里写图片描述


    3

    Specifies Animation —— 指定动画


    创建一个状态后,我们需要给该状态改名(为了区分状态),并指定一个动作 Motion

    这里我创建的是 站立,所以就需要找到对应的 Idel 动画,指定给 Motion
    举个栗子黑白88
    这里写图片描述
    相同流程创建跑步动作:
    这里写图片描述


    4

    Status Swithing —— 状态切换


    由于丧尸这个游戏对象,具备跑和站立 2 个状态/动作 Motion

    我们需要的是点击鼠标,它就开始移动,保持跑起来的状态

    走到目的地,它就停下,保持站立状态

    所以这里 站立——跑,状态是需要切换的,如上图,我们可以简单的建立连接

    那么我们需要添加一个 bool 参数,并设置对应条件,来使2个状态可以进行切换

    添加完成后,我们就可以通过代码,来控制人物的站立和跑 2个状态了
    举个栗子黑白88
    这里写图片描述
    具体流程:
    这里写图片描述


    5

    Code Control —— 通过代码控制状态


    完成上边所有设置,即可通过代码控制 动画状态的切换

    调用 Animator 中的函数,通过设置 bool 值,即可改变 游戏对象的 动画状态

    animator.SetBool("ZombieRun", true) 让僵尸切换为 跑 状态

    animator.SetBool("ZombieRun", false) 让僵尸切换为 站立 状态

    Animator.StringToHash("ZombieRun")可以将字符参数转为 ID(int 值) ,同样用来控制状态
    举个栗子黑白88

    using UnityEngine;
    using UnityEngine.AI;
    
    
    /// <summary>
    /// <para>作用:控制丧尸的移动</para>
    /// <para>作者:Chinar</para>
    /// <para>创建日期:2018-08-05</para>
    /// </summary>
    public class ZombieMove : MonoBehaviour
    {
        private NavMeshAgent navMeshAgent;
        private Animator     animator;
    
    
        /// <summary>
        /// 初始化函数
        /// </summary>
        void Start()
        {
            navMeshAgent = GetComponent<NavMeshAgent>(); //获取自身AI组件
            animator     = GetComponent<Animator>();     //动画组件
        }
    
    
        /// <summary>
        /// 每帧刷新
        /// </summary>
        void Update()
        {
            if (Input.GetMouseButton(1)) //右键
            {
                object     ray = Camera.main.ScreenPointToRay(Input.mousePosition); //屏幕坐标转射线
                RaycastHit hit;                                                     //射线投射碰撞
                bool       isHit = Physics.Raycast((Ray) ray, out hit);             //射线投射(射线,结构体信息) ;返回bool 值 是否检测到碰撞
                if (isHit)
                {
                    print("坐标:" + hit.point);               //射线与物体碰撞点
                    navMeshAgent.SetDestination(hit.point); //AI组件,设置目的地/终点
                    animator.SetBool("ZombieRun", true);    //让僵尸跑起来
                }
            }
            if (navMeshAgent.remainingDistance < 0.5f) //当前位置 与终点 的  剩余距离<0.5f
            {
                animator.SetBool("ZombieRun", false); //让僵尸站立
            }
        }
    }
    

    动画效果:

    这里写图片描述


    支持

    May Be —— 开发者,总有一天要做的事!


    拥有自己的服务器,无需再找攻略

    Chinar 提供一站式《零》基础教程

    使有限时间 具备无限可能!

    先点击领取 —— 阿里全产品优惠券 (享受最低优惠)


    Chinar 免费服务器、建站教程全攻略!( Chinar Blog )


    Chinar

    END

    本博客为非营利性个人原创,除部分有明确署名的作品外,所刊登的所有作品的著作权均为本人所拥有,本人保留所有法定权利。违者必究

    对于需要复制、转载、链接和传播博客文章或内容的,请及时和本博主进行联系,留言,Email: ichinar@icloud.com

    对于经本博主明确授权和许可使用文章及内容的,使用时请注明文章或内容出处并注明网址

    展开全文
  • Unity动画状态机Animator使用

    万次阅读 多人点赞 2020-09-30 19:54:38
    2 Animator,5.x之后推荐使用这种方式,因为里面可以加上混合动画,让动画切换更加平滑 添加状态控制参数 编辑切换状态的条件 点击连线,添加条件,这个条件只会显示刚才添加的状态控制参数 AnimState,设置等于0...

    一、前言

    Unity可以用两种方式控制动画
    1 Animation,这种方式简单,直接 Play(“Idle”)或者CorssFade(“Idle”)就可以播放动画;
    2 AnimatorUnity5.x之后推荐使用这种方式,因为里面可以加上混合动画,让动画切换更加平滑。

    二、Animator组件

    你通过Animation窗口(快捷键是Ctrl+6)中的Create New Clip创建Animation时,一个 Animator已经悄无声息地出现在了对应的GameObject
    在这里插入图片描述

    三、Animator Controller文件

    在第一步中生成的Animator组件上, 第一个Controller参数在创建Animator时已经被赋值了,可以点击该值,并切换到Project窗口下,会发现这个 Controller对应的文件是一个.controller文件。
    Animator Controller就是动画控制器,负责在不同的动画间切换,属于制作动画效果的必备原件。

    在这里插入图片描述

    注意,你也可以通过GameObject上的 Add Component添加一个崭新的 Animator组件,但是这种情况下 AnimatorController参数默认为空,所以需要我们手动将事先准备好的.controller文件拖拽到该参数位置,动画控制器才能正常工作。

    四、Animation Clip文件

    双击 .controller"文件,会弹出一个 Animator窗口,该窗口中显示的就是动画控制器文件中的所有内容(也可以在顶部的工具栏通过 Window - Animator打开这个界面)
    在这里插入图片描述
    Project窗口右键单击,选择Create->Animation创建Animation Clip.anim文件)
    在这里插入图片描述
    在这里插入图片描述
    再把.anim文件拖拽进Animator窗口,作为Animator Controller的一个状态(State
    在这里插入图片描述
    通过Animator创建出来的Animation Clip无法直接通过挂Animation组件进行播放,如果强行播放,Console会报一条警告信息:

    The AnimationClip 'XXX' used by the Animation component 'XXX' must be marked as Legacy.
    

    以及一条提示信息

    Default clip could not be found in attached animations list
    

    如下
    在这里插入图片描述
    为什么呢?
    如果我们把Inspector切换为Debug模式
    在这里插入图片描述
    可以看到Animation Clip有个Legacy勾选框
    在这里插入图片描述
    Legacy是遗产的意思,也就是传统的通过Animation组件来播放Animation Clip的做法,如果使用Animation组件来播放Animation Clip,则必须把Legacy勾选上,不过这种方式已经是过时的做法,推荐使用Animator来播放Animation Clip

    五、 状态机的状态(State)

    每个Animator Controller都会自带三个状态:Any State, EntryExit
    在这里插入图片描述

    1、Any State状态

    表示任意状态的特殊状态。例如我们如果希望角色在任何状态下都有可能切换到死亡状态,那么Any State就可以帮我们做到。当你发现某个状态可以从任何状态以相同的条件跳转到时,那么你就可以用Any State来简化过渡关系。

    2、Entry状态

    表示状态机的入口状态。当我们为某个GameObject添加上Animator组件时,这个组件就会开始发挥它的作用。
    如果Animator Controller控制多个Animation的播放,那么默认情况下Animator组件会播放哪个动画呢? 由Entry来决定的。
    但是Entry本身并不包含动画,而是指向某个带有动画的状态,并设置其为默认状态。被设置为默认状态的状态会显示为 橘黄色。
    在这里插入图片描述
    当然,你可以随时在任意一个状态上通过 鼠标右键->Set as Layer Default State更改默认状态。
    在这里插入图片描述

    记住, EntryAnimator组件被激活后 无条件 跳转到默认状态,并且每个Layer有且仅有一个默认状态。

    3、Exit状态

    表示状态机的出口状态,以红色标识。如果你的动画控制器只有一层,那么这个状态可能并没有什么卵用。但是当你需要从子状态机中返回到上一层(Layer)时,把状态指向Exit就可以了。
    在这里插入图片描述

    六、动画状态的属性

    我们可以选中某个自定义状态,并在Inspector窗口下观察它具有的属性
    在这里插入图片描述

    属性名 描述
    Motion 状态对应的动画。每个状态的基本属性,直接选择已定义好的动画(Animation Clip)即可
    Speed 动画播放的速度。默认值为1,表示速度为原动画的1.0倍。
    Mutiplier 勾选右侧的Parameter后可用,即在计算Speed的时考虑 区域1 中定义的某个参数。若选择的参数为smooth, 则动画播放速度的计算公式为 smooth * speed * fps(animation clip中指定)
    Mirror 仅适用于humanoid animation(人型机动画)
    Cycle Offset 周期偏移,取值范围为0-1.0,用于控制动画起始的偏移量。把它和正弦函数的offset进行对比就能够理解了,只会影响起始动画的播放位置。
    Foot IK 仅适用于humanoid animation(人型机动画)
    Write Default 最好保持默认,感兴趣可以参考官方手册
    Transitions 该状态向其他状态发起的过渡列表,包含了Solo和Mute两个参数,在预览状态机的效果时起作用
    Add Behaviour 用于向状态添加“行为”

    七、状态间的过渡关系(Transitions)

    直观上说它们就是连接不同状态的有向箭头
    在这里插入图片描述

    要创建一个从状态A状态B的过渡,直接在状态A上 鼠标右键 - Make Transition并把出现的箭头拖拽到状态B上点击鼠标左边即可。
    在这里插入图片描述

    八、添加状态控制参数

    参数有FloatIntBoolTrigger
    在这里插入图片描述
    FloatInt用来控制一个动画状态的参数,比如速度方向等可以用数值量化的东西,
    Bool用来控制动画状态的转变,比如从走路转变到跑步,
    Trigger本质上也是bool类型,但它默认为false,且当程序设置为true后,它会自动变回false

    如下这里创建一个Int类型的参数AnimState
    在这里插入图片描述

    九、编辑切换状态的条件

    点击连线,在Inspecter窗口中可以进行设置,在Conditions栏下可以添加条件,如下图表示当参数
    AnimState0时会执行这个动画Any StateNew Animation2的过渡

    必须在Parameters面板中添加了参数才可以在这里查看到,其次添加的条件为&&”与”关系,即必须同时满足。

    在这里插入图片描述

    十、代码中控制状态

    我们可以通过代码来设置条件状态,达到动画切换的目的

    Animator ator = go1.GetComponent<Animator>();
    ator.SetInteger("AnimState", 0);
    

    上面的代码,让AnimState这个参数值为0,满足了从Any StateNew Animation2的过渡条件,从而实现New Animation2动画的过渡。

    十一、检查动画状态

    方法1、AnimatorStateInfo

    在脚本中添加代码

    //检查是否正在播放jump动画.
    AnimatorStateInfo stateinfo = anim.GetCurrentAnimatorStateInfo(0);   
    bool playingJump = stateinfo.IsName("jump");
    if(playingJump)
    {
    	if(stateinfo.normalizedTime < 1.0f)
    	{
    		//正在播放
    	}
    	else
    	{
    		//播放结束
    	}
    	
    }
    

    当处于状态jump,则stateinfo.IsName("jump")返回true

    方法2、继承StateMachineBehaviour

    Animator的每个状态都可以挂载脚本,创建脚本,继承于StateMachineBehaviour类,用于检测状态机中动画切片(Anamation)的运行状态。
    官方示例:https://docs.unity3d.com/ScriptReference/StateMachineBehaviour.html
    将脚本挂载在对应的状态上即可。代码如下

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class JumpState : StateMachineBehaviour
    {
        private GameObject player;
    
    
        override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
        {
            // 正在played的状态的第一帧被调用
            Debug.Log("------OnStateEnter------------");
        }
    
        // OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
        override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
        {
            
        }
    
        // OnStateExit is called when a transition ends and the state machine finishes evaluating this state
        override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
        {
            // 转换到另一个状态的最后一帧 被调用
            Debug.Log("-------------OnStateExit-----------------");
        }
    
        // OnStateMove is called right after Animator.OnAnimatorMove()
        override public void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
        {
        	// 在OnAnimatorMove之前被调用 
            
        }
    
        // OnStateIK is called right after Animator.OnAnimatorIK()
        override public void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
        {
            // 在OnAnimatorIK之后调用,用于在播放状态时的每一帧的monobehavior。
            // 需要注意的是,OnStateIK只有在状态位于具有IK pass的层上时才会被调用。
            // 默认情况下,图层没有IK通道,所以这个函数不会被调用
            // 关于IK的使用,可以看看这篇文章《Animator使用IK实现头部及身体跟随》
            // https://www.jianshu.com/p/ae6d65563efa
        }
    }
    

    十二、控制播放速度

    Animator ator = go1.GetComponent<Animator>();
    var stateinfo = ator.GetCurrentAnimatorStateInfo(0);
    if(stateinfo.IsName("Jump"))
    {
    	ator.speed = 2;
    }
    

    十三、注意事项

    1 取消勾选 Can Transition To Self,不然动画会出现抖动
    在这里插入图片描述

    2 动作循环。不然如果没有下个状态切换,直接停止动作
    在这里插入图片描述

    3 Has Exit Time,如果勾选了,则表示在该动作完成后才允许切换,但是一般我们要的都是立即切换,所以这里 不要勾选
    在这里插入图片描述

    十四、补充

    1、Mirror

    镜像,可以反转当前动画,减少动画师工作量
    在这里插入图片描述

    2、Solo与Mute

    Mute相当于把目标过渡禁用掉。Solo表示只生效这一条过渡
    可以多选,当选中后会出现箭头提示
    条件满足优先于Solo/Mute,当条件没有满足时依然不会过渡
    在这里插入图片描述

    展开全文
  • 属性动画之Animator API基本使用

    千次阅读 2019-05-16 17:21:23
    上一篇中我们简单的介绍了如何使用视图动画Animation API的基本使用,今天就来介绍一下功能更为强大的属性动画Animator API的基本使用,首先我们来看一看Animator的继承结构: Animator public abstract class ...

            上一篇中我们简单的介绍了如何使用视图动画Animation API的基本使用,今天就来介绍一下功能更为强大的属性动画Animator API的基本使用,首先我们来看一看Animator的继承结构:

    Animator

    public abstract class Animator 
    extends Object implements Cloneable

    java.lang.Object
       ↳ android.animation.Animator
    Known direct subclasses

    AnimatorSetValueAnimator

    Known indirect subclasses

    ObjectAnimatorTimeAnimator

     

    上面是官方API给出的解答,下面是我简单绘制的关系图:

     

    其中,使用最多的就是ObjectAnimator以及ObjectAnimator配合AnimatorSet使用,下面就来看一看具体的用法。

     

    1,ObjectAnimator基本使用

     

    1.1 创建方法

    (1)通过它的静态工厂类获取,比如:

    ObjectAnimator animator1= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"alpha",1,0,1);

    (2)通过资源文件配合AnimatorInflater获取,比如:

    ObjectAnimator animator9= (ObjectAnimator) AnimatorInflater.loadAnimator(this,R.animator.animator1);

    1.2 动画属性参数

    和视图动画类似,这里分的更加细致了,主要有以下几个:

    • alpha
      ObjectAnimator animator1= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"alpha",1,0,1);
      
    • scaleX
      ObjectAnimator animator2= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"scaleX",1,0,1);
      
    • scaleY
      ObjectAnimator animator3= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"scaleY",1,0,1);
      
    • rotationX
      ObjectAnimator animator4= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"rotationX",0,180,0);
      
    • rotationY
      ObjectAnimator animator5= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"rotationY",0,240,0);
      
    • rotation
      ObjectAnimator animator6= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"rotation",0,360,0);
      
    • translationX
      ObjectAnimator animator7= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"translationX",10,100,200,400);
      
    • translationY
      ObjectAnimator animator8= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"translationY",10,100,200,400);
      

     

    上面的属性都是默认自带的属性,我们也可以通过自定义属性的方式来添加新的属性,比如给一个ImageView对象添加宽度属性,首先需要定义各一个包装类WrapperView,在其中实现getXXX和setXXX即可(该部分代码直接源于《Android群英传》):

    package com.hfut.operationanimator;
    
    import android.widget.ImageView;
    
    /**
     * author:why
     * created on: 2018/9/8 23:18
     * description:
     */
    public class WrapperView {
        ImageView mImageView;
        public WrapperView(ImageView imageView){
            this.mImageView=imageView;
        }
    
        public int getWidth(){
            return mImageView.getLayoutParams().width;
        }
    
        public void setWidth(int width){
            mImageView.getLayoutParams().width=width;
            mImageView.requestLayout();
        }
    }
    

     

    应用(自定义width属性):

            WrapperView wrapperView=new WrapperView(imageView);        
            ObjectAnimator objectAnimator10=ObjectAnimator.ofInt(wrapperView,"width",100);

     

    1.3 ObjectAnimator示例

    下面就来看一下ObjectAnimator配合AnimatorSet的示例:

     

    activity_main.xml代码:

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout 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"
        tools:context="com.hfut.operationanimator.MainActivity">
    
        <Button
            android:id="@+id/control_text_button"
            android:text="文本动画"
            android:onClick="textAnimator"
            android:textSize="30sp"
            android:layout_marginTop="20dp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    
        <ImageView
            android:layout_marginTop="45px"
            android:id="@+id/test_image"
            app:layout_constraintTop_toBottomOf="@id/control_text_button"
            android:src="@mipmap/image"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            android:layout_width="500dp"
            android:layout_height="420dp" />
    
    </android.support.constraint.ConstraintLayout>
    

     

    MainActivity代码:

    package com.hfut.operationanimator;
    
    import android.animation.Animator;
    import android.animation.AnimatorInflater;
    import android.animation.AnimatorListenerAdapter;
    import android.animation.AnimatorSet;
    import android.animation.ObjectAnimator;
    import android.animation.ValueAnimator;
    import android.os.Handler;
    import android.os.Message;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.View;
    import android.widget.ImageView;
    import android.widget.TextView;
    import android.widget.Toast;
    
    /**
     * @author why
     * @date 2018-9-6 20:55:46
     */
    public class MainActivity extends AppCompatActivity {
        private static final String TAG = "MainActivity";
    
        TextView textView;
        ImageView imageView;
    
    //    Handler handler=new Handler(){
    //        @Override
    //        public void handleMessage(Message msg) {
    //            Toast.makeText(MainActivity.this,"透明度在0.3左右了,开始同步旋转",Toast.LENGTH_SHORT).show();
    //            //animator2.start();
    //            ObjectAnimator animator3= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"alpha",0.3f,1);
    //            ObjectAnimator animator2=(ObjectAnimator) ObjectAnimator.ofFloat(imageView,"rotation",0,90);
    //            animator2.addListener(new AnimatorListenerAdapter() {
    //                @Override
    //                public void onAnimationCancel(Animator animation) {
    //                    super.onAnimationCancel(animation);
    //                }
    //
    //                @Override
    //                public void onAnimationStart(Animator animation) {
    //                    super.onAnimationStart(animation);
    //                }
    //
    //                @Override
    //                public void onAnimationEnd(Animator animation) {
    //                    super.onAnimationEnd(animation);
    //                    Toast.makeText(MainActivity.this,"动画结束,好好欣赏妹子吧",Toast.LENGTH_SHORT).show();
    //                }
    //            });
    //            animator2.setDuration(4000);
    //            animator3.setDuration(4000);
    //            AnimatorSet animatorSet=new AnimatorSet();
    //            animatorSet.playTogether(animator2,animator3);
    //            animatorSet.start();
    //        }
    //    };
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            imageView=findViewById(R.id.test_image);
            imageView.setImageDrawable(getResources().getDrawable(R.mipmap.image));
        }
    
    
        public void textAnimator(View view){
    //
            ObjectAnimator animator1= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"alpha",0,1);
            ObjectAnimator animator2= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"scaleX",1,0,1);
            ObjectAnimator animator3= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"scaleY",1,0,1);
            ObjectAnimator animator4= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"rotationX",0,180,0);
            ObjectAnimator animator5= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"rotationY",0,240,0);
            ObjectAnimator animator6= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"rotation",0,450);
            ObjectAnimator animator7= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"translationX",imageView.getX()-45,100);
            ObjectAnimator animator8= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"translationY",imageView.getY(),300);
    
            ObjectAnimator animator9= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"translationX",100,10);
            ObjectAnimator animator10= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"scaleX",1,1.2f);
            ObjectAnimator animator11= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"scaleY",1,1.2f);
            ObjectAnimator animator12= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"translationY",300,200);
    
    
            // ObjectAnimator animator9= (ObjectAnimator) AnimatorInflater.loadAnimator(this,R.animator.animator1);
    
             animator1.setDuration(1000);
             animator1.start();
    
            animator2.setDuration(1000);
            animator2.setStartDelay(300);
    
            animator3.setDuration(1000);
            animator3.setStartDelay(300);
    
            animator4.setDuration(1000);
            animator4.setStartDelay(300);
    
            animator5.setDuration(1000);
            animator5.setStartDelay(300);
    
            animator6.setDuration(1000);
            animator6.setStartDelay(300);
    
            animator7.setDuration(1000);
            animator7.setStartDelay(300);
    
            animator8.setDuration(1000);
            animator8.setStartDelay(300);
    
            animator9.setDuration(2000);
            animator9.setStartDelay(300);
            animator10.setDuration(2000);
            animator10.setStartDelay(300);
            animator11.setDuration(2000);
            animator11.setStartDelay(300);
            animator12.setDuration(2000);
            animator12.setStartDelay(300);
    
            AnimatorSet animatorSet1=new AnimatorSet();
            animatorSet1.playTogether(animator10,animator11,animator12);
    
    
            AnimatorSet set=new AnimatorSet();
            set.playSequentially(animator1,animator2,animator3,animator4,animator5,animator6,animator7,animator8,animator9,animatorSet1);
            //set.playSequentially(animator1,animator2,animator3,animator4,animator5,animator6,animator7,animator8,animator9,animator10,animator11);
            set.start();
    
    
    //        WrapperView wrapperView=new WrapperView(imageView);
    //        ObjectAnimator objectAnimator10=ObjectAnimator.ofInt(wrapperView,"width",100);
    
    
    //        final ValueAnimator valueAnimator=ValueAnimator.ofFloat(0,1.0f);
    //       // valueAnimator.setTarget(imageView);
    //        valueAnimator.setDuration(6000);
    //        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    //            @Override
    //            public void onAnimationUpdate(ValueAnimator animation) {
    //                float endAlpha= (float) animation.getAnimatedValue();
    //                Log.d(TAG, "onAnimationUpdate: "+endAlpha);
    //                if(endAlpha>=0.29f&&endAlpha<=0.31f){
    //                    valueAnimator.cancel();
    //                    //Toast.makeText(MainActivity.this,"透明度在0.5左右了",Toast.LENGTH_SHORT).show();
    //                    Message message=new Message();
    //                    handler.sendMessage(message);
    //                }
    //            }
    //        });
    //        valueAnimator.start();
        }
    }
    

    上面的代码注意几点:

    (1)每一个Animator可以单独作用到一个View上

    (2)多个Animator可以通过ANimatorSet作用到View上

    (3)AnimatorSet中可以嵌套AnimatorSet

    (4)AnimatorSet中添加的多个动画有以上两种播放模式(不考虑参数类型):

    • set.playTogether()
    • set.playSequentially()

    (5)AnimatorSet播放动画时长可以自己设置,也可以通过其添加的动画自己设置,比如:

    AnimatorSet set=new AnimatorSet();
    set.setDuration(2000);

    也可以通过上例中来设置。

     

    2 ,ValueAnimator基本使用

    2.1 基础理解

    从最开始的继承关系图可以看出,它是ObjectAnimator的父类,它本身不产生任何动画效果,但是我们可以通过它实现对我们动画更加精确的控制,这里面我们可以通过对其添加更新监听实时获取动画的相关参数数值,从而实现对动画的精确控制,下面我就来提一个需求:

    实现当一个View 的透明度在0.3的时候开启同步旋转放大动画?下面就来看看具体实现

     

    2.2 示例实现

    效果图如下:

     

    activity_main.xml代码:

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout 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"
        tools:context="com.hfut.operationanimator.MainActivity">
    
        <Button
            android:id="@+id/control_text_button"
            android:text="文本动画"
            android:onClick="textAnimator"
            android:textSize="30sp"
            android:layout_marginTop="20dp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    
        <ImageView
            android:layout_marginTop="150px"
            android:id="@+id/test_image"
            app:layout_constraintTop_toBottomOf="@id/control_text_button"
            android:src="@mipmap/image"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            android:layout_width="500dp"
            android:layout_height="420dp" />
    
    </android.support.constraint.ConstraintLayout>
    


    Mainactivity代码:

    package com.hfut.operationanimator;
    
    import android.animation.Animator;
    import android.animation.AnimatorInflater;
    import android.animation.AnimatorListenerAdapter;
    import android.animation.AnimatorSet;
    import android.animation.ObjectAnimator;
    import android.animation.ValueAnimator;
    import android.os.Handler;
    import android.os.Message;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.View;
    import android.widget.ImageView;
    import android.widget.TextView;
    import android.widget.Toast;
    
    /**
     * @author why
     * @date 2018-9-6 20:55:46
     */
    public class MainActivity extends AppCompatActivity {
        private static final String TAG = "MainActivity";
        ImageView imageView;
    
        Handler handler=new Handler(){
            @Override
            public void handleMessage(Message msg) {
                Toast.makeText(MainActivity.this,"透明度在0.3左右了,开始同步旋转",Toast.LENGTH_SHORT).show();
                //animator2.start();
                ObjectAnimator animator1=(ObjectAnimator) ObjectAnimator.ofFloat(imageView,"scaleX",1,1.3f);
                ObjectAnimator animator2=(ObjectAnimator) ObjectAnimator.ofFloat(imageView,"scaleY",1,1.3f);
                ObjectAnimator animator3=(ObjectAnimator) ObjectAnimator.ofFloat(imageView,"rotation",0,90);
                animator2.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationCancel(Animator animation) {
                        super.onAnimationCancel(animation);
                    }
    
                    @Override
                    public void onAnimationStart(Animator animation) {
                        super.onAnimationStart(animation);
                    }
    
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);
                        Toast.makeText(MainActivity.this,"动画结束,好好欣赏妹子吧",Toast.LENGTH_SHORT).show();
                    }
                });
                animator1.setDuration(5000);
                animator2.setDuration(5000);
                animator3.setDuration(5000);
    
                AnimatorSet animatorSet=new AnimatorSet();
                animatorSet.playTogether(animator1,animator2,animator3);
                animatorSet.start();
            }
        };
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            imageView=findViewById(R.id.test_image);
            imageView.setImageDrawable(getResources().getDrawable(R.mipmap.image));
        }
    
    
        public void textAnimator(View view){
    //
    //        ObjectAnimator animator1= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"alpha",0,1);
    //        ObjectAnimator animator2= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"scaleX",1,0,1);
    //        ObjectAnimator animator3= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"scaleY",1,0,1);
    //        ObjectAnimator animator4= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"rotationX",0,180,0);
    //        ObjectAnimator animator5= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"rotationY",0,240,0);
    //        ObjectAnimator animator6= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"rotation",0,450);
    //        ObjectAnimator animator7= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"translationX",imageView.getX()-45,100);
    //        ObjectAnimator animator8= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"translationY",imageView.getY(),300);
    //
    //        ObjectAnimator animator9= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"translationX",100,10);
    //        ObjectAnimator animator10= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"scaleX",1,1.2f);
    //        ObjectAnimator animator11= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"scaleY",1,1.2f);
    //        ObjectAnimator animator12= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"translationY",300,200);
    //
    //
    //        // ObjectAnimator animator9= (ObjectAnimator) AnimatorInflater.loadAnimator(this,R.animator.animator1);
    //
    //         animator1.setDuration(1000);
    //         animator1.start();
    //
    //        animator2.setDuration(1000);
    //        animator2.setStartDelay(300);
    //
    //        animator3.setDuration(1000);
    //        animator3.setStartDelay(300);
    //
    //        animator4.setDuration(1000);
    //        animator4.setStartDelay(300);
    //
    //        animator5.setDuration(1000);
    //        animator5.setStartDelay(300);
    //
    //        animator6.setDuration(1000);
    //        animator6.setStartDelay(300);
    //
    //        animator7.setDuration(1000);
    //        animator7.setStartDelay(300);
    //
    //        animator8.setDuration(1000);
    //        animator8.setStartDelay(300);
    //
    //        animator9.setDuration(2000);
    //        animator9.setStartDelay(300);
    //        animator10.setDuration(2000);
    //        animator10.setStartDelay(300);
    //        animator11.setDuration(2000);
    //        animator11.setStartDelay(300);
    //        animator12.setDuration(2000);
    //        animator12.setStartDelay(300);
    //
    //        AnimatorSet animatorSet1=new AnimatorSet();
    //        animatorSet1.playTogether(animator10,animator11,animator12);
    
    
           // AnimatorSet set=new AnimatorSet();
            //set.setDuration(2000);
            //set.playSequentially(animator1,animator2,animator3,animator4,animator5,animator6,animator7,animator8,animator9,animatorSet1);
            //set.playSequentially(animator1,animator2,animator3,animator4,animator5,animator6,animator7,animator8,animator9,animator10,animator11);
            //set.start();
    
    
    //        WrapperView wrapperView=new WrapperView(imageView);
    //        ObjectAnimator objectAnimator10=ObjectAnimator.ofInt(wrapperView,"width",100);
            ObjectAnimator animator3= (ObjectAnimator) ObjectAnimator.ofFloat(imageView,"alpha",0,1);
            animator3.setDuration(8000);
            animator3.start();
            final ValueAnimator valueAnimator=ValueAnimator.ofFloat(0,1.0f);
           // valueAnimator.setTarget(imageView);
            valueAnimator.setDuration(8000);
            valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    float processAlpha= (float) animation.getAnimatedValue();
                    Log.d(TAG, "onAnimationUpdate: "+processAlpha);
                    if(processAlpha>=0.29f&&processAlpha<=0.31f){
                        valueAnimator.cancel();
                        //Toast.makeText(MainActivity.this,"透明度在0.5左右了",Toast.LENGTH_SHORT).show();
                        Message message=new Message();
                        handler.sendMessage(message);
                    }
                }
            });
            valueAnimator.start();
        }
    }
    

    下面是日志信息:

    可见,此API可以实时获取动画属性值,可以帮助我们做更为精确的控制。

     

    3, PropertyValuesHolder的基本使用

    其功能和AnimatorSet中的playTogether()接口类似,用于定义一个View多个动画同时播放,下面给出简单的演示代码,效果示例就不完整写了:

    PropertyValuesHolder valuesHolder1=PropertyValuesHolder.ofFloat("scaleX",1,1.1f);
    PropertyValuesHolder valuesHolder2=PropertyValuesHolder.ofFloat("scaleY",1,1.1f);
    PropertyValuesHolder valuesHolder3=PropertyValuesHolder.ofFloat("alpha",0.5f,1.0f);
    ObjectAnimator.ofPropertyValuesHolder(imageView,valuesHolder1,valuesHolder2,valuesHolder3).setDuration(3000).start();

    4,xml实现属性动画

    4.1 单个属性动画实现

    这个在上面已经介绍过了

    4.2 多个属性动画实现

    说到这里,我们就需要先看一下官方API了:

    从api 23开始,可以使用PropertyValuesHolderKeyframe在资源文件中创建更复杂的动画。使用PropertyValuesHolders允许Animator并行地动画几个属性,如本示例所示:

    <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:repeatCount="1"
    android:repeatMode="reverse">
    <propertyValuesHolder
        android:propertyName="x"
        android:valueTo="400" />
    <propertyValuesHolder
        android:propertyName="y"
        android:valueTo="200" />
    </objectAnimator>
    

    使用关键帧允许动画遵循更复杂的路径从开始到结束值。请注意,您可以为每个关键帧指定显式小数值(从0到1),以确定动画在总体持续时间内何时应该达到该值。或者,您可以离开分数,关键帧将在整个持续时间内平均分布。此外,当动画器启动时,没有值的关键帧将从目标对象中派生出它的值,就像只指定一个值的动画师一样。此外,还可以指定一个可选的内插器。内插器将应用于内插器设置的关键帧与前一个关键帧之间的间隔上。如果没有提供内插器,则默认AccelerateDecelerateInterpolator会被使用。

    <propertyValuesHolder android:propertyName="x">
    <keyframe
        android:fraction="0"
        android:value="800" />
    <keyframe
        android:fraction=".2"
        android:interpolator="@android:anim/accelerate_interpolator"
        android:value="1000" />
    <keyframe
        android:fraction="1"
        android:interpolator="@android:anim/accelerate_interpolator"
        android:value="400" />
    </propertyValuesHolder> <propertyValuesHolder android:propertyName="y">
    <keyframe />
    <keyframe
        android:fraction=".2"
        android:interpolator="@android:anim/accelerate_interpolator"
        android:value="300" />
    <keyframe
        android:interpolator="@android:anim/accelerate_interpolator"
        android:value="1000" />
    </propertyValuesHolder>

    说白了,就是可以通过<objectAnimator>标签下的<propertyValuesHolder>子标签同时定义多个动画作用于一个View,具体的应用和单个xml属性动画一样。这里就不在累述了。

     

    5,View的animate方法

    这个API也是伴随属性动画产生而产生的,下面是其主要使用方法的关键代码:

    imageView.animate().translationX(100).translationY(200)
            .alpha(0.2f).rotation(90).setDuration(8000).withStartAction(
            new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(MainActivity.this,"动画开始",Toast.LENGTH_SHORT).show();
                }
            }
    ).withEndAction(new Runnable() {
        @Override
        public void run() {
            Toast.makeText(MainActivity.this,"动画结束",Toast.LENGTH_SHORT).show();
        }
    }).start();

    效果图如下:

     

    由结果可知:

    • 这是一种动画集合效果的简易书写方式
    • 可以动画集合结束和开始处做相应的逻辑操作
    • 其实就是对动画的API做了一些封装
    • 里面的参数不是增量,是最终值,具体请看下面,我们拿translationX(float value)举例,其源码为:
    /**
     * This method will cause the View's <code>translationX</code> property to be animated to the
     * specified value. Animations already running on the property will be canceled.
     *
     * @param value The value to be animated to.
     * @see View#setTranslationX(float)
     * @return This object, allowing calls to methods in this class to be chained.
     */
    public ViewPropertyAnimator translationX(float value) {
        animateProperty(TRANSLATION_X, value);
        return this;
    }

    关于参数的解释:

    @param value The value to be animated to. 所以这个是目标值,不是增量值。

    总结:好的,到这里,关于这一块的内容就介绍完了,前前后后也写了不少时间。整体的内容不难,多动手操作一下,会体会很多,这里面我关于xml实现属性动画结束的比较少,但其实和接口实现一样,就是属性的组合而已。

    注:欢迎扫码关注

     

    展开全文
  • Animator功能

    千次阅读 2018-01-02 16:31:54
    获取当前播放动画的名称 ...2.将Animator的动画倒着播放 将动画片段拖入animator中并添加一个float类型的参数为-1. 将该参数赋值到该动画片段的Multiplier参数中. 脚本中播放该动画 ActionCtl.animator.Pl
  • Android Animator(Android动画)

    千次阅读 2016-09-20 11:24:54
    Android动画分为两类: 1.View Animation(视图动画,在api1引入)View Animation又分为两类:Frame animation(幁动画) 和Tween animation (补间动画) 2.Property Animator(属性动画,在api11引入)
  • Animator介绍

    2015-02-03 22:23:40
    一、ObjectAnimator 重要的方法: public static ObjectAnimator ofFloat(Objecttarget, String propertyName, float... values); 构造并返回一个ObjectAnimator对象。 注意values值的意义: 1.如果values只有一个值...
  • Android动画Animator家族使用指南

    千次阅读 2018-12-27 14:23:14
    零、前言:本文知识点 ValueAnimator的认识与使用 估值器TypeEvaluator的自定义与使用 插值器TimeInterpolator的自定义与使用 Path于Animator的结合使用 ObjectAnimator的自定义...Animator家族的监听器介绍与使用...
  • Animator

    千次阅读 2018-04-24 16:24:03
    1. 准备工作a) 通过FBX创建AnimationClip文件。(目的:更新动作后不会对动作状态机产生影响)核心代码:b) 替换动画状态机内的文件(目的:动画文件进行修改后,替换进入动画状态机)核心代码:2....
  • Unity/Animator -- 创建Animator Controller

    万次阅读 多人点赞 2017-09-09 14:13:08
    然而,通常情况下,一个单独的动画(即Animation Clip)可能无法很好地达到我们期望的效果,所以这时Animator Controller就能发挥其用武之地,帮助我们在合适的时间触发合适的动画,而不是在一个动画效果上无限循环。
  • Unity3D AnimatorController

    万次阅读 2014-11-07 10:46:04
    官方的潜行游戏模型资源
  • Unity判断Animator动画是否播放完毕

    万次阅读 2017-05-08 11:20:36
    Unity判断Animator动画是否播放完毕private Animator animator; void Start() { animator = this.GetComponent<Animator>(); } void Update() { AnimatorStateInfo info =animator.GetCurre
  • Unity3D Animator不常用方法

    万次阅读 2015-08-26 10:43:17
    原创文章如需转载请注明:转载自 脱莫柔Unity3D学习之旅 QQ群:【Unity3D(AR/VR) 334163814】...Animator不常用方法 回到起始帧 public void animToStart() { animator.Play("Take 001", 0, 0f); animator.Upda
  • Unity AnimatorController注意事项

    千次阅读 2017-02-23 10:58:37
    通过assetbundle加载的单独打包AnimatorController使用下面方法赋值 Go.GetComponent().runtimeAnimatorController = (RuntimeAnimatorController) obj; 通过Resouce.load 加载的AnimatorController...
  • public static animator ani; // 创建状态机 void Start() { ani = this.GetComponent(); 获取物体上的animator组建 } void Update() { AnimatorStateInfo info =animator.GetCurrentAnimatorStateInfo...
  • 获取当前播放Animator的动画时间

    万次阅读 2016-05-23 13:10:04
    还是在写小游戏的时候遇到了这个问题,然后就各种搜索啊都没有找到想要的结果,之后偶然发现动画的那个inspector界面... Animator anim = GetComponent(); Debug.Log(anim.GetCurrentAnimatorStateInfo(0).length);
  • 状态机Attack 的Motion 动作为...一般在 Start函数里面获得Animator组件 animator = =GetComponent&lt;Animator&gt;();//获得当前挂载脚本的 物体 的 Animator组件 animator = GetComponentInChildren...
  • Animator does not have an AnimatorController

    千次阅读 2020-03-30 15:16:15
    Animator does not have an AnimatorController unity3d 5.6.xx会报这个警告; Animator does not have an AnimatorController,这是因为,unity3d 要播放这个动画必须保证这个对象是activeself = true的,所以...
  • using UnityEngine; using System.Collections; public class PlayAminitors : MonoBehaviour { public GameObject gaminitor; public RuntimeAnimatorController controller1,controller2;...
  • unity Animator Override Controller的使用

    千次阅读 2017-10-22 15:53:32
    这里给大家简单介绍下Animator Override Controller。 Animator Override Controller是用来配合Animator Controller使用的,它让Animator Controller变得更加实用,可以让不同的使用实例的在同一状态播放不同的动作...
1 2 3 4 5 ... 20
收藏数 24,137
精华内容 9,654
关键字:

animator