emmmm…. It’s hard to get a title this time. I don’t know what to call this animation

It is also a small friend’s demand, I helped to achieve, and then sent me a few small red envelopes, today at work can be free to order a cup of Starbucks later, here again thanks to the throw line greatly taught me to write animation, haha ~

This 10 minute animation also takes 10 minutes to achieve, no title dog oh.

By the way, not all the animations I wrote will be included in the 10-minute series. The first two were about drawing, this time mainly ObjectAnimation, and the next 10-minute series will also be about different knowledge points, please pay attention to them if you like.

The topic is far away, first look at the effect ~

This is my implementation on demo ~

Requirements: refer to the design drawing implementation, the more realistic the better.

Animation and dismantling

As usual, get the animation, before the implementation of the convention of dismantling, this animation let’s disassemble into three stages.

  • Stage 1: A dozen “scary” emoticons appear successively at a fixed speed on the screen. The rotation Angle and position of each emoticons are random, and the icon closer to the center of the screen is bigger.
  • Stage 2: Shake “left and right” according to the rotation Angle of each icon. Note that this is not the left and right side of the screen, but the action shake of the icon. That is to say, ICONS with different rotation angles wobble in different directions.
  • Three stages: zoom to 8x size, and the opacity goes from 1 to 0 halfway through the zoom animation.

Step by step implementation of unpack animation

If you see this, you can think about how to do it.

Think for three minutes…

Okay, three minutes have passed.

The three stages of this animation are realized based on ObjectAnimation. If you don’t already know ObjectAnimation, take a look at HenCoder 1.6 and 1.7.

A phase

There are three requirements.

  • A number of “scary” emojis appear on the screen at a fixed speed
  • The rotation Angle and position of the creepy expression are random
  • The closer you get to the center of the screen, the bigger the expression

First, the first question, which is very simple, involves only the timing function. Draw 0 ~ m ICONS in sequence within 0 ~ N time.

Second question, the rotation Angle and position for each expression of random, icon overlapping problems may arise, but if every time a new icon to detect problems cover again and whether existing icon, the performance will be embarrassing, and random position is not strong demand, after the last communication with designer, Agree with the size and rotation Angle of 17 ICONS.

The third problem, the second problem solved, the third problem naturally disappear.

Hahahahaha, isn’t that great? Programmers should remember to be brave enough to communicate with designers. Ok, let me just say what we should do if the designer doesn’t agree to write the dead position and the icon can’t cover each other.

  • If detecting new icon, will it cover other existing icon? First of all, we regard a circle as a circle, and the icon will randomly generate a center point(x,y). Meanwhile, we can also calculate the radius according to the size. Then, the existing icon is traversed before adding to determine whether the center distance of the new and old ICONS is greater than the radius and.
  • How about closer to the center of the screen the bigger the expression. Set an X-axis centering coefficient and a Y-axis centering coefficient according to the center point(x,y) of the icon and the point center(centX,centY) in the middle of the screen. Then set the scale of the icon according to these two coefficients.

The parameters here involve the central point of the icon x and y, rotation Angle rotate and scale. So we can create a bean IconInfo to hold this information.

Two stage

Requirement: According to the rotation Angle of icon, do left and right jitter operation.

This is a little awkward, if it’s just a 0 degree rotation, then you can shake 50px left and right by adding or subtracting 50px from the x axis. But a 90-degree rotation becomes a y plus or minus 50px. Both cases are fine. What about 45 degrees? I’m adding and subtracting x and y at the same time

? \dfrac{ \sqrt{2}}{2} *50?

Well, I was thinking of this, and suddenly I realized that this is a company that computes sines and cosines. So, when the rotation Angle is rotate, the X-axis deviation is cos(rotate) ·offset and the Y-axis deviation is sin (rotate) ·offset.

This sine and cosine is junior high school mathematics knowledge, you should understand it.

Three phase

This is not interesting, just a scaleX, scaleY, and alpha operation.

Code implementation

Since this is not my own project, and I don’t know if there are other similar requirements, the code implementation should be as decoupled as possible, preferably able to display the animation in a single line of code call.

Imagine our Toast, SnackBar, also popping up a show on the screen, but how decoupled their implementations are.

