Related recommendations:

  • The Android source code to read | Handler
  • Android custom View | wave motion effect
  • Programming based | introduction to coding format

1. A brief introduction

This time to achieve a simple animation, animation is the practice. The effect to be achieved is as follows:

This is a signboard that pops up from the bottom every time it switches, and the text changes. The animation is simple and suitable for beginners.

2. Animation decomposition

The animation takes place in a Rect:240px by 290px square (1280 by 720 here) that has three bodies: the indicator, the indicator stick, and the indicator text. The animation time is 350 milliseconds, and the speed is accelerated and then decelerated.

Indicator pole:

It starts at the bottom, shows a little bit, and has a clockwise tilt, off to the right of the center of the X-axis, then pops out of the bottom, rises to near the top, then falls down, and finally reaches the end point, which is in the center of the X-axis, with the bottom of the bar close to the bottom of the animation area, and the tilt of the bar becomes 0°.

  • 1) Moving to the end point from the right off the X-axis center, this is the X-axis displacement
  • 2) Pop up to fall, this is the change of displacement in the Y direction
  • 3) Tilt Angle X to tilt Angle 0, this is the change in rotation Angle

Sign:

The indicator is also the same animation as the indicator pole. It is invisible at the bottom of the animation area at the beginning, and then pops out. To be specific, there is no displacement in the X-axis direction of the indicator

Sign text:

The text follows the signboard, same animation, but pay attention to the starting point of the text drawing.

3. Simple version implementation

We set an animation completion factor, changeFactor, and use ObjectAnimator to change the value of the factor to move each subject.

private float changeFactor = 0;
private final float FACTOR_END = 100;
private final float FACTOR_START = 0;

objectAnimator = ObjectAnimator.ofFloat(this."changeFactor", FACTOR_START, FACTOR_END);
objectAnimator.setDuration(350);
objectAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
objectAnimator.start();
Copy the code

ChangeFactor represents the progress of the entire animation, from 0 to 100, and don’t forget to set the corresponding getter/setter method, and in the setter method, tell the view to redraw.

public void setChangeFactor(float changeFactor) {
    this.changeFactor = changeFactor;
    invalidate();
}
public float getChangeFactor(a) {
    return changeFactor;
}
Copy the code

Sign pole

There are three variations of the signpost, which together produce the final result. Let’s look at the first one:

  • Here we load the indicator stick:

bitmapTrunk = BitmapFactory.decodeResource(getResources(), R.drawable.car_sign_trunk);

  • We’re moving from the right away from the center of the X axis to the end point, this is the displacement in the X direction, the actual data from the UI, we’re moving from the right away from the center of the X axis to the center, we’re movingbitmapTrunk.getWidth() / 2And ourschangeFactorThat is, the completion of the animation is from0 - > 100, needs to map to0->bitmapTrunk.getWidth() / 2, so we need a mapping function:
 /** * factor mapping *@paramCurrentFactor The value of the cause child *@paramOrigStartFactor Specifies the upper limit of a cause *@paramOrigEndFactor the lower bound of the cause *@paramStartFactor Upper limit of a new factor *@paramEndFactor The lower limit of the new factor *@returnThe value of the new factor */
public static float factorMapping(float currentFactor, float origStartFactor, float origEndFactor, float startFactor, float endFactor) {
    return (currentFactor - origStartFactor) * (endFactor - startFactor) / (origEndFactor - origStartFactor) + startFactor;
}
Copy the code
  • Applied to theonDrawInside, to better illustrate, an animation area is drawn:RectF rectFSign = new RectF(0,0,240,290), because considering the need for rotation, displacement and other animation, so, hereMatrixTo drawBitmap
// Draw the animation area
paint.setColor(Color.GRAY);
canvas.translate(100.20);
canvas.drawRect(rectFSign,paint);

// bitmapTrunk
BitmapTrunk width/2
matrix.reset();
float xOffset = ViewUtils.factorMapping(changeFactor, FACTOR_START, FACTOR_END, 0, bitmapTrunk.getWidth() / 2);
matrix.postTranslate(rectFSign.right/2 - xOffset, rectFSign.bottom);
canvas.drawBitmap(bitmapTrunk, matrix, paint);
Copy the code
  • The resulting effect is as follows:

