目录
动画详情之属性动画(二)

1.ValueAnimator

1.1 引入属性动画的原因

视图动画虽然能够实现控件的透明度变化、移动、拉伸、旋转,但是无法改变控件的内部属性。看过上篇文章的知道,本质上视图动画就是通过强制父容器重新绘制子View来实现,但是并没有改变子view的内部属性(如颜色,大小,宽高,位置)。例如将TextView从位置A移动到位置C,分别使用视图动画和属性动画。

  • 视图动画中,如果给子view设置点击事件,只有在子view的初始位置(A)才会触发点击事件,点击子view的最终位置(C)或者移动轨迹中的位置(B)都不会触发点击事件。属性动画主要针对控件的某个属性做动画。
  • 属性动画中,动画开始前,点击位置A触发点击事件,动画开始时,点击运动中的控件即位置B可触发点击事件,动画结束时,点击位置C触发点击事件
    视图动画和属性动画的区别

1.2 ValueAnimator的简单使用

  • 第一步,创建ValueAnimator实例
kotlin
复制代码
val animator = ValueAnimator.ofInt(0,400).apply{
                       duration = 2000
	}
animator.start()

在这里我们利用ValueAnimator.ofInt()创建了一个值从0 到 400 变化的动画,动画时长为 2s,从代码中,可以看到,valueAnimator 没有和任何控件相关联,只是进行了值的计算,

  • 下一步我们需要对valueAnimator的值进行监听,然后自己根据valueAnimator的值来控制控件
kotlin
复制代码
  animator.addUpdateListener {
    val curValue =  it.animatedValue.toString()
    Log.d("qin", curValue)  
//根据valueAnimmator的值来变换textview的位置
      tvText.layout(500,curValue.toIntOrNull()?:0,500 + tvText.width,(curValue.toIntOrNull()?:0).plus(tvText.height))
 }

打印log如下图片描述
注意看打印出来的值不一定连续,有可能重复,有知道原因的好兄弟可以在评论区说一下

