1. Write at the beginning

By convention, the backhand is a hyperlink: the Github address

2. The target

The View effect to be achieved in this paper is shown as follows:

3. The analysis

It is easy to see from the renderings that the functions in the diagram are mainly divided into two parts:

  • Left thumb animation
  • Text animation on the right

3.1 Left side (PraiseView)

It is not difficult to find that the left animation effect is mainly composed of three parts:

  1. MotionEvent_DOWN thumb zoom and UP thumb zoom
  2. MotionEvent_UP circle diffusion (Water ripple)
  3. MotionEvent_UP effect on the top four line segments

Thumb zoom everybody objective must also know, there are no more than two ways:

  • Use the scale animation for the entire View
  • Careful and objective use of scale animation for VectorDrawable in View has found that when four line segments exist, after clicking, line segments will also scale accordingly. Yes, Beans scale the entire View. The code is as follows:
Override public Boolean onTouchEvent(MotionEvent event) {switch (event.getactionmasked ()) {Override public Boolean onTouchEvent(MotionEvent event) {caseMotionEvent.ACTION_DOWN: move = event.getY(); The animate (). ScaleY (0.8 f). ScaleX (0.8 f). The start ();break;
            case MotionEvent.ACTION_UP:
                getHandler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        animate().cancel();
                        setScaleX(1);
                        setScaleY(1); }}, 300); . // omit extraneous codebreak;
        }
        return super.onTouchEvent(event);
    }
Copy the code

#### 3.1.1 Circle diffusion Yes, circle drawing. Similarly, a careful comrade should have discovered something, and there seemed to be some skeletons in the closet. Yes, there are two caveats:

  • The radius of the initial circle, and the center of the circle, where the circle should be drawn.
  • Measure the size of the View and ensure that the bound of the drawable is wrapped.
@override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { switch (widthSpecMode) { ...case MeasureSpec.AT_MOST:
                widthMeasureSpec = mDrawable.getIntrinsicWidth();
                break; . } switch (heightSpecMode) { ... // wrap_contentcase MeasureSpec.AT_MOST:
                heightMeasureSpec = mDrawable.getIntrinsicHeight();
                break; . }setMeasuredDimension(widthMeasureSpec, heightMeasureSpec); initDrawable(mDrawable, widthMeasureSpec, heightMeasureSpec); InitPointFs (1.3 f); Private void initDrawable(drawable drawable, int width, int width); int height) { mCircleCenter.x = width / 2f; mCircleCenter.y = height / 2; mDrawable = drawable; // The drawable side length is 0.6 of the viewfloat diameter = (float) ((width > height ? Height: width) * 0.6); int left = (int) ((width - diameter)/2); int top = (int)(height - diameter)/2; int right = (int) (left + diameter); int bottom = (int) (top + diameter); Rect drawableRect = new Rect(left, top, right, bottom); mDrawable.setBounds(drawableRect); requestLayout(); }Copy the code

From this, we can calculate the size of the view and drawable, so we can draw it. Now we know where the circle should be drawn, and then the diffusion effect, just need to control the radius of the circle, still look at the code:

@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mDrawable.draw(canvas); drawEffect(canvas); } private void drawEffect(Canvas Canvas) {// Draw circlesif (mRadius > 0)
            canvas.drawCircle(mCircleCenter.x, mCircleCenter.y, mRadius, mPaint);
        if(drawLines == 1) {// drawLines... } public voidanimation() {
        final float radius = getInitRadius(mDrawable);
        ObjectAnimator animator = ObjectAnimator.ofFloat(this, "radius", RADIUS, RADIUS * 1.5F, radius * 3.0f); animator.setInterpolator(new AnticipateInterpolator()); animator.setDuration(500); // draw a line //... set.start(); }Copy the code

So far we have completed the thumb zoom and ripple effect, the heart is happy there is #### 3.1.2 line segment effect line segment how to draw it? Primary and secondary school teachers told us that two points confirm a line segment. The question then shifts:

  • So how do we know where these two points are?
  • For sustainable development, how can we determine the direct distance between two line segments? Each guest officer, might as well drink a cup of tea, eat some melon seeds, thinking about this problem above. . . // Timing… timing timing… Observant friends have noticed that my previous onMeasure method had an initPointFs(1.3); View size = View size = View size = View size = View size
@param scale Scale multiple of the radius of the outer circle */ private void initPointFs(float scale) {
        mPointList.clear();
        float radius = getInitRadius(mDrawable);
        int base = -60;
        int factor = -20;
        for(int i = 0; i < 4; i++) { int result = base + factor * i; PointF p1 = new PointF(McIrclecenter. x + (float) (radius * Math.cos(Math.toRadians(result))),
                    mCircleCenter.y + (float) (radius * Math.sin(Math.toRadians(result))) ); PointF p2 = new PointF(McIrclecenter. x + (new PointF(McIrclecenter. x))float) (scale * radius * Math.cos(Math.toRadians(result))),
                    mCircleCenter.y + (float) (scale * radius * Math.sin(Math.toRadians(result))) ); mPointList.add(p1); mPointList.add(p2); }}Copy the code

It is not hard to find through the code notes that we cleverly used concentric circles and angles to determine the values of the four line segments and the set of eight points (Doudou couldn’t help lamenting the importance of mathematics for programmers). The advantage of this is that it is flexible enough that the spacing and length of the line segments are appropriate regardless of the size of the View. This is the end of the thumb animation on the left.

3.2 Right side (RecordView)

DrawText () = drawText(); drawText() = drawText(); drawText() = drawText(); That’s true in principle, but it’s worth noting:

  • The values of the digits that do not change will not be flipped
  • The height of the Text should be three times that of the previous, current, and next digits. The width of the Text should be three times the width of the previous, current, and next digits.
@override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {switch (widthMeasureSpec) {Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)case MeasureSpec.AT_MOST:
                int width = (int) mPaint.measureText("0", 0, 1) * mCurrentString.length();
                widthMeasureSpec = width;
                break; Switch (heightSpecMode) {··· ·case MeasureSpec.AT_MOST:
                mTextHeight = mPaint.getFontSpacing();
                heightMeasureSpec = (int) (mTextHeight * 3);
                break;
            case MeasureSpec.EXACTLY:
                mPaint.setTextSize(heightSpecSize / 4);
                mTextHeight = (int) mPaint.getFontSpacing();
                heightMeasureSpec = heightSpecSize;
                break;
        }
        pointY = 2 * mTextHeight;
        setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
    }
Copy the code

Once you’ve measured the width and height of the View, it’s time to draw the View’s contents, which are simply a series of String values. It’s easy to implement up to this point, but the hard part is determining the last and the next values and where they are. If you are careful, you may have noticed that in measure, we have an mTextHeigh to record the height of the text, and pointY to record twice the height of the text. Yes, mTextHeight is used to control the position of the three possible string values to be drawn.

DrawText (@nonNULL String text, float x, float y, @nonnull Paint Paint) drawText(@nonnull String text, float x, float y, @nonnull Paint Paint) The position of the bottom of a String is drawn above the bottom. That’s why we use pointY = 2 mTextHeight. So it’s not hard to imagine that the positions of our lastNum, currentNum, and NextNum maps to mTextHeight, 2 * mTextHeight, and 3 * mTextHeight, respectively. At this point, the positions of the three values are determined.

3.2.1 Add 1 animation

First look at the increment of 1 processing, the above code:

    public void addOne() { mCurrentString = String.valueOf(mCurrentNum); mCurrentNum++; mNextString = String.valueOf(mCurrentNum); mStatus = ADD; // The number of digits is incremented by 1if (mCurrentString.length() < mNextString.length()) {
            mCurrentString = "" + mCurrentString;
            requestLayout();
        }

        ObjectAnimator animator = ObjectAnimator.ofFloat(this, "pointY", 2 * mTextHeight, mTextHeight);
        ObjectAnimator alphaAnim = ObjectAnimator.ofInt(this, "paintAlpha", 255, 0);
        AnimatorSet set = new AnimatorSet();
        set.playTogether(alphaAnim, animator);
        set.start();
    }
