Go to work recently can be really busy very much, very not easy a bit belong to own time, need not work overtime, actually sometimes feel busy a bit quite good also, at least won’t have idle, empty feeling, take a break in busy just is the most happy. In my spare time, I have recently reviewed the use of custom View, Bezier curve drawing and attribute animation, etc. Well, said so much has not seen the graph ah, no graph no truth, after reading the following wave graph began to roll up the sleeves of code.

Effect:





Send heart effect

The effect is less important than how it is achieved.

implementation

First of all, if we look at the View on this diagram, the whole thing can be viewed as a big container, the heart images can be viewed as imageViews coming out of the bottom of the container in the middle, So we can customize a View that inherits from a RelativeLayout and we dynamically add each image to our View.

. To create aImageViewThe properties of theLayoutParamslp ; .//dWidth dHeight is the length and width of each image, where all heart images are the same size.
dWidth = drawable[0].getIntrinsicWidth();
dHeight = drawable[0].getIntrinsicHeight();
lp = new LayoutParams(dWidth,dHeight);
lp.addRule(ALIGN_PARENT_BOTTOM);
lp.addRule(CENTER_HORIZONTAL);

/ / add the ImageView
ImageView image = new ImageView(getContext());
image.setImageDrawable(drawable[random.nextInt(5)]);
image.setLayoutParams(lp);
addView(image);Copy the code

Ok, so this is pretty simple, now that we can add the ImageView to the bottom of the container, we can move and flit the animation.

We can see that the heart is moving from the bottom to the top, the trajectory is curved, and the position to the top is random, so it’s easy to think that we can just move the ImageView along a curve, so we think of bezier curves, do we use second order or third order?





Second order Bezier curve





Second order Bezier curve formula

This is a second-order Bezier curve, let’s ignore the formula, let’s see if the path of the curve drawn is not consistent with the path of the ImageView motion in our rendering ah, then look at the third-order curve:





Third order Bezier curve





Third order Bezier curve formula

We can see that there are two control points in the third-order Bezier curve. As long as the positions of the two control points on the figure are changed, the feeling of s-shaped motion trajectory can be achieved.

Coming back to the image movement issue, we all know that Android gives us a way to draw Bezier curves, and we can draw different Bezier curves by calling certain methods of Path, but in this case, instead of drawing bezier curves, we need the Path. We get every point on this motion curve, we get the x and y points and we set the x and y of the ImageView to that.





Sports sketches

I simply drew the motion, please do not blame me if I am not good, because I have done my best. It can be seen from this figure that the starting point is fixed, and the end point is basically fixed, but the abscissa is randomly generated within the range of width.

Let’s start to write the animation, first of all, the initial image shows the animation from small to large, transparency gradually changed to 1:

/** * Set the imageView property animation, from small to large, gradually clear * @param image * @return */
public AnimatorSet getInitAnimationSet(final ImageView image){
    ObjectAnimator scaleX = ObjectAnimator.ofFloat(image."scaleX".0.4f,1f);
    ObjectAnimator scaleY = ObjectAnimator.ofFloat(image."scaleY".0.4f,1f);
    ObjectAnimator alpha = ObjectAnimator.ofFloat(image."alpha".0.4f,1f);

    AnimatorSet animate = new AnimatorSet();
    animate.playTogether(scaleX,scaleY,alpha);
    animate.setDuration(500);
    returnanimate ; }...// call this method at PointF
ValueAnimator.ofObject(TypeEvaluator evaluator, Object. values)Copy the code

ValueAnimator. OfObject can generate a ValueAnimator object. TypeEvaluator can customize the change rules we need. The control points in the middle are PointF1 and PointF2, so we define a TypeEvaluator:

Public class BezierEvaluator implements TypeEvaluator<PointF> {/** * this2*/ private PointF point1; private PointF point2 ; public BezierEvaluator(PointF point1 ,PointF point2 ) { this.point1 = point1 ; this.point2 = point2 ; } /** * @paramt@override public PointF evaluate(float)t, PointF point0, PointF point3) {
            PointF point = new PointF();
            point.x = point0.x*(1-t) * (1-t) * (1-t)
                      +3*point1.x*t* (1-t) * (1-t)
                      +3*point2.x*t*t* (1-t) * (1-t)
                      +point3.x*t*t*t ;
            point.y = point0.y*(1-t) * (1-t) * (1-t)
                     +3*point1.y*t* (1-t) * (1-t)
                     +3*point2.y*t*t* (1-t) * (1-t)
                     +point3.y*t*t*t; return point; }}Copy the code

As for the determination of two control points, ensure that one point is on top and the other point is on the bottom:

private PointF getPointF(int scale) {
        PointF pointF = new PointF();
        pointF.x = random.nextInt((mWidth - 100));// Subtract 100 to control the range of activities in the X-axis
        // On the Y axis, to make sure that the second point is above the first point, I split the Y into the upper and lower parts to make the animation better
        pointF.y = random.nextInt((mHeight - 100)) /scale;
        return pointF;
}Copy the code

There is an initial animation, there is a Bezier animation, the sequence can be executed to complete the process:

/** * animation * @param image */
private AnimatorSet getRunAnimatorSet(final ImageView image) {
    AnimatorSet runSet = new AnimatorSet();
    PointF point0 = new PointF((mWidth-dWidth)/2,mHeight-dHeight); / / starting point
    PointF point3 = new PointF(random.nextInt(getWidth()),0); / / end point
    /** * Start executing bezier animation */
    TypeEvaluator evaluator = new BezierEvaluator(getPointF(2),getPointF(1));
    ValueAnimator bezier = ValueAnimator.ofObject(evaluator,point0,point3);
    bezier.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            // Get the x and y values calculated by the Bezier curve and assign them to view so that the heart can follow the curve
            PointF pointF = (PointF) animation.getAnimatedValue();
            image.setX(pointF.x);
            image.setY(pointF.y);
            image.setAlpha(1-animation.getAnimatedFraction()); }}); runSet.play(bezier);
    runSet.setDuration(3000);
    return runSet;
}