And then the second one, the Y displacement

  • Starting from the upper left corner of the drawing area, the Y-axis motion of the rod is: from the beginning (35px from the top of the rod to the bottom of the drawing area)y=255And move up toy=5And then move down toy=120So here we have to be right againchangeFactorThe Mapping toy=255 -> y=5 -> 120There is an interpolation in the middle of the Mapping change, so you can’t use the above function directly.

It’s kind of A math problem: Give two regions of change A: (origStartFactor origEndFactor), B (startFactor, interpolatorFactor, A current value currentFactor endFactor) and area, B area have A interpolation, that is, the change is: StartFacotr -> interpolatorFactor -> endFactor Here’s an answer I wrote:

/** * factor mapping *@paramCurrentFactor The value of the cause child *@paramOrigStartFactor Specifies the upper limit of a cause *@paramOrigEndFactor the lower bound of the cause *@paramStartFactor Upper limit of a new factor *@paramInterpolatorFactor Interpolation of new factors *@paramEndFactor The lower limit of the new factor *@returnThe value of the new factor */
public static float factorMapping(float currentFactor, float origStartFactor, float origEndFactor,
                                  float startFactor, float interpolatorFactor, float endFactor) {
    // The current percentage
    float temp = (currentFactor - origStartFactor) / (origEndFactor - origStartFactor);
    // New total size
    float newFactorMax = Math.abs(startFactor-interpolatorFactor) + Math.abs(endFactor-interpolatorFactor);
    // The percentage of the upper limit
    float startTemp = Math.abs(startFactor-interpolatorFactor) / newFactorMax;
    if (startTemp<temp) {
        // Percentage of the current limit
        float tempX = Math.abs( (temp - startTemp) * newFactorMax / Math.abs(endFactor-interpolatorFactor));
        return tempX * (endFactor-interpolatorFactor) + interpolatorFactor;
    }
    else {
        // Percentage of the current limit
        float tempX = Math.abs(temp * newFactorMax / Math.abs(startFactor-interpolatorFactor));
        returntempX * (interpolatorFactor-startFactor) + startFactor; }}Copy the code
  • Applied to theonDrawinside
// bitmapTrunk
BitmapTrunk width/2
matrix.reset();
float xOffset = ViewUtils.factorMapping(changeFactor, FACTOR_START, FACTOR_END, 0, bitmapTrunk.getWidth() / 2);
// Vertical displacement, (rectFSign. Bottom-35, 5, 120)
float yOffset = ViewUtils.factorMapping(changeFactor, FACTOR_START, FACTOR_END, rectFSign.bottom - 35.5.120);
matrix.postTranslate(rectFSign.right/2 - xOffset, yOffset);
canvas.drawBitmap(bitmapTrunk, matrix, paint);
Copy the code

Same with the third

  • Rotating from21 °to0 °, we want to base onBitmap for center rotation, encapsulatematrixRotation operation of:
/** * Apply rotation to matrix * This rotation is based on Bitmap center rotation **@paramBitmap object *@paramRotation degree *@paramPosX Canvas drawing Bitmap starting point *@paramPosY Canvas drawing Bitmap starting point *@param matrix matrix
 */
public static void applySelfRotationToMatrix(Bitmap bitmap, float rotation, float posX, float posY, Matrix matrix) {
    float offsetX = bitmap.getWidth() / 2;
    float offsetY = bitmap.getHeight() / 2;
    matrix.postTranslate(-offsetX, -offsetY);
    matrix.postRotate(rotation);
    matrix.postTranslate(posX + offsetX, posY + offsetY);
}
Copy the code
  • Applied to theonDraw
