引言:
属性动画是一种很强大的东西,不同于补间动画,它在完成之后,会保留最终的状态。比如说,实现一个平移操作,最后View就会真的失去初始的位置和占用的空间,而获得一个新的位置和空间。
说明:
这里我们说明一下为什么要自定义上面两个东西。
1.对于Evaluator,可用的类型有好几种,比如Integer,Float等,他们是基于一个类型进行变换。如果我们想要实现从一个对象变到另一个对象,当然必须是是同一个类的对象。这种状态的过渡就需要我们自己重写evaluate方法来实现。
2.那又为什么要自定义插值器呢,因为有时候我们可能需要根据需求,在规定时间内对每个时间段动画变化快慢和方式进行调整,因此需要重写getInterpolation方法。可以把参数input理解为定义域为【0,1】的自变量x,我们要返回一个同样值域为【0,1】的因变量y,且y是x的函数。哈哈哈,太形象了吧。
实现TypeEvaluator:
说一下fraction,这是一个分数,在0到1之间,可以理解为进度。
/**
* 自定义计算器,计算[0,1]之间对象的每个对应状态
* 我们这里改变点的坐标
*/
public class PointEvaluator implements TypeEvaluator<Point> {
@Override
public Point evaluate(float fraction, Point startValue, Point endValue) {
Point point=new Point();
point.x= (int) (startValue.x+fraction*(endValue.x-startValue.x));
point.y=(int)(startValue.y+fraction*(endValue.y-startValue.y));
return point;
}
}
实现TimeInterpolator:
这个实现,是用来控制上面那个进度变化的速度,变化快慢。
/**
* 自定义插值器,即控制动画变换快慢,这里我们实现以正弦函数在【0,1】变换
* 即斜率逐渐减小,动画逐渐变的迟缓
*/
public class MyInterpolator implements TimeInterpolator{
@Override
public float getInterpolation(float input) {
//正弦函数sin(Π*x/2),在定义域区间【0,1】取值为【0,1】,若定义的函数值域超出【0,1】
//会出现回弹现象,即不能准确定位到我们定义属性动画时的最终状态
return (float) Math.sin(input*Math.PI/2);
}
}
完整Demo:
给出完整的代码,实现一个“点击按钮就在屏幕上以正弦函数变化快慢绘制一个小圆的移动过程”的功能。
MainActivity:
package com.example.advancedanimatorusage;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import com.example.advancedanimatorusage.views.MyView;
public class MainActivity extends AppCompatActivity {
private Button mStartAnimation;
private MyView myView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
setClickEvent();
}
private void setClickEvent() {
mStartAnimation.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//开始动画时,隐藏按钮,显示MyView
mStartAnimation.setVisibility(View.GONE);
myView.setVisibility(View.VISIBLE);
}
});
}
private void initView() {
mStartAnimation=findViewById(R.id.bt_start_animation);
myView=findViewById(R.id.my_view_main);
}
public void setmStartAnimationVisibility() {
//动画结束时,显示按钮,隐藏MyView
mStartAnimation.setVisibility(View.VISIBLE);
myView.setVisibility(View.GONE);
}
}
布局代码:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".MainActivity">
<Button
android:id="@+id/bt_start_animation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start Animation"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.example.advancedanimatorusage.views.MyView
android:id="@+id/my_view_main"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
MyView(实现绘制的自定义View):
package com.example.advancedanimatorusage.views;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
import com.example.advancedanimatorusage.MainActivity;
public class MyView extends View {
private Paint mPaint;
private static final int RADIUS=80;
private Point currentPoint;
private Point startPoint; //初始状态
private Point endPoint; //最后状态
private MainActivity mContext;
public MyView(Context context) {
super(context);
init(context);
}
public MyView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(currentPoint==null){
currentPoint=new Point(RADIUS,RADIUS);
canvas.drawCircle(currentPoint.x,currentPoint.y,RADIUS,mPaint);
startAnimator();
}else{
canvas.drawCircle(currentPoint.x,currentPoint.y,RADIUS,mPaint);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
startPoint=new Point(RADIUS,RADIUS);
endPoint=new Point(getMeasuredWidth()-RADIUS,getMeasuredHeight()-RADIUS);
}
private void init(Context context){
//初始化画笔对象
mPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.RED);
mContext= (MainActivity) context;
}
public void startAnimator(){
//定义一个属性动画
ValueAnimator valueAnimator = ValueAnimator.ofObject(new PointEvaluator(),startPoint,endPoint);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Point point= (Point) animation.getAnimatedValue();
currentPoint.set(point.x,point.y);
//调用该方法,会使View重新绘制,会清空画布,并且会重新调用onDraw方法
invalidate();
}
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mContext.setmStartAnimationVisibility();
currentPoint=null;
}
});
valueAnimator.setInterpolator(new MyInterpolator());
valueAnimator.setDuration(4000);
valueAnimator.start();
}
/**
* 自定义计算器,计算[0,1]之间对象的每个对应状态
* 我们这里改变点的坐标
*/
public class PointEvaluator implements TypeEvaluator<Point> {
@Override
public Point evaluate(float fraction, Point startValue, Point endValue) {
Point point=new Point();
point.x= (int) (startValue.x+fraction*(endValue.x-startValue.x));
point.y=(int)(startValue.y+fraction*(endValue.y-startValue.y));
return point;
}
}
/**
* 自定义插值器,即控制动画变换快慢,这里我们实现以正弦函数在【0,1】变换
* 即斜率逐渐减小,动画逐渐变的迟缓
*/
public class MyInterpolator implements TimeInterpolator{
@Override
public float getInterpolation(float input) {
//正弦函数sin(Π*x/2),在定义域区间【0,1】取值为【0,1】,若定义的函数值域超出【0,1】
//会出现回弹现象,即不能准确定位到我们定义属性动画时的最终状态
return (float) Math.sin(input*Math.PI/2);
}
}
}
效果图由于是动态的,还不会制作gif~~~自己复制粘贴我的代码去试试吧,注意改一改引入的文件就好了