/** * merge two animations * @param image */
public void start(final ImageView image){
    AnimatorSet finalSet = new AnimatorSet();
    finalSet.setInterpolator(interpolators[random.nextInt(4)]);// Implement random variable speed
    finalSet.playSequentially(getInitAnimationSet(image), getRunAnimatorSet(image));
    finalSet.setTarget(image);
    finalSet.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            removeView(image); }}); finalSet.start(); }Copy the code

Remove the ImageView~ from the container after performing an animation

Write a method to call the animation:

/** * create a removable View */
public void startAnimation(){
    ImageView image = new ImageView(getContext());
    image.setImageDrawable(drawable[random.nextInt(5)]);
    image.setLayoutParams(lp);
    addView(image);
    start(image);
}Copy the code

When the activity calls the control’s startAnimation() method, we see a heart float to the top.

Now I need one click to repeatedly appear a lot of heart effect, call this method again to pause the animation, so add a timer:

/** * timer, can automatically execute animation */
public void startAutoAnimation(a){ isPlayingAnim = ! isPlayingAnim ;if (isPlayingAnim){
        if(timer! =null){
            timer.cancel();
        }
        if(task! =null){ task.cancel(); }}else {
        timer = new Timer();
        task = new TimerTask() {
            @Override
            public void run(a) {
                // What to do: Send a message
                Message message = handler.obtainMessage();
                message.what = 1;
                handler.sendMessage(message); }}; timer.schedule(task,0.150); // Execute the task in a 150ms loop
    }
}


Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        if (msg.what==1){
            ImageView image = new ImageView(getContext());
            image.setImageDrawable(drawable[random.nextInt(5)]); image.setLayoutParams(lp); addView(image); start(image); }}};Copy the code

Ok, that’s it, attached with the complete code, many of the attributes can be extracted and defined to write in the XML layout, I think it’s easy to write in the control.

Finally, attach the complete source code:

package com.wzh.ffmpeg.study.view;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AnimationSet;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.widget.ImageView;
import android.widget.RelativeLayout;

import com.wzh.ffmpeg.study.R;

import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;

/** * author: Administrator on 2017/3/15 09:18 * description: File description * version: version */
public class BezierView extends RelativeLayout {
private Interpolator[] interpolators ;
private Drawable drawable[];
/** * the width and height of the image */
private int dWidth = 0 ;
private int dHeight = 0 ;
private LayoutParams lp ;
private Random random ;
/** * parent control width height */
private int mWidth = 0 ;
private int mHeight = 0 ;
private Timer timer = null;
private TimerTask task = null ;
private boolean isPlayingAnim = true ;

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

public BezierView(Context context, @Nullable AttributeSet attrs) {
    this(context, attrs,0);
}

public BezierView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
}

/** * Initializes data */
private void init() {
    drawable = new Drawable[5];
    drawable[0] = ContextCompat.getDrawable(getContext(), R.drawable.red);
    drawable[1] = ContextCompat.getDrawable(getContext(),R.drawable.yellow);
    drawable[2] = ContextCompat.getDrawable(getContext(),R.drawable.deep_red);
    drawable[3] = ContextCompat.getDrawable(getContext(),R.drawable.blue);
    drawable[4] = ContextCompat.getDrawable(getContext(),R.drawable.green);

    interpolators = new Interpolator[4];
    interpolators[0] = new AccelerateInterpolator();
    interpolators[1] = new DecelerateInterpolator();
    interpolators[2] = new AccelerateDecelerateInterpolator();
    interpolators[3] = new LinearInterpolator();

    dWidth = drawable[0].getIntrinsicWidth();
    dHeight = drawable[0].getIntrinsicHeight();

    lp = new LayoutParams(dWidth,dHeight);
    lp.addRule(ALIGN_PARENT_BOTTOM);
    lp.addRule(CENTER_HORIZONTAL);

    random = new Random();
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    // Get the width and height of the control
    mWidth = getMeasuredWidth();
    mHeight = getMeasuredHeight();
}