1.3 常用函数

  • public static ValueAnimator ofInt(int... values)
    接收可变长参数,接收的参数越多,变化越多越复杂

  • public static ValueAnimator ofFloat(Float... values)
    接收可变长参数,接收的参数越多,变化越多越复杂

  • ValueAnimator setDuration(long duration) 设置动画持续时间

  • Object getAnimatedValue() 监听ValueAnimator动画,获取当前的值

  • start() 开始动画

  • setRepeatCount(int vlaue) 设置循环次数,值为INFININTE(无限循环动画时,在Activity结束时,需要手动调用cancel(),防止内存泄露

  • setRepeatMode(int value) 设置循环模式,值为ValueAnimator.REVERSE 或者ValueAnimator.RESTART

  • void cancel() 取消动画

  • removeUpdateListener(AnimatorUpdateListener listener)移除值变化监听器

  • removeAllUptateListeners() 移除所有值变化的监听器

  • removeListener(AnimatorListener listener)移除Animator中指定的监听器

  • removeAllListeners() 移除所有监听器

  • public void setStartDelay(long startDelay) 设置动画延迟多少秒开始

例子如下

kotlin
复制代码
val animator = ValueAnimator.ofFloat(0f,100f,30f,300f)
animator.duration = 3000
animator.addUpdateListener{
	val curValueFloat = it.animatedValue.toString()
	val curValue = curValueFloat.toIntOrNull()
        tv.layout(curValue?:0,curValue?:0,(curValue?:0).plus(tv.width),(curValue?:0).plus(tv.heigth))
}

上面代码实现了将textview将从屏幕(0,0)移动到(100,100),再移动到(30,30),最后移动到(300,300)

ValueAnimator有两个不同的监听器,一个addUpdateListener监听值的变化过程,一个AnimatorListener监听动画的某个瞬间

kotlin
复制代码
public static interface AnimatorListener{
 void onAnimatorStart(Animator animation); //动画开始
 void onAnimatorEnd(Animator animation);  //动画结束
 void onAnimatorCancel(Animator animation); //动画取消
 void onAnimatorRepeat(Animator animation); //动画重复
}

开发者可以通过监听ValueAnimator的四个状态start、end、cancel、repeat,来针对动画的不同状态做出特殊处理。

1.4插值器(Interpolator)

在线画图工具

1.4.1 AcclerateDecelerateInterpolator(加速减速插值器)

找到它的源码,找出计算方式

java
复制代码
@HasNativeInterpolator
public class AccelerateDecelerateInterpolator extends BaseInterpolator
        implements NativeInterpolator {
    //...省略其他代码
    public float getInterpolation(float input) {
        return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
    }
    
}

加速减速
可以看出,这时一个余弦函数,使用了这个插值器,刚开始动画的变化速率较慢,然后慢慢加快,最后减慢,然后不断循环,斜率越大的地方,变化越快。

1.4.2 AnticipateInterpolator

AnticipateInterpolator 是初始偏移插值器,表示动画刚开始时向前偏移一段距离,然后应用动画
跟踪源码

java
复制代码
public class AnticipateInterpolator extends BaseInterpolator implements NativeInterpolator {
    private final float mTension;

    public AnticipateInterpolator() {
        mTension = 2.0f;
    }
    //省略其他代码
    public float getInterpolation(float t) {
        // a(t) = t * t * ((tension + 1) * t - tension)
        return t * t * ((mTension + 1) * t - mTension);
    }

使用画图工具得出下面图:
初始偏移插值器
因为x 从0开始,刚开始时,向y轴的反方向移动,然后在继续向正方向移动,

1.4.3 BounceInterpolator

弹跳插值器,模拟了控件自由落地后回弹的效果

java
复制代码
@HasNativeInterpolator
public class BounceInterpolator extends BaseInterpolator implements NativeInterpolator {
    public BounceInterpolator() {
    }

    @SuppressWarnings({"UnusedDeclaration"})
    public BounceInterpolator(Context context, AttributeSet attrs) {
    }

    private static float bounce(float t) {
        return t * t * 8.0f;
    }

    public float getInterpolation(float t) {
        // _b(t) = t * t * 8
        // bs(t) = _b(t) for t < 0.3535
        // bs(t) = _b(t - 0.54719) + 0.7 for t < 0.7408
        // bs(t) = _b(t - 0.8526) + 0.9 for t < 0.9644
        // bs(t) = _b(t - 1.0435) + 0.95 for t <= 1.0
        // b(t) = bs(t * 1.1226)
        t *= 1.1226f;
        if (t < 0.3535f) return bounce(t);
        else if (t < 0.7408f) return bounce(t - 0.54719f) + 0.7f;
        else if (t < 0.9644f) return bounce(t - 0.8526f) + 0.9f;
        else return bounce(t - 1.0435f) + 0.95f;
    }

效果图如下

弹性插值器![图片描述]
下落时,首先超过了动画的极限1,然后进行第一次回弹,先上移动,速度由快到慢到快,进行第二次回弹,先快到慢,减速

1.4.4 OvershootInterpolator

结束偏移插值器,表示在动画结束时,沿着动画方向继续运动一段距离后结束动画,查看源码

java
复制代码
public class OvershootInterpolator extends BaseInterpolator implements NativeInterpolator {
    private final float mTension;

    public OvershootInterpolator() {
        mTension = 2.0f;
    }
    //..省略代码
        mTension = a.getFloat(R.styleable.OvershootInterpolator_tension, 2.0f);
        setChangingConfiguration(a.getChangingConfigurations());
        a.recycle();
    }

    public float getInterpolation(float t) {
        // _o(t) = t * t * ((tension + 1) * t + tension)
        // o(t) = _o(t - 1) + 1
        t -= 1.0f;
        return t * t * ((mTension + 1) * t + mTension) + 1.0f;
    }

}

画出对应的数学图像
结束偏移插值器
在动画快要结束的时候,随着时间的推移,动画进度会超过结束的位置,然后再逐渐返回。

1.4.5 AnticipateOvershootInterpolator

AnticipateOvershootInterpolator 是AniticipateInterpolator 和OvershootInterpolator的结合体,即在动画开始时,先向前偏移一段,动画结束时向后偏移一段。跟踪代码

java
复制代码
@HasNativeInterpolator
public class AnticipateOvershootInterpolator extends BaseInterpolator
        implements NativeInterpolator {
    private final float mTension;

    public AnticipateOvershootInterpolator() {
        mTension = 2.0f * 1.5f;
    }

//省略其他代码
    private static float a(float t, float s) {
        return t * t * ((s + 1) * t - s);
    }

    private static float o(float t, float s) {
        return t * t * ((s + 1) * t + s);
    }

    public float getInterpolation(float t) {
        // a(t, s) = t * t * ((s + 1) * t - s)
        // o(t, s) = t * t * ((s + 1) * t + s)
        // f(t) = 0.5 * a(t * 2, tension * extraTension), when t < 0.5
        // f(t) = 0.5 * (o(t * 2 - 2, tension * extraTension) + 2), when t <= 1.0
        if (t < 0.5f) return 0.5f * a(t * 2.0f, mTension);
        else return 0.5f * (o(t * 2.0f - 2.0f, mTension) + 2.0f);
    }
}

图片描述

1.4.6 LinearInterpolator

这个是我们最常见的插值器,表示匀速

java
复制代码
@HasNativeInterpolator
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolator {
//...省略其他代码
    public float getInterpolation(float input) {
        return input;
    }
}

匀速插值器

1.4.7 AccelerateInterpolator

加速插值器,表示动画开始速率改变的比较慢,然后开始加速
计算方式为:

java
复制代码

public class AccelerateInterpolator extends BaseInterpolator implements NativeInterpolator {
    private final float mFactor;
    private final double mDoubleFactor;

    public AccelerateInterpolator() {
        mFactor = 1.0f;
        mDoubleFactor = 2.0;
    }
   //省略其他代码
    public float getInterpolation(float input) {
        if (mFactor == 1.0f) {
            return input * input;
        } else {
            return (float)Math.pow(input, mDoubleFactor);
        }
    }

}

图片描述

1.4.8 DecelerateInterpolator

减速插值器,在动画开始时一瞬间加速到最大值,然后逐渐变慢

java
复制代码
public class DecelerateInterpolator extends BaseInterpolator implements NativeInterpolator {
    public DecelerateInterpolator() {
    }
//省略其他代码
    public float getInterpolation(float input) {
        float result;
        if (mFactor == 1.0f) {
            result = (float)(1.0f - (1.0f - input) * (1.0f - input));
        } else {
            result = (float)(1.0f - Math.pow((1.0f - input), 2 * mFactor));
        }
        return result;
    }

减速插值器## 1.6ValueObject进阶OfObject()

1.4.9 循环插值器

CycleInterpolator是循环插值器,表示动画循环播放特定的次数,速率沿着正弦曲线变化,

java
复制代码
public class CycleInterpolator extends BaseInterpolator implements NativeInterpolator {
 public float getInterpolation(float input) {
        return (float)(Math.sin(2 * mCycles * Math.PI * input));
    }

    private float mCycles;

}

画出循环2次的函数图像
循环插值器
红色方框代表一次循环

2 ObjectAnimator

2.1 基本使用

上面我们通过ValueAnimator对动画的值进行变化,如果想要改变某个控件的属性,还要监听valueAnimator的动画过程,使用起来比较复杂。ObjectAnimator就是为了解决这个问题,相对于ValueAnimator,ObjectAnimator简化使用的API。ObjectAnimator是ValueAnimator的子类,所有ValueAnimator的方法都可以使用,同时对ofInt()、ofFloat()方法进行了重写,使用方法为:

kotlin
复制代码
val animator = ObjectAnimator(tv,"float",1f,0f,1.0f)
animator.duration = 1000
animator.start()

上面代码实现了TextView的透明度从1变到0再变到1,
从上面的代码中可以看到,构造ObjectAnimator的方法非常简单

plaintext
复制代码
public static ObjectAnimator ofFloat(object target,string propertyName,float... values)
  • 第一个参数用于指定动画要操作的控件
  • 第二个参数指定动画要操作的属性
  • 第三个参数,可变长参数,上面的代码透明度从1到0再到1

2.2 ObjectAnimaor原理

ObjectAnimator通过构造方法ObjectAnimator()的第2个参数,propertyName,来调用相应的Set()方法,他们定义在View中

java
复制代码
//设置透明度
public void setAlpha(float alpha)
//设置绕Z旋转
public void setRotation(float rotation)
//设置绕x轴旋转
public void setRotationX(float rotationX)
//设置绕y轴旋转
public void setRotationY(float rotationY)

//设置移动
public void setTranslateX(float translateX)
public void setTranslateY(float translateY)

//设置缩放
public void setScaleX(float scaleX)
public void setScaleY(float scaleY)

2.3 自定义控件中如何使用ObjectAnimator

首先我们要知道使用ObjectAnimator的前提是,view中有属性名字对应的set(),然后就是set()方法的命名方法是小驼峰,即set后对应的每个单词首字母大写,其余小写

3 AnimatorSet

AnimatorSet可以将ValueAnimator和ObjectAnimator组合到一起,实现组合动画,

3.1 playSequentially()

函数说明如下

java
复制代码
public void playSequentially(Animator... items)
public void playSequentially(List<Animator> items)

playSequentially()的意思是先执行完动画一,再执行动画二,动画三

3.2 playTogether

函数说明如下

java
复制代码
public void playTogether(Animator... items)
public void playTogether(List<Animator> items)

AnimatorSet中没有设置循环次数的函数,是否无限循环主要看动画本身,和playTogether()、playSequntially()无关

3.3 AnimatorSet.Builder

虽然使用playTogether()和playSquentially()分别能实现动画依次播放和同时开始动画,但是不能非常自由第组合动画,比如我们有3个动画A、B、C,想先播放C,利用上面两个函数没办法实现。

kotlin
复制代码
val animatorSet = AnimatorSet()
val buidler = animator.play(tv1)
builder.with(tv1TranslateY) 

AnimatorSet.play()这个方法生成Builder()的唯一方法,

java
复制代码
//表示要播放某个动画
public Builder play(Animator anim)
//和前面动画一起执行
public Builder with(Animator anim)
//先执行此动画,在执行前面的动画
public Builder before(Animator anim)
//在前面的动画执行完之后,再执行此动画
public Builder after(Animator anim)
//延迟n 毫秒之后执行动画
public Builder after(long delay)

添加监听器,AnimatorSet和ValueAnimator都是同一个父类Animator,所以可以添加下面的监听器

kotlin
复制代码
public static interface AnimatorListener{
 void onAnimatorStart(Animator animation); //动画开始
 void onAnimatorEnd(Animator animation);  //动画结束
 void onAnimatorCancel(Animator animation); //动画取消
 void onAnimatorRepeat(Animator animation); //动画重复
}

4.Evaluator(估值器、计算器)

我们知道创建ValueAnimator实例方法是调用valueAnimator的ofInt(),ofFloat()或者ofObject()静态方法
ofInt()和ofFloat()的计算前面我们都学会了,那么ofObject()呢?初始对象和结束对象,如何过渡?或者说这玩意怎么用?
Android原生提供了以下几种Evaluator

  • IntEvaluator:用于计算int类型属性值的估值器
  • FloatEvaluator:用于计算Float类型属性值的估值器
  • ArgbEvaluator:用于计算十六进制形式表示颜色值的估值器
  • TypeEvaluator:估值器的接口,我们可以实现这个接口来完成自定义估值器
    下面我们先来看IntEvaluator内部是怎么是怎么实现的

4.1 IntEvaluator内部实现

java
复制代码
public class IntEvaluator implements TypeEvaluator<Integer>{
	public Integer evaluate(float frcation,Integer startValue,Integer endValue){
	int startInt = startValue;
	return (int)(start + fraction*(endValue-startInt);
    }
}
  • fraction 参数,插值器(Interpolator)中的返回值,以百分制的小数形式表示
  • startValue 和 endValue 分别对应ofInt(int start,int end)函数中的start 和 end的数值,比如我们的动画ofInt(100,300)进行到数值进度为20%,那么fraction = 0.2 startvalue = 100,endValue = 300
  • 返回值就是当前数值监听器所得到的具体数值,可以通过addUpdateListener监听器进行监听器,然后调用animation.getAnimatedValue()函数得到解决。
    计算公式:
java
复制代码
返回值 = startValue + (endValue - startValue)* fraction 
动画的值 = 初始值 + 完成度 * (结束值 - 初始值)

如果我们想告诉系统如何从初始对象过度到结束对象,那么我们就要自己来实现 TypeEvaluator 接口,即自定义 Evaluator
整个流程为:

  • ofInt(0,400):定义动画数值区间
  • 插值器 : 返回当前动画的进度,比如说 30%
  • Evaluator :根据插值器返回的动画进度,计算出动画的值,公式为
    动画的值 = 初始值 + 完成度 * (结束值 - 初始值)
  • 然后开发者在监听器addUpdateListener中监听,获取到动画的值

4.2 插值器和Evaluator的区别

插值器只能改变动画速度的快慢,而Evaluator可以通过接收插值器中的动画进度,增加一些自己对动画的处理,改变在addUpdateListener中的值,通过Evaluator 与ofObject的配合,使得valueAnimator更加强大,在Evaluator的处理中,可以给监听器返回一个自定义的对象。

5.PropertyvlauesHolder 和Keyframe

ValueAnimator 和 ObjectAnimator 除了提供ofInt(),ofFloat(),ofObject()方法外,还提供了一个ofPropertyValuesHolder()

java
复制代码
public static ValueAnimator ofPropertyValuesHolder(PropertyVlauesHolder... vlaues)

public static ObjbectAnimator ofPropertyValuesHolder(PropertyValuesHolder... values)

ValueAnimator和ObjectAnimator的ofPropertyValuesHolder差不多,这里以ObjectAnimator为例子

5.1概述

PropertyValuesHolder类的含义就是,它其中保存了动画过程所需要操作的属性和对应的值,我们同构ofFloat(Object target,String propertyName,float... values)构建的动画,ofFloat()内部实现其实就是将传入的参数封装成PropertyValuesHolder实例来保存动画状态,后面的各种操作也是以PropertyValusHolder为主。下面是源码

java
复制代码
public final class ObjectAnimator extends ValueAnimator {
	 public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setFloatValues(values);
        return anim;
    }
}

点击setFloatValue()

java
复制代码
//ObjectAnimator.java中
    @Override
    public void setFloatValues(float... values) {
        if (mValues == null || mValues.length == 0) {
            // No values yet - this animator is being constructed piecemeal. Init the values with
            // whatever the current propertyName is
            if (mProperty != null) {
                setValues(PropertyValuesHolder.ofFloat(mProperty, values));
            } else {
                setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
            }
        } else {
            super.setFloatValues(values);
        }
    }
java
复制代码
//ValueAnimator.java中
  public void setValues(PropertyValuesHolder... values) {
        int numValues = values.length;
        mValues = values;
        mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
        for (int i = 0; i < numValues; ++i) {
//封装成了PropertyValuesHolder实例
            PropertyValuesHolder valuesHolder = values[i];
            mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
        }
        // New property/values/target should cause re-initialization prior to starting
        mInitialized = false;
    }

跟踪到这里我们验证了刚刚结论,动画的状态和参数被封装成了封装成了PropertyValuesHolder实例,并存在以属性名为键的HashMap中

5.2 PropertyValusHolder的常用函数

java
复制代码
public static PropertyValuesHolder ofFloat(String propertyName,float... values)
public static PropertyVlauesHolder ofInt(String propertyName,int... values)
public static PropertyValuesHolder ofObject(String propertyName,TypeEvaluator evaluator,object... values)
public static PropertyValuesHolder ofKeyframe(String propertyName,keyframe.. values)
  • propertyName 表示ObjectAnimator需要操作的属性名,ObjectAnimator通过反射操作对应的setProperty()
  • values 属性所对应的参数,同样是可变长参数,可以指定多个,如果只指定了一个,那么ObjectAnimator会通过查找对应属性getProperty()来获取初始值,

5.3 设置PropertyValuesHolder实例

ObjectAnimator给我们提供了一个设置PropertyValuesHolder实例的入口

java
复制代码
public static ObjecetAnimator ofPropertyValuesHolder(Object target ,PropertyValuesHolder... values)
  • target 需要执行动画的控件
  • values 可变长参数,可以传入多个PropertyValuesHolder实例,由于每个PropertyValuesHolder实例都会针对一个属性执行动画,所以PropertyValuesHolder实例,则会对控件的多个属性同时执行动画操作。

示例如下:

5.4 Keyframe

前面提到,要想动画速率的变化,可以通过自定义插值器,也可以通过自定义Evaluator来实现,但是如果真的让我们从速率变化效果而自定义插值器或者Evaluator大部分要涉及数学知识,恐怕也不容易。
谷歌为根据电影的原理,为我们提供了KeyFrame这一套API,只需要知道动画某个进度点的关键帧,然后API会根据各个帧之间的先后顺序进行平滑过渡。

5.4.1 生成关键帧

java
复制代码
public static Keyframe ofFloat(float fraction,float value)
  • fraction 当前动画的进度,即插值器中getInterpolation()的返回值
  • value:当前动画当前所在的数值的位置

比如Keyframe.ofFloat(0,0)表示动画进度为0时,动画所在数值的位置为0,Keyframe.ofFloat(0.25f,-20f)表示动画进度为25时,动画所在的数值为-20,

5.4.2 关键帧的使用

kotlin
复制代码
val frame0 = Keyframe.ofFloat(0f,0)
val frame1 = Keyframe.ofFloat(0.1f,-20f)
val frame2 = keyframe.ofFloat(1f,0)
val frameHolder = PropertyValuesHolder.ofKeyframe("rotation",frame0,frame1,frame2)
val animator = ObjectAnimator.ofPropertyValuesHolder(mImage,frameHolder)
animator.duration = 1000
animator.start()

Keyframe也有一些常用的其他函数用来设置fraction、value、和Interpolator

java
复制代码
//设置KeyFrame所对应的进度
public void setFraction(float fraction)
//设置KeyFrame所对应的值
public void setValue(Object value)

//设置插值器
//表示从上一帧到这一帧使用这个插值器
public void setInterpolator(TimeInterpolator interpolator)

实际中使用可以这样

kotlin
复制代码
val keyframe3 = Keyframe.ofFloat(1)
keyframe2.value = 0f
//对于keyframe而言,fraction和value这两个参数必须有的,所以不论使用哪种方法实例化Keyframe,都必须保证这两个值被初始化。
//没有使用插值器,系统默认调用线性插值器

5.5 PropertyValuesHolder的其他函数

PropertyValuesHolder除了拥有上面讲到的ofInt(),ofFloat(),ofObject(),ofKeyframe()还有其他函数

java
复制代码
//设置动画的Evaluator
public void setEvaluator(TypeEvaluator evaluator)
//设置ofFloat()所对应的动画值列表
public void setFloatValues(float... values)

//用于设置ofInt()所对应的动画值列表
public void setIntValues(int... values)

//用于设置ofkeyframe()所对应的动画值列表
public void setKeyframes(Keyframe... values)

//用于设置ofObject()所对应的动画值列表
public void setObjectValues(Object... values)

//设置动画属性名
public void setPropertyName(String propertyName)

6 为ViewGroup内组件添加动画

前面讲述的ViewAnimator、ObjectAnimator、AnimatorSet都只能针对一个控件做动画,如果我们想对ViewGroup内部控件做统一的入场动画、处场动画,如Recyclerview里面的item的添加、删除、数据更新时的动画,上面3个动都无法做到。
为了解决这个问题,android API 11 开始提供了一个简单的属性android:animateLayoutChanges = "true/false",所有的ViewGroup类控件都有这个属性,只要在xml添加了这个属性,就能实现在添加、删除viewGroup内部控件时的默认动画,但是这个动画不能自定义

6.1 LayoutTransition

上面我们体验了android给我们提供的viewGroup在增加、删除子控件时的默认动画,但是无法使用自定义动画。为了使用自定义动画,我们可以使用LayoutTransition

  • 创建LayoutTransition示例

  • 创建动画并进行设置

  • 将LayoutTransition 设置到ViewGroup中

kotlin
复制代码
val transitioner = LayoutTransition()
val animOut = ObjectAnimator.ofFloat(null,"rotation",0f,90f,0f)
transitioner.setAnimator(LayoutTransition.DISAPPEARING,animOut)
linearLayout.layoutTransition = transitioner

在第二步中,transitioner.setAnimator设置动画的函数声明如下

kotlin
复制代码
public void setAnimator(int transitionType,Animator animator)

其中transitionType表示应用当前动画的对象范围,取值如下:

  • APPERAING:元素在容器中出现是所定义的动画
  • DISAPPEARING :元素在容器中消失时所定义的动画
  • CHANGE_APPEARING:由于容器中要显示一个新的元素,其他需要变化的元素所应用的动画
  • CHANGE_DISAPPEARING:由于容器中某个元素消失时,其他需要便是的元素所应用的动画

LayoutTransition.CHANGE_APPEARING 和LayoutTransition.CHANGE_DISAPPEARING必须要使用PropertyValuesHolder所构建的动画才会有效果,也就是说使用ObjectAnimator构建的动画,在这里不会有效果。

6.2其他函数

java
复制代码
//设置所有动画完成所需要的时间
public void setDuration(long duration)

//针对某个Type设置动画时长
public void setDuration(int transitionType,long duration)

//设置单个Type的插值器
public void setInterpolator(int transitonType,TimeInterpoloator interpolator)

//针对单个type设置动画延时
public void setStartDelay(int transitionType,long delay)
//针对单个Type设置每个子item动画的时间间隔
//参数一:transitionType用于指定Type类型的动画
//参数二:各个item间做动画的间隔
public void setStagger(int transitionType,long duration)

6.3 设置监听

LayoutTransition 提供了一个监听函数

java
复制代码
public void addTransitionListener(TransitionListener listener)

//其中TransitionListener
public interfaceTransitionListener{
   public void startTransition(LayoutTransition transition,ViewGroup viewGroup,View view,int transitionType)

public void endTransition(LayoutTransition transition,ViewGroup viewGroup,View view,int transitionType)
}

在任何类型的LayoutTransition开始和结束时,都会调用TransitionListener的startTransition() 和 endTransition(),在transitionListener有4个参数

  • LayoutTransition transiton 当前的LayoutTransition 实例
  • ViewGroup container 当前应用LayoutTransition的容器
  • View view 当前正在做动画的View对象
  • int transitionType: 当前的LayoutTransition类型取值有:
    APPEARING,DISAPPEARING,CHANGE_APPEARING和CHANGE_DISAPPEARING

下一篇预告:动画实战实例

本文由 波鲁萨利诺.黄猿 原创发布于 阳光沙滩 , 未经作者授权,禁止转载
评论
0 / 1024
用户头像
这年头,还这么用心写博客的少了。
2022-08-25 09:10回复
回复@拉大锯: 陆陆续续写了一周,太难啦
2022-08-25 09:29回复
用户头像
【注意看打印出来的值不一定连续,有可能重复】,重复问题,从数据上面看是有2个0,也就是第一帧和第二帧。这个地方诡异。至于为什么不是连续的,那是因为你给的时间,就是那么多,但是每一帧的刷新时间固定的,根据数据,你的设备是60帧,也就是一帧16.6ms,从你的数据上面也能看得出接近这个16ms刷新一次。
2022-08-22 08:24回复
回复@断点: 如果你想看到连续的,你需要符合这样条件,由于16ms刷一次,你可以理解16ms理论上打印一条记录,你需要连续,你就配合他。 比如你设置动画的ValueAnimator.ofInt(0,60),时间1000,这样情况就是1秒钟60帧,每一帧触发一次回调,就触发60次,这样打印就连续了
2022-08-22 08:29回复
回复@断点: 强呀,刚开始我以为是类型转换带来的误差,后面看又不香,茅塞顿开呀[doge]
2022-08-22 08:53回复
回复@波鲁萨利诺.黄猿: 茅厕大开
2022-08-22 08:56回复
推荐文章
一篇文章讲清楚雅思、托福、GRE、SAT、GMAT:它们是什么?有什么区别?应该考哪个?
面对IELTS、TOEFL、GRE、SAT和GMAT,你是否感到困惑?本文深度解析五大考试的核心区别:从语言证明到学术能力评估,明确各自适用人群与场景。特别更新2026年托福改革及最新评分体系,通过对比表格与真实申请案例,助你精准定位目标考试,避免无效备考,高效规划海外升学之路。
一篇文章讲清楚 Android 签名:PK8、X509.PEM、JKS 是什么?它们有什么关系?
深入解析Android应用与系统签名的核心文件:PK8、X509.PEM与JKS。本文从非对称加密原理出发,厘清私钥、证书与密钥库的本质区别及相互转换方法,涵盖AOSP平台签名与普通应用发布的实际场景。通过对比V1-V4签名方案及解答常见疑难,助你掌握保障APK完整性与身份认证的关键技术,规避签名丢失或冲突风险。
系统监控运维系统
你是否也深受证书过期、磁盘满载等运维痛点的折磨?排查耗时,影响线上体验。本文分享一款自研运维系统面板,集成证书统一监测、资源预警及WebSSH等功能,实现问题快速定位与处理。附代码共享链接,欢迎参考或二次开发,提升运维效率。
断点说搞个万年历,怎么搞?
想快速拥有一个专属的程序员日历?只需一篇清晰的设计文档,剩下的交给AI!本文带你从零搭建“sob-calendar”项目,集成农历、节气及独特的程序员运势功能。通过实际案例展示如何利用AI高效生成代码并部署上线,让繁琐的开发工作变得轻松有趣,快来体验科技与传统历法的奇妙碰撞吧!
linux服务器的日志、容器清理
本手册提供了一套全面的Linux服务器磁盘清理与排查方法,帮助用户高效管理磁盘空间。通过查看磁盘使用情况、分析Docker占用、清理无用容器和日志,确保系统运行流畅。适合需要优化服务器性能的运维人员参考。
记给网站接入图形验证码
本文分享了如何为个人站点添加图形验证码以提升安全性,详细介绍了使用第三方接口实现验证码功能的过程,并通过代码示例展示了具体实现方式。对于关注网站安全的开发者来说,这篇文章提供了实用的参考价值。
Java开发环境搭建
本文是一份详细的Java开发环境搭建指南,涵盖了Java的发展历史、版本选择及安装配置过程。无论你是初学者还是有经验的开发者,都能从中找到实用的信息和步骤,帮助你快速上手Java开发。
Vue3.js项目的创建
本文详细介绍了如何使用Vue3创建项目,包括Node.js的安装和配置,以及项目的初始化过程。适合初学者学习如何快速搭建Vue3开发环境,并推荐了相关课程供进一步学习。
Network Tools : 您的运维“助手”升级了
Network Tools是一款专为解决运维难题而设计的软件,集设备管理、配置备份、性能监控和告警处理于一体。通过统一的工作台,帮助运维人员更高效地管理资产、定位故障并提升团队协作效率。无论是设备在线状态还是具体问题排查,都能得到全面支持。
Ubuntu 软件安装与彻底卸载指南
本文详细介绍了在 Ubuntu 系统中如何规范安装和彻底卸载 .deb 格式软件,包括依赖修复、残留清理以及图形化工具推荐,适合需要维护系统环境的用户。
JavaWeb开发客户程序更新特殊环境下的骚操作
本文分享了在无外网环境下,如何利用Docker和docker-compose实现高效部署的实战经验。通过基础镜像和本地jar包挂载的方式,解决了内网环境下的依赖问题,适合需要灵活部署方案的技术人员参考。
Linux 防火墙完全指南:从原理到多发行版实战
本文全面解析Linux防火墙的底层原理与主流工具配置,涵盖UFW、firewalld、iptables、nftables等方案,适合不同发行版用户。从基础概念到生产环境加固建议,助你掌握核心安全策略,确保服务器与网络的安全运行。
水上派对英语单词篇
探索FLOATOPIA水上派对的完整指南,涵盖签到流程、场地指引、安全须知、活动安排及夜场舞会等实用英语词汇与对话。无论是初学者还是老手,都能轻松掌握关键术语,享受完美水上体验。
麒麟操作系统下使用 virt-customize 重置虚拟机 root 密码实战
本文详细介绍了在麒麟操作系统环境下使用 virt-customize 工具重置 KVM 虚拟机 root 密码的完整流程,包含安装步骤、常见问题及解决方案,帮助运维人员高效处理密码遗忘或批量初始化场景,避免数据丢失。
浅析JS预编译-函数篇
本文详细讲解了JavaScript中函数的预编译过程,包括AO对象的创建、变量和函数声明的提升,以及执行阶段的变化。适合初学者了解JS执行机制,帮助深入理解代码运行逻辑。
访问Github的另一种姿势
本文介绍了如何通过Watt ToolKit等工具加速访问Github,适合对网络技术感兴趣的读者。内容涵盖工具介绍、下载和使用方法,以及一些实用技巧,值得一看。
使用Gulp压缩JS
本文详细介绍了如何使用Gulp压缩JavaScript文件,特别是支持ES6语法的处理方法。通过实际操作和效果对比,帮助开发者提升代码性能,适合对前端自动化工具感兴趣的读者。
使用Gulp压缩CSS
本文详细介绍了如何使用Gulp工具对CSS文件进行压缩,提升网页加载速度。适合需要优化静态资源的开发者,提供清晰的步骤和示例,帮助快速上手。
从0部署Nuxtjs项目
本文详细介绍了如何将Nuxt.js项目部署到公网,涵盖Node.js安装、环境配置、源码上传及使用PM2启动服务的全过程,适合开发者学习和实践。
Linux 命令行美化利器:tree 命令完全指南(安装 + 全部参数详解)
掌握 Linux 下的 `tree` 命令,快速直观地查看目录结构。本文详细讲解安装方法、参数含义及实战场景,适合开发者和系统管理员提升工作效率。
Linux常用命令(一)
本文详细介绍了Linux基础命令,包括文件操作、系统显示、网络状态、软件包管理等,适合初学者学习和查阅,是掌握Linux系统的实用指南。
大模型推理参数解码:温度、Top-p 与核采样如何塑造生成文本的灵魂
探索大语言模型的采样参数如何塑造生成内容,从温度到Top-p,从惩罚机制到熵控制,揭示背后的信息论原理与实践策略。了解如何通过参数调优,让AI既保持知识的严谨,又拥有创造力的自由。
记从0使用Claude code写代码
本文介绍了如何使用Claude Code搭配DeepSeek进行编程,详细讲解了安装Node.js、全局安装Claude、配置模型以及使用方法。对于开发者来说,这是一份实用的技术教程,尤其适合对AI编程工具感兴趣的读者。
Android-Studio-Gradle同步失败-Library为null的通用排查指南
遇到 Android Studio Gradle Sync 失败时,不要轻易删除全局缓存。本文详细解析了错误原因,并提供了一系列精准的排查步骤和解决方案,帮助开发者快速定位并解决问题,提升开发效率。
PHP实现密码加密
本文详细介绍了Bcrypt密码加密技术及其在PHP中的应用,帮助开发者提升用户密码的安全性。通过实际代码示例,展示了如何使用password_hash和password_verify函数进行密码加密与验证,是学习网络安全知识的实用指南。
WordCloud效果,滚动标签
本文详细介绍了如何在Vue项目中集成WordCloud组件,包括依赖安装、属性配置和使用方法。适合开发者学习如何实现数据可视化功能,提升项目交互体验。
从0使用WordPress搭建一个优美的网站
本文详细介绍了如何使用WordPress搭建一个美观的网站,从安装到主题配置和功能拓展,为读者提供了实用的操作指南。无论是初学者还是有一定经验的开发者,都能从中获得有价值的参考。
JS实现在网站底部添加运行时间
想知道如何在网站底部显示运行时间?本文详细讲解了通过JavaScript实现这一功能的方法,包括时间计算逻辑和代码实现。适合前端开发者学习参考,轻松为网站添加实用功能。
在Vercel上部署Hexo博客
本文详细介绍了如何在Vercel上快速部署Hexo博客,无需后端服务即可实现高效发布。相比传统方式,省去了手动生成和上传静态文件的步骤,更加便捷。适合想要搭建个人博客的开发者参考。
从0搭建一个Hexo博客
本文详细介绍了Hexo博客框架的使用方法,从安装到部署全流程讲解,适合想快速搭建个人博客的技术爱好者。内容清晰易懂,是入门Hexo的理想指南。