一、属性动画出现的原因
属性动画(Property Animation)是在Android3.0(API 11)后才提供的一种全新的动画模式。
原因:由于补间和逐帧动画存在的一些缺点:
①作用的对象局限:即这两种动画只能够作用在视图View
上,即只可以对一个Button
、TextView
、甚至是LinearLayout
、或者其它继承自View
的组件进行动画操作,但无法对非View
的对象进行动画操作。但是有时候,需要实现视图颜色动态变化,那么就需要操作视图颜色属性,从而实现动画效果。
②没有改变View的属性,只是改变视觉效果:举例
- 如,将屏幕左上角的按钮 通过补间动画 移动到屏幕的右下角
- 点击当前按钮位置(屏幕右下角)是没有效果的,因为实际上按钮还是停留在屏幕左上角,补间动画只是将这个按钮绘制到屏幕右下角,改变了视觉效果而已。
③动画效果单一,在功能和可扩展性上有局限性
为了解决补间动画的缺陷,Android在3.0以后给我们提供了一种全新的动画模式:属性动画。
二、属性动画简介
作用对象:任意的Java对象 不在局限于视图View对象
实现的动画效果:可自定义各种动画效果
特点:
- 作用对象进行了扩展:不只是View对象,甚至没对象也可以
- 动画效果:不只是4种基本变换,还有其他动画效果
- 作用领域:API11后引入的
工作原理:在一定时间间隔内,通过不断对值进行改变,并不断将该值赋给对象的属性,从而实现该对象在该属性上的动画效果。
工作原理逻辑图参照:https://www.jianshu.com/p/2412d00a0ce4
属性动画的两个核心类:ValueAnimator和ObjectAnimator
三、ValueAnimator的使用
通过不断控制 值 的变化,再不断 手动 赋给对象的属性,从而实现动画效果
ValueAnimator.ofInt(int values)
:将初始值 以整型数值的形式 过渡到结束值 (即整型估值器-Int Evaluator)
具体使用:Java代码设置、xml设置
推荐使用Java代码设置,因为很多时候属性的起始值是无法提前知晓的,这需要在Java代码里动态获取。
例子,将一个Button按钮的宽从布局文件中默认设置的100扩大到1000
package com.example.wcystart.otherproject;
public class AnimationActivity extends AppCompatActivity {
private ImageView mIvImageView;
private Button mBtnStartAnimation;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_animation);
mIvImageView = findViewById(R.id.iv_image);
mBtnStartAnimation = findViewById(R.id.start_animation);
ValueAnimator valueAnimator=ValueAnimator.ofInt(mBtnStartAnimation.getLayoutParams().width,300);
valueAnimator.setDuration(2000);
valueAnimator.setRepeatCount(5);
valueAnimator.setStartDelay(2000);
//**将属性数值 手动赋值给 对象的属性:此处是将值 赋值给 图片的宽度
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int currentValue = (int) animation.getAnimatedValue();
System.out.println(currentValue);
mBtnStartAnimation.getLayoutParams().width=currentValue;//每次变化后的值,赋给图片的宽度
//刷新视图,即重新绘制
mBtnStartAnimation.requestLayout();
}
});
valueAnimator.start(); //启动动画
}
}
打印出来的从初始值过度到结束值:
2.ValueAnimator.oFloat(float values)将初始值以浮点型数值的形式过度到结束值
这个演示在xml文件中配置:在res/animator
<?xml version="1.0" encoding="utf-8"?>
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="0"
android:valueTo="100"
android:duration="2000"
android:repeatCount="2"
android:valueType="intType">
</animator>
在Activity中引用
public class AnimationActivity extends AppCompatActivity {
private ImageView mIvImageView;
private Button mBtnStartAnimation;
private Button mBtnStopAnimation;
private AnimationDrawable mAnimationDrable;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_animation);
mIvImageView = findViewById(R.id.iv_image);
mBtnStartAnimation = findViewById(R.id.start_animation);
mBtnStopAnimation = findViewById(R.id.stop_animation);
//引用属性动画
Animator animator= AnimatorInflater.loadAnimator(this,R.animator.set_animation);
animator.setTarget(mBtnStartAnimation.getLayoutParams().width);
animator.start();
}
3. ValueAnimator.ofObject()将初始值以对象的形式过渡到结束值,即通过操作对象实现动画效果。
实现动画效果:一个圆从一个点移动到另外一个点
1.因为ValueAnimator.ofObject()是面向对象操作的,所以需要自定义对象类
public class Point {
//设置两个变量用于记录坐标的位置
private float x;
private float y;
public Point(float x, float y) {
this.x = x;
this.y = y;
}
public float getX() {
return x;
}
public void setX(float x) {
this.x = x;
}
public float getY() {
return y;
}
public void setY(float y) {
this.y = y;
}
}
2.自定义估值器(TypeEvaluator)类
/**
* Created by ${wcystart}
* date:on 2019/1/17
* description: 当初始值以对象的形式过渡到结束值时,估值器需自定义
* 自定义的目的是自定义如何从初始坐标过渡到结束点坐标
*/
public class ObjectEvaluator implements TypeEvaluator {
// 复写evaluate()
// 在evaluate()里写入对象动画过渡的逻辑
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
// fraction:表示动画完成度(根据它来计算当前动画的值)
// 1.将动画初始值startValue 和 动画结束值endValue 强制类型转换成Point对象
Point startPoint = (Point) startValue;
Point endPoint = (Point) endValue;
//2.根据fraction来计算当前动画的x,和 Y的值
float x=startPoint.getX()+fraction*(endPoint.getX()-startPoint.getX());
float y=startPoint.getY()+fraction*(endPoint.getY()-startPoint.getY());
//3.将计算后的坐标封装到一个新的Point对象中返回
Point point=new Point(x,y);
return point;
// 返回对象动画过渡的逻辑计算后的值
}
}
3.将属性动画应用到自定义View类中
/**
* Created by ${wcystart}
* date:on 2019/1/17
* description: 将属性动画应用到自定义的View中
*/
public class PotinView extends View {
private static final float REDIUS = 80f; //圆的半径
private Point currentPoint;//当前点的坐标
private Paint mPaint; //画笔
public PotinView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
//初始化画笔
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.RED);
}
@Override
protected void onDraw(Canvas canvas) {
//super.onDraw(canvas);
if (currentPoint == null) {
currentPoint = new Point(REDIUS, REDIUS);
//在该点(80,80)上画一个圆,圆心(80,80),坐标80
float x = currentPoint.getX();
float y = currentPoint.getY();
canvas.drawCircle(x, y, REDIUS, mPaint);
//将属性动画应用到View上
//1.创建初始值 和结束值
Point startPoint = new Point(REDIUS, REDIUS);
Point endPoint = new Point(800, 1300);
//2.创建动画对象 设置初始值和结束值
ValueAnimator animator = ValueAnimator.ofObject(new ObjectEvaluator(), startPoint, endPoint);
animator.setDuration(5000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 将每次变化后的坐标值(估值器PointEvaluator中evaluate()返回的Piont对象值)到当前坐标值对象(currentPoint)
// 从而更新当前坐标值(currentPoint)
currentPoint = (Point) animation.getAnimatedValue();
// 步骤4:每次赋值后就重新绘制,从而实现动画效果
// 调用invalidate()后,就会刷新View,即才能看到重新绘制的界面,即onDraw()会被重新调用一次
// 所以坐标值每改变一次,就会调用onDraw()一次
invalidate();
}
});
animator.start();
} else {
// 如果坐标值不为0,则画圆
// 所以坐标值每改变一次,就会调用onDraw()一次,就会画一次圆,从而实现动画效果
float x = currentPoint.getX();
float y = currentPoint.getY();
canvas.drawCircle(x, y, REDIUS, mPaint);
}
}
}
4.在布局文件中引用自定义View
<?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"
android:padding="10dp"
tools:context="com.example.wcystart.otherproject.PropertyActivity">
<com.example.wcystart.otherproject.PotinView
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.example.wcystart.otherproject.PotinView>
</android.support.constraint.ConstraintLayout>
5.在Activity中设置显示视图
public class PropertyActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_property);
}
}
运行效果图:由于这是个动态的效果图,我录制了一个小视频,但是不知道怎么上传视频。求助求助~~~
其实ValueAnimator.ofObject()
的本质还是操作 ** 值 **,只是是采用将 多个值 封装到一个对象里的方式 同时对多个值一起操作而已
就像上面的例子,本质还是操作坐标中的x,y两个值,只是将其封装到Point对象里,方便同时操作x,y两个值而已
四、ObjectAnimator类
直接对对象的属性值进行改变操作,如直接改变View的color,从而实现颜色的动画效果。
继承自ValueAnimator类,它的动画实现机制是基于ValueAnimator类的
工作原理:通过不断控制 值 的变化,再不断 自动 赋给对象的属性,从而实现动画效果.
ObjectAnimator
与 ValueAnimator
类的区别:
ValueAnimator
类是先改变值,然后 手动赋值 给对象的属性从而实现动画;是 间接 对对象属性进行操作;ObjectAnimator
类是先改变值,然后 自动赋值 给对象的属性从而实现动画;是 直接 对对象属性进行操作;
具体使用:可以使用ObjectAnimator实现补间动画的4种类型
ObjectAnimator animator=ObjectAnimator.ofFloat(mBtnStartAnimation,"alpha",1f,0f,1f);
animator.setDuration(5000);
animator.start();
ObjectAnimator animator=ObjectAnimator.ofFloat(mBtnStartAnimation,"rotation",0f,360f);
animator.setDuration(5000);
animator.start();
float curTranslationX = mBtnStartAnimation.getTranslationX();
ObjectAnimator animator=ObjectAnimator.ofFloat(mBtnStartAnimation,"translationX",curTranslationX,360f, curTranslationX);
animator.setDuration(5000);
animator.start();
ObjectAnimator animator=ObjectAnimator.ofFloat(mBtnStartAnimation,"scaleX",1f,3f, 1f);
animator.setDuration(5000);
animator.start();
不仅仅是这几种属性值,可以写任意的属性值,因为ObjectAnimator
类 对 对象属性值 进行改变 再不断的自动赋给对象的属性 从而实现动画效果的。
至于是怎么自动赋给对象的属性的?本质
- 是调用该对象属性的set() & get()方法进行赋值
- 所以,
ObjectAnimator.ofFloat(Object object, String property, float ....values)
的第二个参数传入值的作用是:让ObjectAnimator
类根据传入的属性名 去寻找 该对象对应属性名的set() & get()
方法,从而进行对象属性值的赋值
自动赋值的逻辑:
- 初始化时,如果属性的初始值没有提供,则调用属性的
get()
进行取值; - 当 值 变化时,用对象该属性的
set()
方法,从而从而将新的属性值设置给对象属性。
ObjectAnimator
类针对的是任意对象 & 任意属性值,并不是单单针对于View对象- 如果需要采用
ObjectAnimator
类实现动画效果,那么需要操作的对象就必须有该属性的set() & get()
***自定义对象属性实现动画效果
- 为对象设置需要操作属性的set() & get()方法
- 通过实现TypeEvaluator类从而定义属性变化的逻辑
ObjectAnimator
更加智能、自动化程度更高
举例:一个圆的颜色渐变
public class PotinView2 extends View {
private static final float RADIUS=100f;//圆的半径
private Paint mPaint;
private String color;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
mPaint.setColor(Color.parseColor(color));
invalidate();
}
public PotinView2(Context context) {
super(context);
}
public PotinView2(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.BLUE);
}
//先在初始点画圆,通过监听当前坐标值(currentPoint)的变化,每次变化都调用onDraw()重新绘制圆,从而实现圆的平移动画效果
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(500,500,RADIUS,mPaint);
}
}
public class PropertyActivity extends AppCompatActivity {
private PotinView2 potinView2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_property);
potinView2=findViewById(R.id.pointView2);
ObjectAnimator animator=ObjectAnimator.ofObject(potinView2,"color",new ColorEvaluator(),"#0000FF", "#FF0000");
animator.setDuration(5000);
animator.start();
}
}
具体可参考:https://www.jianshu.com/p/2412d00a0ce4
上述对象属性的set()不是设置属性 或 根本没有set() / get ()
的情况应该如何处理?
手动设置属性的set() get()方法
通过包装原始对象,间接给对象加上该属性
public class AnimationActivity extends AppCompatActivity {
private ImageView mIvImageView;
private Button mBtnStartAnimation;
private Button mBtnStopAnimation;
private AnimationDrawable mAnimationDrable;
private ViewWrapper viewWrapper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_animation);
mIvImageView = findViewById(R.id.iv_image);
mBtnStartAnimation = findViewById(R.id.start_animation);
mBtnStopAnimation = findViewById(R.id.stop_animation);
}
public void startAnimation(View view)
}
public void stopAnimation(View view) {
viewWrapper=new ViewWrapper(mBtnStartAnimation);
ObjectAnimator.ofInt(viewWrapper, "width", 500).setDuration(3000).start();
// 设置动画的对象是包装类的对象
}
private static class ViewWrapper {
private View mTarget;
public ViewWrapper(View target) {
mTarget = target;
}
public int getWidth() {
return mTarget.getLayoutParams().width;
}
public void setWidth(int width) {
mTarget.getLayoutParams().width = width;
mTarget.requestLayout();
}
}
}
好了,至此属性动画的两个核心类就学完了。
属性动画的本质原理:通过不断对值进行改变,并不断将该值赋给对象的属性,从而实现该对象在该属性上的动画效果
整体文章参考:https://www.jianshu.com/p/2412d00a0ce4
如果面试中被问到,为什么属性动画被移动之后仍可点击?
view动画和属性动画的实现
//平移动画 TranslateAnimation translateAnimation = new TranslateAnimation(0, 150, 0, 300); translateAnimation.setDuration(500); translateAnimation.setFillAfter(true); textView.startAnimation(translateAnimation); //属性动画 textView.animate().translationX(150).translationY(300).setDuration(500).start();
textView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(AnimationActivity.this,"clicked",Toast.LENGTH_SHORT).show(); } });
view动画,当textView位移之后,监听点击事件的区域还在原来的地方。而属性动画,监听点击事件的区域随着View的移动监听事件也随着移动了。
这是为什么呢?
补间动画不能改变接受触摸事件的区域,而属性动画就可以?是因为属性动画改变了坐标吗?
因为他们返回的值都分别加上了Translaion的值,而属性动画更新帧时,也更新了TransLation的值,所以当动画播放完毕,getX 和getY,总能取到正确的值。
public float getX() {
return mLeft + getTranslationX();
}
public float getY() {
return mTop + getTranslationY();
}
参考链接:https://mp.weixin.qq.com/s/cmsuZyJaqBHAA-oC0SyS-g