Interpolator+TypeEvaluator实现贝塞尔曲线插值器

官方提供的Android动画相当丰富,但有时我们需要实现一些特殊的运动轨迹,如缓动曲线时就会束手无策,接下来我们实现一个基于贝

塞尔曲线的自定义动画轨迹


一、ValueAnimator原理


1、Interpolator


Interpolator即插值器,其作用为根据时间计算属性值的变化,它实现了TimeInterpolator接口


Android系统预置的Interpolator的实现类包括:

Interpolator+TypeEvaluator实现贝塞尔曲线插值器_第1张图片



理解工作原理最好的方法就是:Read the F*cking source code


TimeInterpolator源码

/**
 * A time interpolator defines the rate of change of an animation. This allows animations
 * to have non-linear motion, such as acceleration and deceleration.
 */
public interface TimeInterpolator {

    /**
     * Maps a value representing the elapsed fraction of an animation to a value that represents
     * the interpolated fraction. This interpolated value is then multiplied by the change in
     * value of an animation to derive the animated value at the current elapsed animation time.
     *
     * @param input A value between 0 and 1.0 indicating our current point
     *        in the animation where 0 represents the start and 1.0 represents
     *        the end
     * @return The interpolation value. This value can be more than 1.0 for
     *         interpolators which overshoot their targets, or less than 0 for
     *         interpolators that undershoot their targets.
     */
    float getInterpolation(float input);
}

TimeInterpolator接口中规定了一个方法,其接收一个float值,从0开始,至1结束


通过查看线性变化的插值器——LinearInterpolator,可以看出

@HasNativeInterpolator
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {

    public LinearInterpolator() {
    }

    public LinearInterpolator(Context context, AttributeSet attrs) {
    }

    public float getInterpolation(float input) {
        return input;
    }

    /** @hide */
    @Override
    public long createNativeInterpolator() {
        return NativeInterpolatorFactoryHelper.createLinearInterpolator();
    }
}

getInterpolation()返回值与input相等


加速运动插值器——AcclerateDecelerateInterpolator

@HasNativeInterpolator
public class AccelerateDecelerateInterpolator extends BaseInterpolator
        implements NativeInterpolatorFactory {
    public AccelerateDecelerateInterpolator() {
    }

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

    public float getInterpolation(float input) {
        return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
    }

    /** @hide */
    @Override
    public long createNativeInterpolator() {
        return NativeInterpolatorFactoryHelper.createAccelerateDecelerateInterpolator();
    }
}

返回值为类cos函数0至1段曲线的值

同理,其他系统预置插值器均是函数变化的,参考:插值器函数




2、TypeEvaluator


TypeEvaluator即估值器,其作用是根据当前属性改变进度计算改变后的属性,如ValueAnimator.ofFloat()中为了实现初始值到结束值的平滑过渡,系统内置了FloatEvaluator


FloatEvaluator源码

public class FloatEvaluator implements TypeEvaluator {

    /**
     * This function returns the result of linearly interpolating the start and end values, with
     * fraction representing the proportion between the start and end values. The
     * calculation is a simple parametric calculation: result = x0 + t * (v1 - v0),
     * where x0 is startValue, x1 is endValue,
     * and t is fraction.
     *
     * @param fraction   The fraction from the starting to the ending values
     * @param startValue The start value; should be of type float or
     *                   Float
     * @param endValue   The end value; should be of type float or Float
     * @return A linear interpolation between the start and end values, given the
     *         fraction parameter.
     */
    public Float evaluate(float fraction, Number startValue, Number endValue) {
        float startFloat = startValue.floatValue();
        return startFloat + fraction * (endValue.floatValue() - startFloat);
    }
}


FloatEvaluator重写了TypeEvaluator的evaluate方法,其参数分别为fraction 动画完成度 、 startValue 开始值 、 endValue 结束

值 并根据三个值计算改变后的属性

public class IntEvaluator implements TypeEvaluator {

    /**
     * This function returns the result of linearly interpolating the start and end values, with
     * fraction representing the proportion between the start and end values. The
     * calculation is a simple parametric calculation: result = x0 + t * (v1 - v0),
     * where x0 is startValue, x1 is endValue,
     * and t is fraction.
     *
     * @param fraction   The fraction from the starting to the ending values
     * @param startValue The start value; should be of type int or
     *                   Integer
     * @param endValue   The end value; should be of type int or Integer
     * @return A linear interpolation between the start and end values, given the
     *         fraction parameter.
     */
    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }
}


观察IntEvaluator可以看出evaluate计算方法基本相似




3、ValueAnimator机制总结


1、根据时间t及时长duration的比例计算出fraction,传给Interpolator


2、Interpolator根据自己的插值器函数计算出新的fraction,传给TypeEvaluator


