Before I saw the qq picture sending effect is very cool, very attractive, but now this effect seems to have no. After a few tries, I decided to give it a try. Think about it roughly, the implementation effect is good

The effect that needs to be achieved

A picture is worth a thousand words. Here’s the picture:

How do you do that?

First of all, it can be divided into two parts from the figure. One part is the halo effect of the progress strip. The second part is the process of the circle spreading to the whole picture and showing the whole picture. The next step is code analysis implementation.

1. The scope of drawing, including the picture display, is all in the rounded rectangle, so the canvas should be cut to the rounded rectangle first.

        val path = Path()
        canvas.save()
        path.addRoundRect(RectF(0f, 0f, width.toFloat(), height.toFloat())
                , round, round, Path.Direction.CW)
        canvas.clipPath(path)
Copy the code

Save the canvas first, and end with canvas.restore(). There are two options for displaying images. The first option is to draw your own images by drawable method. Second: Inherit ImageView and get all the properties that ImageView provides, such as scaleType. ImageView is essentially a Drawable implementation. IamgeView also helps us deal with the measurement arrogance, so why not choose inheritance. Then draw the image with a simple line of code, and then crop the canvas:

  super.onDraw(canvas)
Copy the code

2. Draw the background

You can see the renderings below, which are black and translucent. And it shows up at the end. Everything is related to the canvas drawing background. So without further ado, let’s set the brush.

    private var paint: Paint = Paint()
    paint.isAntiAlias = true
    paint.color = getColor(R.color.bantouming)
Copy the code

The canvas. DrawPaint method is used to draw the background. Paint the color of paint over the entire canvas. And I draw it behind the image, so it’s at the top.

canvas.drawPaint(paint)
Copy the code

3. Drawing schedule

According to the different states at each stage, it can be distinguished by three state values:

    companion object {
        private const val READY = 1
        private const val PROGRESS = 2
        private const val FINISH = 3
    }
Copy the code

For easy drawing, and the entire view is symmetric. So moving the coordinate point to the center of the view is very helpful.

     canvas.save()
     canvas.translate(width / 2f, height / 2f)
Copy the code

Finally, don’t forget canvas.restore().

It’s all settled in the middle. Let’s look at the percentage implementation. It’s going to be a little bit tricky to figure out the x and y of drawText(), but you can figure out the baseline and stuff like that.

Let’s look at the percentage paint

    private val textPaint by lazy {
        Paint().apply {
            isAntiAlias = true
            style = Paint.Style.STROKE
            textSize = dp2px(16f).toFloat()
            color = getColor(R.color.main_gray)
        }
    }
Copy the code

Now let’s draw. This might be a long line of code. It needs to be optimized. Don’t make fun of it here. Kind of lazy.

You can see the width and height of the text. To draw. There’s a little extra attention to height here

textPaint.textHeight().div(2) - textPaint.descent()
Copy the code

Textpiat.descent () needs to be subtracted; descent will be drawn downward if it is not subtracted.

  val text = "${progress}%"
  canvas.drawText(text, 0 - textPaint.measureText(text).div(2), textPaint.textHeight().div(2) - textPaint.descent(), textPaint)
Copy the code

4. Draw halo

This is where the implementation gets tricky. There are three things to note:

  • 1. Implementation of halo
  • 2. Breathing effect
  • 3. PorterDuffXmode use.

See breathing effect how to achieve first. Maybe the easy thing to think about is doing it through a circle. If you overlay two circles and set paint.xfermode(porterduff.mode.dst_out) ‘ ‘, you can cut out the inner circles. See the previous article on xferMode for details on how to use it. The implementation of halo depends on Shader, which is implemented by RadilGradient ‘. See the previous article for details.

Set the shader

     paint.setShader(RadialGradient(0f, 0f, outRadius
                        , intArrayOf(Color.TRANSPARENT, Color.WHITE, Color.WHITE, Color.TRANSPARENT)
                        , floatArrayOf(0.1F, 0.4F, 0.8f, 1F), Shader.TileMode.CLAMP)Copy the code

The next breath effect is achieved by animating the change of the radius of the large circle.

canvas.drawCircle(0f, 0f, innRaduus + (outRadius - innRaduus) * animatorValue, paint)
Copy the code

The complete code is as follows

canvas.drawCircle(0f, 0f, innRaduus + (outRadius - innRaduus) * animatorValue, paint)
paint.setXfermode(PorterDuffXfermode(PorterDuff.Mode.DST_OUT))
paint.setShader(null)
paint.color = Color.WHITE
canvas.drawCircle(0f, 0f, innRaduus, paint)
paint.setXfermode(null)
Copy the code

If that’s all it is then you draw a black hole in the middle. Because the background is transparent. So you need canvas.savlayer before drawing. The following

val sc = canvas.saveLayer(-outRadius, -outRadius, outRadius, outRadius, paint, Canvas.ALL_SAVE_FLAG)
Copy the code

The scope of preservation includes large and small circles and is finally restored

canvas.restoreToCount(sc)
Copy the code

Add animatorValue from 0 to 1 to complete the PROGRES phase animation.

5. Draw FINISH animation to reveal the image effect

Porterduff.mode.dst_out is also used here, but you need to operate on the entire rounded canvas range. DST is the background that Canvas. drawPaint draws. SRC is a full rounded rectangle with half the diagonal as the maximum radius, from the PROGRES state of the radius of the large circle, to the maximum range of animation changes. As follows:

val sc = canvas.saveLayer(-width.div(2f), -height.div(2f), width.div(2f), height.div(2f), paint, Canvas.ALL_SAVE_FLAG) canvas.drawPaint(paint) val maxRadius = Math.sqrt(Math.pow(width.toDouble(), (2.0) + math.h pow height. ToDouble (), Div (2) paint. Xfermode = PorterDuffXfermode(porterduff.mode.dst_out) paint. Color = color.white canvas.drawCircle(0f, 0f, (outRadius + (maxRadius - outRadius) * finishAnimValue).toFloat(), paint) paint.xfermode = null canvas.restoreToCount(sc)Copy the code

Use and alternation of animation. This point is relatively simple to use ValueAnimator, set the property, Listener can be. Interested can refer to the source code

Making: github.com/hewking/Hal…


Thank you for taking the time to read this long wordy article

Here I also share a private, their own collection of organized Android learning PDF+ architecture video + interview document + source notes, and advanced architecture technology advanced brain map, Android development interview topic information, advanced architecture information to help you learn to promote advanced, but also save you on the Internet to search for information time to learn, You can also share it with your friends to learn together

If you need to, you can like + comment, follow me, click here to receive Android Learning PDF+ architecture video + interview documents + source notes