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

来源:互联网 发布:ipad可以淘宝直播吗 编辑:程序博客网 时间:2024/05/23 23:19

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

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


一、ValueAnimator原理


1、Interpolator


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


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




理解工作原理最好的方法就是: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,可以看出

@HasNativeInterpolatorpublic 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

@HasNativeInterpolatorpublic 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<Number> {    /**     * This function returns the result of linearly interpolating the start and end values, with     * <code>fraction</code> representing the proportion between the start and end values. The     * calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>,     * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,     * and <code>t</code> is <code>fraction</code>.     *     * @param fraction   The fraction from the starting to the ending values     * @param startValue The start value; should be of type <code>float</code> or     *                   <code>Float</code>     * @param endValue   The end value; should be of type <code>float</code> or <code>Float</code>     * @return A linear interpolation between the start and end values, given the     *         <code>fraction</code> 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<Integer> {    /**     * This function returns the result of linearly interpolating the start and end values, with     * <code>fraction</code> representing the proportion between the start and end values. The     * calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>,     * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,     * and <code>t</code> is <code>fraction</code>.     *     * @param fraction   The fraction from the starting to the ending values     * @param startValue The start value; should be of type <code>int</code> or     *                   <code>Integer</code>     * @param endValue   The end value; should be of type <code>int</code> or <code>Integer</code>     * @return A linear interpolation between the start and end values, given the     *         <code>fraction</code> 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<PointF> {    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<PointF> 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;    }}



实现效果







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

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;            }        });    }}










0 0
原创粉丝点击