3、TypeEvalutor计算出animatedValue


4、控件根据animatedValue,在AnimatorUpdateListener中改变属性值




二、实现Bezier曲线轨迹


贝塞尔曲线相关:贝塞尔曲线开发的艺术


这里我们要使用三阶贝塞尔曲线:


B(t)=(1t)3P0+3(1t)2tP1+3(1t)t2P2+t3P3t[0,1]

P0为startValue 、 P3为endValue 、 P1、P2为控制点


通过重写TypeEvaluator中evaluate方法实现Bezier曲线轨迹

public class BezierEvaluator implements TypeEvaluator {

    private PointF point1;//控制点1
    private PointF point2;//控制点2

    public BezierEvaluator(PointF point1, PointF point2) {
        this.point1 = point1;
        this.point2 = point2;
    }

    @Override
    public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
        final float t = fraction;
        float minusT = 1.0f - t;
        PointF point = new PointF();

        PointF point0 = startValue;

        PointF point3 = endValue;

        point.x = minusT * minusT * minusT * (point0.x)
                + 3 * minusT * minusT * t * (point1.x)
                + 3 * minusT * t * t * (point2.x)
                + t * t * t * (point3.x);

        point.y = minusT * minusT * minusT * (point0.y)
                + 3 * minusT * minusT * t * (point1.y)
                + 3 * minusT * t * t * (point2.y)
                + t * t * t * (point3.y);

        return point;
    }
}



BezierEvaluator的应用



新建类继承自View

public class TestView extends View implements View.OnTouchListener{

    private Paint bPaint;
    private Paint pPaint;
    private Paint lPaint;
    private PointF pointF;

    int temp = 0;
    private List list = new ArrayList<>();

    public TestView(Context context) {
        super(context);
        init();
    }

    public TestView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        bPaint =  new Paint(Paint.ANTI_ALIAS_FLAG);
        bPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        bPaint.setColor(Color.BLUE);

        pPaint =  new Paint(Paint.ANTI_ALIAS_FLAG);
        pPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        pPaint.setColor(Color.GRAY);

        lPaint =  new Paint(Paint.ANTI_ALIAS_FLAG);
        lPaint.setStyle(Paint.Style.STROKE);
        lPaint.setStrokeWidth(1);
        lPaint.setColor(Color.GRAY);
        lPaint.setTextSize(20);

        this.setOnTouchListener(this);
    }

    public void startAnimator() {
        PointF p0 = list.get(0);
        PointF p1 = list.get(1);
        PointF p2 = list.get(2);
        PointF p3 = list.get(3);

        ValueAnimator animator = ValueAnimator.ofObject(
                new BezierEvaluator(new PointF(p1.x , p1.y), new PointF(p2.x , p2.y))
                ,new PointF(p0.x , p0.y), new PointF(p3.x , p3.y));
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                pointF = (PointF) animation.getAnimatedValue();
                invalidate();
            }
        });
        animator.setDuration(5000);
        animator.start();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for(int i = 0 ; i < list.size() ; i ++){
            PointF p = list.get(i);
            canvas.drawCircle(p.x , p.y , 5 ,pPaint);
            canvas.drawText("[" + (int)p.x + "," + (int)p.y + "]" , 50 , 50 + i * 30 , lPaint);
        }

        temp ++;
        Path path = new Path();
        switch (list.size()){
            case 2:
                canvas.drawLine(list.get(0).x , list.get(0).y , list.get(1).x , list.get(1).y , lPaint);
                break;
            case 3:
                path.moveTo(list.get(0).x , list.get(0).y);
                path.quadTo(list.get(1).x , list.get(1).y , list.get(2).x , list.get(2).y);
                canvas.drawPath(path , lPaint);
                break;
            case 4:
                path.moveTo(list.get(0).x , list.get(0).y);
                path.cubicTo(list.get(1).x , list.get(1).y , list.get(2).x , list.get(2).y , list.get(3).x , list.get(3).y);
                canvas.drawPath(path , lPaint);
                if(pointF == null){
                    canvas.drawCircle(list.get(0).x , list.get(0).y , 40 ,bPaint);
                    startAnimator();
                }else{
                    canvas.drawCircle(pointF.x , pointF.y , 40 , bPaint);
                }
                break;
        }

    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if(list.size() < 4){
            PointF pointF = new PointF();
            pointF.x = event.getX();
            pointF.y = event.getY();

            list.add(pointF);
        }else if(list.size() == 4){
            list.clear();
            temp = 0;
            pointF = null;
        }
        invalidate();
        return false;
    }

}



实现效果


Interpolator+TypeEvaluator实现贝塞尔曲线插值器_第2张图片





根据以上我们可以做出更实用的东西