This time MY implementation reference SnackBar, do not have to invade the code in the layout file, the implementation of a line of code display animation, plug and play ~ hahahahaha

First feel the code call ~

findViewById(R.id.fab).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { AnimationHelper.start(v); }});Copy the code

All right, enough bullshit. I’ll just post the code.

public class AnimationHelper {

public static void start(View view) {
    ViewGroup suitableParent = findSuitableParent(view);
    MyView child = new MyView(view.getContext());
    suitableParent.addView(child);
}

private static ViewGroup findSuitableParent(View view) {
    ViewGroup fallback = null;
    do {
        if (view instanceof FrameLayout) {
            if (view.getId() == android.R.id.content) {
                return (ViewGroup) view;
            } else{ fallback = (ViewGroup) view; }}if (view != null) {
            final ViewParent parent = view.getParent();
            view = parent instanceof View ? (View) parent : null;
        }
    } while(view ! = null);return fallback;
}

private static class MyView extends View implements View.OnClickListener {

    private Bitmap mIcon;
    private Paint mPaint;
    private int mWidth;
    private int mHeight;
    private int showCount;
    private int shake;
    private ArrayList<AnimationInfo> mInfo = new ArrayList<>();
    Matrix mMatrix = new Matrix();

    public MyView(Context context) {
        super(context);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        mWidth = MeasureSpec.getSize(widthMeasureSpec);
        mHeight = MeasureSpec.getSize(heightMeasureSpec);
        init();
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }


    private void init() {
        if (mWidth == 0)
            return;
        mIcon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_face_shock);
        mPaint = new Paint();
        mPaint.setAntiAlias(true); mPaint.setColor(Color.WHITE); mInfo.clear(); Int width = (int) (micon.getwidth () * 0.5); Int height = (int) (micon.getheight () * 0.5); int centerX = mWidth / 2 - width / 2; MInfo. Add (new AnimationInfo(0.5f, 0, centerX + width * 4F, mHeight * 3/4-height)); MInfo. Add (new AnimationInfo(0.55f, 20, centerX + width * 3.4f, mHeight * 3/4-height * 2.2f)); MInfo. Add (new AnimationInfo(0.2f, 340, centerX + width * 2.6f, mHeight * 3/4-height * 3.5f)); MInfo. Add (new AnimationInfo(0.65f, 20, centerx-width, mHeight / 2-height)); MInfo. Add (new AnimationInfo(0.2f, 340, centerx-width * 2.8f, mHeight / 2 + height)); MInfo. Add (new AnimationInfo(0.5f, 20, centerX + width * 0.5f, mHeight / 4-height * 2F)); MInfo. Add (new AnimationInfo(0.7f, 320, centerX, mHeight / 2F)); MInfo. Add (new AnimationInfo(0.5f, 40, centerx-width * 0.5f, mHeight / 2 + height * 3F)); MInfo. Add (new AnimationInfo(0.2f, 250, centerx-width * 2F, mHeight / 2-height * 2F)); MInfo. Add (new AnimationInfo(0.2f, 320, centerx-width * 3F, mHeight / 2-height * 1.5f)); MInfo. Add (new AnimationInfo(0.2f, 45, centerX + width * 3F, mHeight / 2-height * 2F)); MInfo. Add (new AnimationInfo(0.75f, 20, centerX, mHeight / 2-height * 2.5f)); MInfo. Add (new AnimationInfo(0.2f, 320, centerX + width * 1.5f, mHeight / 2-height * 4F)); MInfo. Add (new AnimationInfo(0.2f, 45, centerX + width * 0.2f, mHeight / 2-height * 4.5f)); MInfo. Add (new AnimationInfo(0.5f, 320, centerx-width * 1.8f, mHeight / 2-height * 5F)); MInfo. Add (new AnimationInfo(0.2f, 100, centerX + width * 1.8f, mHeight / 2 + height * 3F)); MInfo. Add (new AnimationInfo(0.5f, 320, centerX, mHeight / 2 + height * 5F)); MInfo. Add (new AnimationInfo(0.5f, 10, centerx-width * 0.5f, mHeight / 2 + height * 1.5f)); showCount = 0;setOnClickListener(this);

