ObjectAnimator
float y = textView.getTranslationY();
ObjectAnimator animator = ObjectAnimator.ofFloat(textView,"translationY",y,y+100);
animator.setDuration(2000);
animator.start();
效果:将tv像下平移100,单位我也不知道。
代码分析: 首先获取tv的y方向的值,使用ObjectAnimator的ofFloat方法创建ObjectAnimator对象,传入1234四个参数,
- 1:动画实现的目标对象(demo这里是我们的tv),
- 2:目标对象的属性,ObjectAnimator的原理是改变属性去实现动画效果的。
- 34:这两个参数是绑定在一起的,是动画的参数,他们两个相当于第三个参数(用来描述动画的效果),而这第三个参数可以有多个值,demo中只有两个,如果是y,y+100,y,则效果是向下平移100,然后在平移回复到原来的y位置。
-
下面两行就不用讲了。设置时长和开始动画。
动画集合
// 创建三个属性动画,y方向平移,y方向缩放,x方向缩放
ObjectAnimator translationY = ObjectAnimator.ofFloat(textView, "translationY", y, y+500f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(textView, "scaleY", 1f, 5f, 1f);
ObjectAnimator scaleX = ObjectAnimator.ofFloat(textView, "scaleX", 1f, 5f, 1f);
// 创建一个属性动画集合,
AnimatorSet animSet = new AnimatorSet();
animSet.play(scaleY).with(scaleX).after(translationY);
animSet.setDuration(2000);
animSet.start();
效果:tv向下平移500,xy同时放大5,在缩小到原来大小
首先新建三个动画,再建了一个动画集合,重点分析play这句,由tony play basketball with jack after school这句我们知道scaleY和scaleX是在translationY完成之后再一起同时进行的。
动画集合中的其他方法:
- playTogether() :将传入的动画一起同时播放
- with():同时
- before():在之前
- after():在之后
写一个demo帮助大家深入理解一下
demo描述:动画效果是六个iv选项发散出去围成一圈,有个回弹的效果。
// 重复该段
ObjectAnimator animator_llRepeatThisChapter = ObjectAnimator.ofFloat(ll_repeat_this_chapter,
"translationX",
ll_repeat_this_chapter.getTranslationX()+400,
ll_repeat_this_chapter.getTranslationX()-50,
ll_repeat_this_chapter.getTranslationX());
// 重新讲解
ObjectAnimator animator_llReExplain_1 = ObjectAnimator.ofFloat(ll_re_explain,
"translationX",
ll_re_explain.getTranslationX()+400,
ll_re_explain.getTranslationX()-40,
ll_re_explain.getTranslationX());
ObjectAnimator animator_llReExplain_2 = ObjectAnimator.ofFloat(ll_re_explain,
"translationY",
ll_re_explain.getTranslationY()+300,
ll_re_explain.getTranslationY()-40,
ll_re_explain.getTranslationY());
// 重新开始
ObjectAnimator animator_ll_re_start_1 = ObjectAnimator.ofFloat(ll_re_start,
"translationX",
ll_re_start.getTranslationX()+100,
ll_re_start.getTranslationX()-30,
ll_re_start.getTranslationX());
ObjectAnimator animator_ll_re_start_2 = ObjectAnimator.ofFloat(ll_re_start,
"translationY",
ll_re_start.getTranslationY()+400,
ll_re_start.getTranslationY()-30,
ll_re_start.getTranslationY());
// 问答
ObjectAnimator animator_ll_question_answer_1 = ObjectAnimator.ofFloat(ll_question_answer,
"translationX",
ll_question_answer.getTranslationX()-100,
ll_question_answer.getTranslationX()+30,
ll_question_answer.getTranslationX());
ObjectAnimator animator_ll_question_answer_2 = ObjectAnimator.ofFloat(ll_question_answer,
"translationY",
ll_question_answer.getTranslationY()+400,
ll_question_answer.getTranslationY()-30,
ll_question_answer.getTranslationY());
// 跳到下一段
ObjectAnimator animator_ll_jump_next_chapter_1 = ObjectAnimator.ofFloat(ll_jump_next_chapter,
"translationX",
ll_jump_next_chapter.getTranslationX()-350,
ll_jump_next_chapter.getTranslationX()+30,
ll_jump_next_chapter.getTranslationX());
ObjectAnimator animator_ll_jump_next_chapter_2 = ObjectAnimator.ofFloat(ll_jump_next_chapter,
"translationY",
ll_jump_next_chapter.getTranslationY()+300,
ll_jump_next_chapter.getTranslationY()-30,
ll_jump_next_chapter.getTranslationY());
// 跳到下一个讲解点
ObjectAnimator animator_ll_jump_next_place_1 = ObjectAnimator.ofFloat(ll_jump_next_place,
"translationX",
ll_jump_next_place.getTranslationX()-400,
ll_jump_next_place.getTranslationX()+40,
ll_jump_next_place.getTranslationX());
// 背景动画
ObjectAnimator animator_rl_menu_1 = ObjectAnimator.ofFloat(rl_menu,
"ScaleX",0,1);
ObjectAnimator animator_rl_menu_2 = ObjectAnimator.ofFloat(rl_menu,
"ScaleY",0,1);
rl_menu.setPivotX(rl_menu.getWidth()/2);
rl_menu.setPivotY(rl_menu.getHeight());
animator_rl_menu_1.setDuration(300);
animator_rl_menu_1.start();
animator_rl_menu_2.setDuration(300);
animator_rl_menu_2.start();
// 动画集合
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(animator_llRepeatThisChapter,
animator_llReExplain_1,animator_llReExplain_2,
animator_ll_re_start_1,animator_ll_re_start_2,
animator_ll_question_answer_1,animator_ll_question_answer_2,
animator_ll_jump_next_chapter_1,animator_ll_jump_next_chapter_2,
animator_ll_jump_next_place_1
);
animatorSet.setDuration(600);
animatorSet.start();
重点说一个东西:
rl_menu.setPivotX(rl_menu.getWidth()/2);
rl_menu.setPivotY(rl_menu.getHeight());
这是用来设置缩放的中心位置的,别的都应该可以看懂。
这里需要注意一下view的getWidth方法
private void openQA() {
llQA.setVisibility(View.VISIBLE);
// 打开QA动画
ObjectAnimator animator_QA_up = ObjectAnimator.ofFloat(llQA,"ScaleY",0,1);
llQA.setPivotY(llQA.getHeight());
animator_QA_up.setDuration(400).start();
}
我在xml中将llQA设置为GONE,openQA方法的作用是从屏幕下方下打开这个ll,可是第一次运行程序发现ll是从默认位置0打开的,debug发现 llQA.setPivotY(llQA.getHeight());这行是运行了的,为什么第一次无效呢?原因是在设置ll为Visible后,ll并没有在屏幕上展示出来,而是经过animator慢慢的出来,view都没有绘制过,你的measure怎么会measure到呢,所以llQA.setPivotY(llQA.getHeight());里第一次传入的是0,以后的第二次第三次就正常了,那么我们怎么解决呢。
private void openQA() {
llQA.setVisibility(View.VISIBLE);
// 打开QA动画
ObjectAnimator animator_QA_up = ObjectAnimator.ofFloat(llQA,"ScaleY",0,1);
llQA.post(() -> {
llQA.setPivotY(llQA.getHeight());
});
animator_QA_up.setDuration(400).start();
}
可以通过View.post(runnable)方法获取View的宽高,通过post可以将一个runnable投递到消息队列的尾部,然后等待Looper调用此runnable的时候,View也已经初始化好了,就是将view放到最后去测量,等view绘制完毕再去测量。
下面介绍两个非常重要的概念,插值器(Interpolator)和估值器(TypeEvaluator)。
- 插值器:决定值的变化规律。(加速减速匀速等)
- 估值器:决定值的具体变化。
插值器决定值的变化规律,即决定的是变化趋势;而接下来的具体变化数值则交给估值器
插值器:
安卓为我们提供了九种默认的插值器
// 动画加速进行
AccelerateInterpolator accelerateInterpolator;
// 快速完成动画,超出再回到结束样式
OvershootInterpolator overshootInterpolator;
// 先加速再减速
AccelerateDecelerateInterpolator accelerateDecelerateInterpolator;
// 先退后再加速前进
AnticipateInterpolator anticipateInterpolator;
// 先退后再加速前进,超出终点后再回终点
AnticipateOvershootInterpolator anticipateOvershootInterpolator;
// 最后阶段弹球效果
BounceInterpolator bounceInterpolator;
// 周期运动
CycleInterpolator cycleInterpolator;
// 减速
DecelerateInterpolator decelerateInterpolator;
// 匀速
LinearInterpolator linearInterpolator;
原作者出处:https://www.jianshu.com/p/2f19fe1e3ca1
- 添加插值器:
animation.setInterpolator(new BounceInterpolator());
系统默认的插值器是AccelerateDecelerateInterpolator,即先加速后减速
估值器:
协助插值器 实现非线性运动的动画效果
系统提供的估值器:
// 以整型的形式从初始值 - 结束值 进行过渡
IntEvaluator intEvaluator;
// 以浮点型的形式从初始值 - 结束值 进行过渡
FloatEvaluator floatEvaluator;
// 以Argb类型的形式从初始值 - 结束值 进行过渡
ArgbEvaluator argbEvaluator;
估值器一般无需选择,系统会帮我们配置好,ofInt() & ofFloat()都具备系统内置的估值器,即FloatEvaluator & IntEvaluator。
ValueAnimator
不多bb直接上用法
ValueAnimator animator = ValueAnimator.ofInt(iv_play.getLayoutParams().width,600);
animator.setDuration(1000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int currentValue = (int) valueAnimator.getAnimatedValue();
iv_play.getLayoutParams().width = currentValue;
iv_play.requestLayout();
}
});
animator.start();
ofInt()传入动画的起始值,未指定插值器和估值器,都使用默认的AccelerateDecelerateInterpolator和IntEvaluator。addUpdateListener这个方法用于改变对象的属性值从而达到动—画的效果,比如demo中我要做一个iv变宽的效果,我就在addUpdateListener中不停的改变iv的width。
重点:ValueAnimator.ofObject()
这是一个可以操作对象实现动画效果的类
由于对象复杂多样,所以系统自带的估值器无法使用,需要我们自定义估值器。
ofObject操作对象动画的使用步骤:
- 新建一个动画作用目标的类,
public class Point {
// 设置两个变量用于记录坐标的位置
private float x;
private float y;
// 构造方法用于设置坐标
public Point(float x, float y) {
this.x = x;
this.y = y;
}
// get方法用于获取坐标
public float getX() {
return x;
}
public float getY() {
return y;
}
}
- 根据需求自定义估值器:
这里的demo的需求是将point移动
public class PointEvaluator implements TypeEvaluator {
// 复写evaluate()
// 在evaluate()里写入对象动画过渡的逻辑
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
// 将动画初始值startValue 和 动画结束值endValue 强制类型转换成Point对象
Point startPoint = (Point) startValue;
Point endPoint = (Point) endValue;
// 根据fraction来计算当前动画的x和y的值
float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());
float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());
// 将计算后的坐标封装到一个新的Point对象中并返回
Point point = new Point(x, y);
return point;
}
}
fraction是在0-1之间过度,用于描述动画的完成度,startValue是开始的Object(这里是点),endValue是结束的Object,最后返回当前fraction下动画的点。举个例子,
开始点是(1,1),结束点是(6,7),当fraction是0.25时,我们的点移动到了((6-1)0.25,(7-1)0.25)的坐标位置。
- 将属性动画作用到自定义View当中
/**
* Created by Carson_Ho on 17/4/18.
*/
public class MyView extends View {
// 设置需要用到的变量
public static final float RADIUS = 70f;// 圆的半径 = 70
private Point currentPoint;// 当前点坐标
private Paint mPaint;// 绘图画笔
// 构造方法(初始化画笔)
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
// 初始化画笔
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.BLUE);
}
// 复写onDraw()从而实现绘制逻辑
// 绘制逻辑:先在初始点画圆,通过监听当前坐标值(currentPoint)的变化,每次变化都调用onDraw()重新绘制圆,从而实现圆的平移动画效果
@Override
protected void onDraw(Canvas canvas) {
// 如果当前点坐标为空(即第一次)
if (currentPoint == null) {
currentPoint = new Point(RADIUS, RADIUS);
// 创建一个点对象(坐标是(70,70))
// 在该点画一个圆:圆心 = (70,70),半径 = 70
float x = currentPoint.getX();
float y = currentPoint.getY();
canvas.drawCircle(x, y, RADIUS, mPaint);
// (重点关注)将属性动画作用到View中
// 步骤1:创建初始动画时的对象点 & 结束动画时的对象点
Point startPoint = new Point(RADIUS, RADIUS);// 初始点为圆心(70,70)
Point endPoint = new Point(700, 1000);// 结束点为(700,1000)
// 步骤2:创建动画对象 & 设置初始值 和 结束值
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);
// 参数说明
// 参数1:TypeEvaluator 类型参数 - 使用自定义的PointEvaluator(实现了TypeEvaluator接口)
// 参数2:初始动画的对象点
// 参数3:结束动画的对象点
// 步骤3:设置动画参数
anim.setDuration(5000);
// 设置动画时长
// 步骤3:通过 值 的更新监听器,将改变的对象手动赋值给当前对象
// 此处是将 改变后的坐标值对象 赋给 当前的坐标值对象
// 设置 值的更新监听器
// 即每当坐标值(Point对象)更新一次,该方法就会被调用一次
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentPoint = (Point) animation.getAnimatedValue();
// 将每次变化后的坐标值(估值器PointEvaluator中evaluate()返回的Piont对象值)到当前坐标值对象(currentPoint)
// 从而更新当前坐标值(currentPoint)
// 步骤4:每次赋值后就重新绘制,从而实现动画效果
invalidate();
// 调用invalidate()后,就会刷新View,即才能看到重新绘制的界面,即onDraw()会被重新调用一次
// 所以坐标值每改变一次,就会调用onDraw()一次
}
});
anim.start();
// 启动动画
} else {
// 如果坐标值不为0,则画圆
// 所以坐标值每改变一次,就会调用onDraw()一次,就会画一次圆,从而实现动画效果
// 在该点画一个圆:圆心 = (30,30),半径 = 30
float x = currentPoint.getX();
float y = currentPoint.getY();
canvas.drawCircle(x, y, RADIUS, mPaint);
}
}
}