public class HeartImageView extends ImageView {

    private Bitmap image;

    public HeartImageView(Context context) {
        this(context, null);
    }
    public HeartImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public HeartImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        image = BitmapFactory.decodeResource(getResources(), R.drawable.heart);
    }

    public void setColor(int color) {
        setImageBitmap(createColor(color));
    }

    private Bitmap createColor(int color) {
        int heartWidth= image.getWidth();
        int heartHeight= image.getHeight();
        Bitmap newBitmap=Bitmap.createBitmap(heartWidth, heartHeight, Bitmap.Config.ARGB_8888);
        Canvas canvas=new Canvas(newBitmap);
        Paint paint=new Paint();
        paint.setAntiAlias(true);
        canvas.drawBitmap(image, 0, 0, paint);
        canvas.drawColor(color, PorterDuff.Mode.SRC_ATOP);
        canvas.setBitmap(null);
        return newBitmap;
    }
}



public class HeartLayout extends RelativeLayout {

    private Context context;

    private int width;
    private int height;
    private int[] colors={Color.RED, Color.YELLOW, Color.GRAY, Color.GREEN, Color.BLUE};


    public HeartLayout(Context context) {
        this(context , null);
    }

    public HeartLayout(Context context, AttributeSet attrs) {
        this(context, attrs , 0);
    }

    public HeartLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context = context;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        width=getWidth();
        height=getHeight();
    }

    public  void addHeart() {
        RelativeLayout.LayoutParams params=new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
        params.addRule(RelativeLayout.CENTER_HORIZONTAL);
        final HeartImageView imageView=new HeartImageView(context);
        imageView.setColor(colors[new Random().nextInt(colors.length)]);
        imageView.setVisibility(INVISIBLE);
        addView(imageView, params);

        imageView.post(new Runnable() {
            @Override
            public void run() {

                int point1x=new Random().nextInt((width));
                int point2x=new Random().nextInt((width));
                int point1y=new Random().nextInt(height/2)+height/2;
                int point2y=point1y-new Random().nextInt(point1y);

                int endX=new Random().nextInt(width/2);
                int endY=point2y-new Random().nextInt(point2y);

                ValueAnimator translateAnimator=ValueAnimator.ofObject(
                        new BezierEvaluator(new PointF(point1x, point1y), new PointF(point2x, point2y)),
                        new PointF(width/2-imageView.getWidth()/2, height-imageView.getHeight()),
                        new PointF(endX, endY));

                translateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        PointF pointF= (PointF) animation.getAnimatedValue();
                        imageView.setX(pointF.x);
                        imageView.setY(pointF.y);
                    }
                });
                translateAnimator.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);
                        removeView(imageView);
                    }
                });
                TimeInterpolator[] timeInterpolator={new LinearInterpolator(), new AccelerateDecelerateInterpolator(), new DecelerateInterpolator(), new AccelerateInterpolator()};
                translateAnimator.setInterpolator(timeInterpolator[new Random().nextInt(timeInterpolator.length)]);
                ObjectAnimator translateAlphaAnimator=ObjectAnimator.ofFloat(imageView, View.ALPHA,  1f, 0f);
                translateAlphaAnimator.setInterpolator(new DecelerateInterpolator());
                AnimatorSet translateAnimatorSet=new AnimatorSet();
                translateAnimatorSet.playTogether(translateAnimator, translateAlphaAnimator);
                translateAnimatorSet.setDuration(2000);

                ObjectAnimator scaleXAnimator=ObjectAnimator.ofFloat(imageView, View.SCALE_X, 0.5f, 1f);
                ObjectAnimator scaleYAnimator=ObjectAnimator.ofFloat(imageView, View.SCALE_Y, 0.5f, 1f);
                ObjectAnimator alphaAnimator=ObjectAnimator.ofFloat(imageView, View.ALPHA, 0.5f, 1f);
                AnimatorSet enterAnimatorSet=new AnimatorSet();
                enterAnimatorSet.playTogether(scaleXAnimator, scaleYAnimator, alphaAnimator);
                enterAnimatorSet.setDuration(500);
                enterAnimatorSet.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationStart(Animator animation) {
                        super.onAnimationStart(animation);
                        imageView.setVisibility(VISIBLE);
                    }
                });

                AnimatorSet allAnimator=new AnimatorSet();
                allAnimator.playSequentially(enterAnimatorSet, translateAnimatorSet);
                allAnimator.start();
            }
        });

    }

}

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final HeartLayout layout = (HeartLayout) findViewById(R.id.heart);
        layout.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                layout.addHeart();
                return false;
            }
        });
    }
}



Interpolator+TypeEvaluator实现贝塞尔曲线插值器_第3张图片







你可能感兴趣的:(Android,动画,Android)