        ObjectAnimator animator1, animator2, animator3;


        animator1 = ObjectAnimator.ofInt(this, "showCount", mInfo.size());
        animator1.setDuration(mInfo.size() * 35);

        animator2 = ObjectAnimator.ofInt(this, "shake", 0, 20, 0, -20, 0, 20, 0, -20);
        animator2.setDuration(300);

        PropertyValuesHolder scaleXValuesHolder = PropertyValuesHolder.ofFloat("scaleX", 1, 8);
        PropertyValuesHolder scaleYValuesHolder = PropertyValuesHolder.ofFloat("scaleY", 1, 8); Keyframe keyframe1 = Keyframe.ofFloat(0, 1); Keyframe Keyframe2 = keyframe.offloat (0.5f, 1); Keyframe keyframe3 = Keyframe.ofFloat(1, 0); PropertyValuesHolder alphaValuesHolder = PropertyValuesHolder.ofKeyframe("alpha", keyframe1, keyframe2, keyframe3);
        animator3 = ObjectAnimator.ofPropertyValuesHolder(this, scaleXValuesHolder, scaleYValuesHolder, alphaValuesHolder);
        animator3.setDuration(200);

        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playSequentially(animator1, animator2, animator3);
        animatorSet.start();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (showCount < mInfo.size()) {
            drawStep1(canvas);
        } else {
            drawStep2(canvas);
        }

    }

    private void drawStep1(Canvas canvas) {
        for (int i = 0; i < showCount && i < mInfo.size(); i++) {
            AnimationInfo info = mInfo.get(i);
            canvas.save();
            mMatrix.reset();
            mMatrix.postScale(info.scale, info.scale, info.x, info.y);
            mMatrix.postRotate(info.rotate, info.x, info.y);
            canvas.concat(mMatrix);
            canvas.drawBitmap(mIcon, info.x, info.y, mPaint);
            canvas.restore();
        }
    }

    private void drawStep2(Canvas canvas) {
        for (int i = 0; i < showCount && i < mInfo.size(); i++) {
            AnimationInfo info = mInfo.get(i);
            canvas.save();
            mMatrix.reset();
            float x = info.calculateTranslationX(shake);
            float y = info.calculateTranslationY(shake);
            mMatrix.postScale(info.scale, info.scale, x, y);
            mMatrix.postRotate(info.rotate, x, y);
            canvas.concat(mMatrix);
            canvas.drawBitmap(mIcon, x, y, mPaint);
            canvas.restore();
        }
    }

    @Override
    public void onClick(View v) {
        ViewGroup parent = (ViewGroup) v.getParent();
        parent.removeView(v);
    }

    @Keep
    private void setShowCount(int showCount) {
        this.showCount = showCount;
        invalidate();
    }

    @Keep
    private void setShake(int shake) {
        this.shake = shake;
        invalidate();
    }

}

private static class AnimationInfo {
    float scale;
    float rotate;
    float x;
    float y;

    public AnimationInfo(float scale, float rotate, float x, float y) {
        this.scale = scale;
        this.rotate = rotate;
        this.x = x;
        this.y = y;
    }

    public float calculateTranslationX(float length) {
        return (float) Math.cos(rotate) * length + x;
    }

    public float calculateTranslationY(float length) {
        return (float) Math.sin(rotate) * length + y; }}}Copy the code

The code is pretty simple, so I’m not going to comment it out.

There are a few points I’d like to mention

  • Under normal circumstances, MyView naming is not canonical, and MyView should not be used as an inner class of AnimationHelper. Instead, it can be used as an inner class of MyView, AnimationInfo.
  • The findSuitableParent method in AnimationHelper is copied from SnackBar, which I discussed in the source code of SnackBar.
  • Drawp1 and drawStep2 in onDraw can actually be combined into one method, so I’ll separate them for you to understand.
  • If you have any more code to optimize, feel free to slap brick, I will continue ~
  • Canvas and Matrix related codes are used in drawing. If you can’t understand them, you can read the article on throwline.
  • Later thought to add ~

Like to remember to point a concern oh ~