// bitmapTrunk
BitmapTrunk width/2
float xOffset = ViewUtils.factorMapping(changeFactor, FACTOR_START, FACTOR_END, 0, bitmapTrunk.getWidth() / 2);
// Vertical displacement, (rectFSign. Bottom-35, 5, 120)
float yOffset = ViewUtils.factorMapping(changeFactor, FACTOR_START, FACTOR_END, rectFSign.bottom - 35.5.120);
// Rotate, 21° -0 °, counterclockwise
float rotationOffset = ViewUtils.factorMapping(changeFactor, FACTOR_START, FACTOR_END, 21.0);
matrix.reset();
ViewUtils.applySelfRotationToMatrix(bitmapTrunk, rotationOffset,rectFSign.right / 2 - xOffset, yOffset, matrix);
canvas.drawBitmap(bitmapTrunk, matrix, paint);
Copy the code

sign

With the above analysis, the same goes for signs:

 // bitmapCover
 // Rotate, -10° -0 °, counterclockwise
 rotationOffset = ViewUtils.factorMapping(changeFactor, FACTOR_START, FACTOR_END, -10.0);
 
 Rectfsign. bottom, 80, 108
 yOffset = ViewUtils.factorMapping(changeFactor, FACTOR_START, FACTOR_END, rectFSign.bottom, 80.108);
 
 matrix.reset();
 ViewUtils.applySelfRotationToMatrix(bitmapCover, rotationOffset,rectFSign.right / 2 - bitmapCover.getWidth() / 2, yOffset, matrix);
 canvas.drawBitmap(bitmapCover, matrix, paint);
Copy the code

Signage

  • The text needs to move with the signage, which is rotation, displacement, so normaldrawTextIt doesn’t work. We usedrawTextOnPath“, this method is to write text on a path, just draw a path at the corresponding position of the sign, write text on the path, and then let the path move with the sign, that is, the text also moves. To illustrate, let’s draw a picture of the soul:

  • If the indicator is not rotated, then its textpathThis is the blue line AB (approximate position, can be adjusted later), the coordinates of A and B are also very easy to obtain, and the rotation is based on the signpost center rotation, which is based on the rotation of M, so the blue line AB should also be based on the rotation of M to get the corresponding red line A’B’. So how do we get the coordinates of A prime and B prime?Again, this is A mathematical problem: Given A point A(Ax,ay) and M(mx,my) on the coordinate plane, find the coordinates of A’ based on the rotation of M at any Angle θ.