/** * create a removable View */
public void startAnimation(){
    ImageView image = new ImageView(getContext());
    image.setImageDrawable(drawable[random.nextInt(5)]);
    image.setLayoutParams(lp);
    addView(image);
    start(image);
}

/** * timer, can automatically execute animation */
public voidstartAutoAnimation(){ isPlayingAnim = ! isPlayingAnim ;if (isPlayingAnim){
        if(timer! =null){
            timer.cancel();
        }
        if(task! =null){ task.cancel(); }}else {
        timer = new Timer();
        task = new TimerTask() {
            @Override
            public void run() {
                // What to do: Send a message
                Message message = handler.obtainMessage();
                message.what = 1; handler.sendMessage(message); }}; timer.schedule(task,0.150); // Execute the task in a 150ms loop
    }
}


Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        if (msg.what==1){
            ImageView image = new ImageView(getContext());
            image.setImageDrawable(drawable[random.nextInt(5)]);
            image.setLayoutParams(lp);
            addView(image);
            start(image); }}};/** * view is called after it is destroyed to free resources */
@Override
protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    if(timer! =null){
        timer.cancel();
    }
    if(task! =null){ task.cancel(); }}/** * Set the imageView property animation, from small to large, gradually clear * @param image * @return */
public AnimatorSet getInitAnimationSet(final ImageView image){
    ObjectAnimator scaleX = ObjectAnimator.ofFloat(image."scaleX".0.4f,1f);
    ObjectAnimator scaleY = ObjectAnimator.ofFloat(image."scaleY".0.4f,1f);
    ObjectAnimator alpha = ObjectAnimator.ofFloat(image."alpha".0.4f,1f);

    AnimatorSet animate = new AnimatorSet();
    animate.playTogether(scaleX,scaleY,alpha);
    animate.setDuration(500);
    return animate ;
}
/** * animation * @param image */
private AnimatorSet getRunAnimatorSet(final ImageView image) {
    AnimatorSet runSet = new AnimatorSet();
    PointF point0 = new PointF((mWidth-dWidth)/2,mHeight-dHeight); / / starting point
    PointF point3 = new PointF(random.nextInt(getWidth()),0); / / end point
    /** * Start executing bezier animation */
    TypeEvaluator evaluator = new BezierEvaluator(getPointF(2),getPointF(1));
    ValueAnimator bezier = ValueAnimator.ofObject(evaluator,point0,point3);
    bezier.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            // Get the x and y values calculated by the Bezier curve and assign them to view so that the heart can follow the curve
            PointF pointF = (PointF) animation.getAnimatedValue();
            image.setX(pointF.x);
            image.setY(pointF.y);
            image.setAlpha(1-animation.getAnimatedFraction()); }}); runSet.play(bezier);
    runSet.setDuration(3000);
    return runSet;
}

/** * merge two animations * @param image */
public void start(final ImageView image){
    AnimatorSet finalSet = new AnimatorSet();
    finalSet.setInterpolator(interpolators[random.nextInt(4)]);// Implement random variable speed
    finalSet.playSequentially(getInitAnimationSet(image), getRunAnimatorSet(image));
    finalSet.setTarget(image);
    finalSet.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            removeView(image); }}); finalSet.start(); }/** * get control point * @param scale * @return */
private PointF getPointF(int scale) {
    PointF pointF = new PointF();
    pointF.x = random.nextInt((mWidth - 100));// Subtract 100 to control the range of activities in the X-axis
    // On the Y axis, to make sure that the second point is above the first point, I split the Y into the upper and lower parts to make the animation better
    pointF.y = random.nextInt((mHeight - 100)) /scale;
    return pointF;
}
public class BezierEvaluator implements TypeEvaluator<PointF> {
    /** * these two points are control points */
    private PointF point1 ;
    private PointF point2 ;
    public BezierEvaluator(PointF point1 ,PointF point2 ) {
        this.point1 = point1 ;
        this.point2 = point2 ;
    }
    /** * @param t * @param point0 start point * @param point3 end point * @return */
    @Override
    public PointF evaluate(float t, PointF point0, PointF point3) {
        PointF point = new PointF();
        point.x = point0.x*(1-t)*(1-t)*(1-t)
                  +3*point1.x*t*(1-t)*(1-t)
                  +3*point2.x*t*t*(1-t)*(1-t)
                  +point3.x*t*t*t ;
        point.y = point0.y*(1-t)*(1-t)*(1-t)
                 +3*point1.y*t*(1-t)*(1-t)
                 +3*point2.y*t*t*(1-t)*(1-t)
                 +point3.y*t*t*t ;
        return point; }}}Copy the code

Acitivity call

BezierView bse = (BezierView) findViewById(R.id.bse);
bse.startAutoAnimation(); // Play the animation automaticallyCopy the code

The main thing is to customize the properties of animations, TypeEvaluator<PointF>, which is the core idea. If you want to be compatible with versions 3.0 or lower, you can add your own nineoldandroids package to support animations from earlier versions.

Android custom View – Bezier Curve drawing and property animation (2)

Wrong place hope everybody points out, learn from each other, thank ~