Copy the code

Code is simpler, is nothing more than doing the move and animation effects of transparency, there is solved the “up-and-down prior to a digital may fade away”, point to note is, the use of digital bits into 1 blank holder processing, do not do this processing, after the digital carry, animation effect will be poor, interested friends can go to try it. Combine the onDraw method to look again:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mStatus == NONE) {
            canvas.drawText(mCurrentString, 0, pointY, mPaint);
        } else if (mStatus == ADD) {
            for(int i = mNextString.length() - 1; i >= 0; i--) { String next = String.valueOf(mNextString.charAt(i)); String current = String.valueOf(mCurrentString.charAt(i)); // the I position needs to be changedif(! next.equals(current)) { mPaint.setAlpha(mPaintAlpha); canvas.drawText(current, mPaint.measureText("0", 0, 1) * i, pointY, mPaint); // mPaintAlpha: 255-0 drawdown mPaint. SetAlpha (255-mpaintalpha) canvas.drawText(next, mPaint.measureText("0", 0, 1) * i, mTextHeight + pointY, mPaint); // I position does not need to change}else {
                    mPaint.setAlpha(255);
                    canvas.drawText(current, mPaint.measureText("0", 0, 1) * i, mTextHeight * 2, mPaint); }}}else if(mStatus == REDUCE) {// pointY is cumulative, so there is a sliding effectfor(int i = mCurrentString.length() - 1; i >= 0; i--) { String last = String.valueOf(mLastString.charAt(i)); String current = String.valueOf(mCurrentString.charAt(i)); // the I position needs to be changedif(! last.equals(current)) { mPaint.setAlpha(mPaintAlpha); canvas.drawText(current, mPaint.measureText("0", 0, 1) * i, mTextHeight + pointY, mPaint); // mPaintAlpha: 255-0 drawdown mPaint. SetAlpha (255-mpaintalpha) canvas.drawText(last, mPaint.measureText("0", 0, 1) * i, pointY, mPaint); // I position does not need to change}else {
                    mPaint.setAlpha(255);
                    canvas.drawText(current, mPaint.measureText("0", 0, 1) * i, mTextHeight * 2, mPaint); }}}}Copy the code

And that’s the crux of it: how do values in places that don’t change not get flipped? The onDraw method gives us the answer, the idea is very simple:

  • Compare the next number to be displayed with each digit of the current number being displayed one by one. If they are different, redraw them through animation effect. If they are the same, draw them directly without animation effect.

At this point, both parts of the GIF have been implemented

3.3 Overall (PraiseRecordView)

The above two views are separate and independent. In order to use this effect more conveniently, we need to integrate the functions of the two views together to achieve a linkage effect. In other words, we need to introduce a ViewGroup to determine the layout of the two views (PraiseView and RecordView). This part mainly involves layout, as well as viewgroup mapping, when using match_parent width and height, how to control the display of child view, interested friends can directly go to see the code, here is not to repeat.

4 summarizes

At this point, I can not help but point the root of the Yellow Crane Tower, looking at the curling smoke, a hand touched the day… On the horizon floated one: the Github address

5 benefits

Complimentary gift package: Ali Cloud air tickets