/** * In the plane, the coordinates of the point after a point is rotated radian * about any point **@paramRX0 rotates to reference point X *@paramRY0 rotation reference point y *@paramRadian Rotate radian *@paramX rotates at the point x star@paramY rotation point y star@returnThe result of the rotation, x, y star/theta
public static float pointRotateGetX(float rX0, float rY0, float radian, float x, float y) {
    return (float) ((x - rX0) * Math.cos(radian) - (y - rY0) * Math.sin(radian) + rX0);
}
public static float pointRotateGetY(float rX0, float rY0, float radian, float x, float y) {
    return (float) ((x - rX0) * Math.sin(radian) + (y - rY0) * Math.cos(radian) + rY0);
}
Copy the code
  • Let’s draw a Path and rotate it:
// Lower left point A
float x0 = rectFSign.right / 2 - bitmapCover.getWidth() / 2;
float y0 = yOffset + bitmapCover.getHeight();
// Rotate point M
float rX0 = x0 + bitmapCover.getWidth() / 2;
float rY0 = y0 - bitmapCover.getHeight() / 2;
// Lower right point B
float x1 = x0 + bitmapCover.getWidth();
float y1 = y0;
// Rotate radians
float radians = (float) Math.toRadians(rotationOffset);
//A'
path.moveTo(ViewUtils.pointRotateGetX(rX0, rY0, radians, x0, y0), ViewUtils.pointRotateGetY(rX0, rY0, radians, x0, y0));
//B'
path.lineTo(ViewUtils.pointRotateGetX(rX0, rY0, radians, x1, y1), ViewUtils.pointRotateGetY(rX0, rY0, radians, x1, y1));
/ / A 'B' line
paint.setColor(Color.RED);
paint.setStrokeWidth(5);
canvas.drawPath(path,paint);
Copy the code

  • Well, it’s almost there, just tweak it a little bit and put the text in:
// Draw the path of STR
paint.setColor(Color.RED);
path.reset();
/ / lower left
float x0 = rectFSign.right / 2 - bitmapCover.getWidth() / 2;
float y0 = yOffset + bitmapCover.getHeight();
/ / pivot point
float rX0 = x0 + bitmapCover.getWidth() / 2;
float rY0 = y0 - bitmapCover.getHeight() / 2;
/ / right point
float x1 = x0 + bitmapCover.getWidth();
float y1 = y0;
float radians = (float) Math.toRadians(rotationOffset); path.moveTo(ViewUtils.pointRotateGetX(rX0, rY0, radians, x0, y0), ViewUtils.pointRotateGetY(rX0, rY0, radians, x0, y0));  path.lineTo(ViewUtils.pointRotateGetX(rX0, rY0, radians, x1, y1), ViewUtils.pointRotateGetY(rX0, rY0, radians, x1, y1));  paint.setColor(Color.WHITE); paint.setTextSize(signStrSize);if(typeface ! =null)
    paint.setTypeface(typeface);
float hOffset = (bitmapCover.getWidth() - paint.measureText(signStr)) / 2;
canvas.drawTextOnPath(signStr, path, hOffset, -20, paint);
Copy the code

See Demo for details

4. Elegant version implementation

  • After writing the above, the total feeling is not too elegant, as if many functions and many apis are written by themselves, is there no native? I looked it up, I was too young, I had such a set of animation API.

  • Our animation involves a variety of property values, and we share animation time and interpolators, so it should be used hereAnimatorSetProperty animation collection to do.PropertyValuesHolderIs an element of the animation collection that represents a property value.

Here directly post the code, mainly to determine the change area of the attribute value can be:

// The indicator pole's X axis displacement
PropertyValuesHolder trunkXPropertyValuesHolder =
        PropertyValuesHolder.ofFloat("trunkX",trunkX, trunkX-bitmapTrunk.getWidth()/2);
// The Y axis of the indicator pole is shifted
PropertyValuesHolder trunkYPropertyValuesHolder =
        PropertyValuesHolder.ofFloat("trunkY",trunkY,5.120);
// The rotation of the signpost changes
PropertyValuesHolder trunkRotationDegreePropertyValuesHolder =
        PropertyValuesHolder.ofFloat("trunkRotationDegree",trunkRotationDegree,0);
// Indicate the shift of the Y axis
PropertyValuesHolder coverYPropertyValuesHolder =
        PropertyValuesHolder.ofFloat("coverY",coverY,80.108);
// The rotation of the indicator Y axis changes
PropertyValuesHolder coverRotationDegreePropertyValuesHolder =
        PropertyValuesHolder.ofFloat("coverRotationDegree",coverRotationDegree,0);
// Add to the animation collection
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(this,
        trunkXPropertyValuesHolder,trunkYPropertyValuesHolder,trunkRotationDegreePropertyValuesHolder,
        coverYPropertyValuesHolder,coverRotationDegreePropertyValuesHolder);
animator.setDuration(350);
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.addUpdateListener(valueAnimator -> CarAdvancedSignView.this.invalidate());
animator.start();
Copy the code
  • Here, you also declare the corresponding set of attribute values:TrunkX, trunkY, trunkRotationDegree...And the correspondinggetter/setterMethods. Note that when animated property values are updated, each property value is called one by onesetterMethod, when all property values have been updated, calls:
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator valueAnimator) {}});Copy the code
  • So, you need to refresh actively in onAnimationUpdate.

  • Words, the same idea as above.

  • Final effect:

The effect is about the same, the CPU performance is about the same, the amount of code, TSK TSK…

See Demo for details

5. To summarize

  • Through this animation, it’s familiardrawTextOnPathWhen the text has animation effects (mainly displacement, rotation), you can consider using this idea
  • More familiarAnimatorSetThe use of

Code word is not easy, convenient if the quality of three even, or pay attention to my public number technology sauce, focus on Android technology, not regularly push fresh articles, if you have a good article to share with you, welcome to pay attention to contribute!