preface

The previous article introduced the API and advanced use of Paint. Next, we will talk about the knowledge of Canvas drawing. Canvas can implement many drawing methods, and this article will introduce the use of Canvas.

Today, I found a way to explain customization. You can read the original text of Android about Canvas what you know and don’t know, which is explained in the way of coordinate system. I think it should be easy to understand. I want to draw a circle on the screen x == 500,y == 500, radius ==150, so it looks like this:

Isn’t it clear? You can see where the View is at a glance.

Canvas

Canvas base usage

Draw background color

//color: use 0x hexadecimal color values
void drawColor(int color);
// Allow a, R,g,b to be passed in. Each color value ranges from 0 to 255
void drawARGB(int a, int r, int g, int b)
// Only R.G.B color components are allowed to be passed in. Transparency alpha is 255
void drawRGB(int r, int g, int b)
Copy the code

Draw a red background that looks like this

    override fun draw(canvas: Canvas) {
        super.draw(canvas)
        / / 1.
        canvas.drawColor(Color.RED)
        / / 2.
/ / canvas. DrawARGB (0 XFF, 0 XFF, 0, 0 x00)
        / / 3.
/ / canvas. DrawRGB (0 XFF, 0 x00 to 0 x00)
    }
Copy the code

All three apis have the same effect

Draw a straight line

//startX,y
//stopX,y endpoint
/ / paint brush
void drawLine(float startX, float startY, float stopX, float stopY,
            @NonNull Paint paint)
void drawLines(@Size(multiple = 4) @NonNull float[] pts, int offset, int count,
            @NonNull Paint paint)
void drawLines(@Size(multiple = 4) @NonNull float[] pts, @NonNull Paint paint)  
Copy the code
    override fun draw(canvas: Canvas) {
        super.draw(canvas)
        mPaint.strokeWidth = 10f
        mPaint.setColor(Color.RED)
        / / 1.
        canvas.drawLine(100f,100f,600f,600f,mPaint)
        / / 2.
        canvas.drawLines(floatArrayOf(
            100f,100f,600f,600f
        ),mPaint)
        // The first argument is a set of coordinate points
      			// Which coordinate point the second argument starts from
      			// The third parameter coordinates point is to fetch 4 data
        canvas.drawLines(floatArrayOf(
            100f,100f,600f,600f
        ),0.4,mPaint)
    }
Copy the code

The effect is the same

Plot points

/ / x, y coordinates
void drawPoint(float x, float y, @NonNull Paint paint)
// PTS coordinate group, offset from which point, count: how many points
void drawPoints(@Size(multiple = 2) float[] pts, int offset, int count,
            @NonNull Paint paint)
/ / PTS coordinate groups
void drawPoints(@Size(multiple = 2) @NonNull float[] pts, @NonNull Paint paint)
Copy the code
        / / 1
        canvas.drawPoint(100f, 100f, mPaint)
        
        / / 2.
        var offset = 0
        var pts = floatArrayOf(
            500f + offset, 100f, 500f + offset, 200f,
            500f + offset, 300f, 500f + offset, 600f,
            500f + offset, 700f, 500f + offset, 800f,
            500f + offset, 900f, 500f + offset, 1000f
        )
        mPaint.setColor(Color.BLUE)
        canvas.drawPoints(pts, 0.16, mPaint)
        
        / / 3.
        mPaint.setColor(Color.GREEN)
        offset = 100
        pts = floatArrayOf(
            500f + offset, 100f, 500f + offset, 200f,
            500f + offset, 300f, 500f + offset, 600f,
            500f + offset, 700f, 500f + offset, 800f,
            500f + offset, 900f, 500f + offset, 1000f
        )
        canvas.drawPoints(pts, mPaint)
Copy the code

The effect is as follows:

Draw a rectangle

RectF: Saves a float rectangle structure

Rect: Saves the int rectangle structure

Top,right, and bottom (left,top,right,bottom, left,top, bottom, left,top,right,bottom

/ / 1.
void drawRect(@NonNull RectF rect, @NonNull Paint paint) {
        super.drawRect(rect, paint);
/ / 2.
void drawRect(@NonNull Rect r, @NonNull Paint paint)
/ / 3.
void drawRect(float left, float top, float right, float bottom, @NonNull Paint paint)

Copy the code
        / / 1.
        var rect = RectF(100.50f,100.50f,500.50f,500.50f)
        mPaint.style = Paint.Style.FILL
        canvas.drawRect(rect,mPaint)

        / / 2.

        var rect2 = Rect(300.300.600.600)
        mPaint.style = Paint.Style.FILL
        mPaint.setColor(Color.BLUE)
        mPaint.alpha = 100
        canvas.drawRect(rect2,mPaint)

        / / 3.
        mPaint.style = Paint.Style.FILL
        mPaint.setColor(Color.YELLOW)
        canvas.drawRect(500f,500f,1000f,1000f,mPaint)
Copy the code

The effect is as follows:

Draw the path

// Draw a path according to path
void drawPath(@NonNull Path path, @NonNull Paint paint) 
Copy the code

        /** * 1. Draw a path line */
        var path = Path()
        //1. Set the starting point
        path.moveTo(100f, 100f)
        //2. The starting point of the second line is the start of the moveTo setting
        path.lineTo(100f,300f)
        //3. The beginning of the third line is the end of the second, and so on
        path.lineTo(300f,500f)
        path.lineTo(500f,200f)
        4 / / closed
        path.close()
        canvas.drawPath(path, mPaint)


        /** * 2. Draw a radian path */
        var path2 = Path()
        // Draw the starting position of radians
        path2.moveTo(100f,600f)
        var rectF = RectF(100f,600f,600f,1000f)
        // The first argument generates the rectangle of the ellipse, the second argument is the Angle at which the arc begins 0 degrees in the positive X-axis, and the third argument is the Angle at which the arc continues
        path2.arcTo(rectF,0f,90f)
        canvas.drawPath(path2, mPaint)
Copy the code

The above notes are very detailed, not to explain

Draw a circle/ellipse

// Draw the ellipse
void drawOval(@NonNull RectF oval, @NonNull Paint paint)
/ / draw circle
void drawCircle(float cx, float cy, float radius, @NonNull Paint paint)
Copy the code

        /** * 1. Draw ellipse */
        canvas.drawOval(RectF(100f,500f,600f,800f),mPaint)

        /** * 2. Draw circle */
        mPaint.setColor(Color.YELLOW)
        mPaint.alpha = 100
        canvas.drawCircle(400f,400f,200f,mPaint)
Copy the code

Draw the Bitmap

//
val bitmap = BitmapFactory.decodeResource(context.resources, R.mipmap.gild_3)
// The second and third parameters represent the starting position
canvas.drawBitmap(bitmap,100f.100f,mPaint)
Copy the code

Draw the Text

/ / 1.
void drawText(@NonNull char[] text, int index, int count, float x, float y,
            @NonNull Paint paint)
/ / 2.
void drawText(@NonNull String text, float x, float y, @NonNull Paint paint)
/ / 3.
void drawText(@NonNull String text, int start, int end, float x, float y,
            @NonNull Paint paint)
/ / 4.
void drawText(@NonNull CharSequence text, int start, int end, float x, float y,
            @NonNull Paint paint)
Copy the code

        /** * 1. * Use positions 0 to 5 to draw */
        mPaint.textSize = 100f
        canvas.drawText(charArrayOf('1'.'2'.'3'.'4'.'5'),0.5.200f,200f,mPaint)

        / * * * 2. * /
        canvas.drawText("12345".300f,300f,mPaint)

        /** * 3. Use positions 0 to 5 to draw */
        canvas.drawText("12345".0.5.400f,400f,mPaint)
Copy the code

Draws Text based on the path

// Draw text by offsetting vOffset PX from hOffset
void drawTextOnPath(@NonNull String text, @NonNull Path path, float hOffset,
            float vOffset, @NonNull Paint paint)
Copy the code

        mPaint.setColor(Color.GREEN)
        mPaint.alpha = 100

        mPaint.textSize = 100f
        var path = Path()
        //1. Set the starting point
        path.moveTo(300f, 300f)
        //2. The starting point of the second line is the start of the moveTo setting
        path.lineTo(300f,500f)
        //3. The beginning of the third line is the end of the second, and so on
        path.lineTo(500f,800f)
        path.lineTo(800f,200f)
        4 / / closed
        path.close()
        canvas.drawPath(path,mPaint)
        // Shift the pixel from 0 to 100px
        canvas.drawTextOnPath("12345asodnaspdnfpoashfeuinfapjn",path,0f,100f,mPaint)
Copy the code

Draw arcs/sectors

/ / 1.
void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter,
            @NonNull Paint paint)
/ / 2.
void drawArc(float left, float top, float right, float bottom, float startAngle,
            float sweepAngle, boolean useCenter, @NonNull Paint paint)
Copy the code

        var rectF = RectF(100f, 100f, 500f, 500f)
        /** * 1. Draw arc *@paramOvar: rectangular coordinate *@paramStartAngle: startAngle *@paramSweepAngle: End Angle *@paramUserCenter: If true, includes the center of the ellipse in the arc *@paramPaint: paint */
        canvas.drawArc(rectF, 0f, 90f, true, mPaint)

        /** * 2. Draw arc */
        canvas.drawArc(100f,500f,500f,900f,0f,90f,false,mPaint)
Copy the code

Draw rounded rectangles

/ / 1.
void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint)
/ / 2.
void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
            @NonNull Paint paint)
Copy the code

        /** * 1. Draw a rounded rectangle according to RectF *@paramRx: the radius of a fillet on the X-axis *@paramRy: Radius of fillet on y axis */
        canvas.drawRoundRect(rectF,50f,50f,mPaint)
        /** * 2. Draw a rounded rectangle */ based on the input rectangle position
        canvas.drawRoundRect(100f,600f,500f,900f,100f,100f,mPaint)
Copy the code

Canvas transform

Translate – Layer translation
//dx/dy:x/y point new position
void translate(float dx, float dy)
Copy the code

Scale – Layer scale down 0 to 1
// The larger the reduction coefficient of x and y is between 0 and 1, the closer it is to the original image
void scale(float sx, float sy)
Copy the code

        /** * 1. Original rectangle */
        mPaint.color = Color.RED
        mPaint.alpha = 100
        canvas.drawRoundRect(rectF,50f,50f,mPaint)


        /** * 2. Shrink the original image by 0.5 times */
        var rectF2 = RectF(100f, 100f, 500f, 500f)
        mPaint.color = Color.BLUE
        mPaint.alpha = 100
        canvas.scale(0.5f,0.5f)
        canvas.drawRoundRect(rectF2,50f,50f,mPaint)
Copy the code
Rotate – Layer rotation
/ / 1.
void rotate(float degrees)
/ / 2.
void rotate(float degrees, float px, float py)
Copy the code

        /** * 1. Original rectangle */
        mPaint.color = Color.RED
        mPaint.alpha = 100
        canvas.drawRoundRect(rectF,50f.50f,mPaint)


        /** * 2. Rotate the original shape 45° */
        mPaint.color = Color.BLUE
        mPaint.alpha = 100
        canvas.rotate(45f)
        canvas.drawRoundRect(rectF,50f.50f,mPaint)

        /** * 3. Rotate the original figure 280° * at the coordinates point 500,100 clockwise 280° */
        mPaint.color = Color.YELLOW
        mPaint.alpha = 100
        canvas.rotate(280f.500f.100f)
        canvas.drawRoundRect(rectF,50f.50f,mPaint)
Copy the code
Skew – Layers cut incorrectly
// Tangent is a plane graph obtained by scaling the directed distance between each point of the graph and a line parallel to that direction in a certain direction. Horizontal tangent (or tangent parallel to the X-axis) is an operation that maps any point (X,y) to a point (X +my,y), where m is a fixed parameter called the tangent factor
// Sx and SY are the tangent factors, which are the tan value of the tilt Angle, where the tilt Angle of 45 degrees is 1
void skew (float sx, float sy)
Copy the code

        /** * 1
        mPaint.color = Color.RED
        mPaint.alpha = 100
        canvas.drawRoundRect(rectF,50f,50f,mPaint)
        /** * 2. The layer starts cutting incorrectly */
        canvas.skew(0f,0.5f)
        mPaint.color = Color.BLUE
        mPaint.alpha = 100
        canvas.drawRoundRect(rectF,50f,50f,mPaint)
Copy the code
Matrix

API details

        /** * original graphics */
        canvas.drawBitmap(mBitmap,100f,100f,mPaint)
        /** *1. Shift the matrix 500,500 */
        var matrix = Matrix()
        matrix.setTranslate(500f,500f)
        canvas.drawBitmap(mBitmap,matrix,mPaint)

        /** * 2. The matrix is scaled 0.5 times */
        var matrix2 = Matrix()
        matrix2.setScale(0.5f,0.5f)
        canvas.drawBitmap(mBitmap,matrix2,mPaint)

        /** * 3. Matrix rotation 125° */
        var matrix3 = Matrix()
        matrix3.setRotate(125f,500f,500f)
        canvas.drawBitmap(mBitmap,matrix3,mPaint)

        /** * 4
        var matrix4 = Matrix()
        matrix4.setSkew(0.5f,0.5f)
        canvas.drawBitmap(mBitmap,matrix4,mPaint)
Copy the code

Cut out the canvas

/ / cutting
boolean clipRect(RectF rect, Region.Op op);
boolean clipRect(Rect rect, Region.Op op);
boolean clipRect(RectF rect);
boolean clipRect(Rect rect);
boolean clipRect(float left, float top, float right, float bottom, Region.Op op);
boolean clipRect(float left, float top, float right, float bottom);
boolean clipRect(int left, int top, int right, int bottom);
boolean clipPath(Path path, Region.Op op);
boolean clipPath(Path path);
boolean clipRegion(Region region, Region.Op op);
boolean clipRegion(Region region);
Copy the code

The 1 in the figure above represents the original layer, uncropped; 2 represents the clipped layer; 3. No matter how you draw it, you can only draw it inside the region.

        /** * 1
        mPaint.color = Color.RED
        mPaint.alpha = 100
        canvas.drawRect(300f.300f.700f.700f,mPaint)
        canvas.drawText("1. The original".400f.600f,Paint(Paint.ANTI_ALIAS_FLAG).also {
            it.textSize = 100f
            it.color = Color.WHITE
        })
        /** * 2. Cut a canvas in the RectF rectangle area
        var rectf2 = RectF(100f.100f , 500f.500f);
        canvas.clipRect(rectf2)
        mPaint.color = Color.BLUE
        mPaint.alpha = 100
        canvas.drawColor(mPaint.color)
        canvas.drawText("2.clip".200f.200f,Paint(Paint.ANTI_ALIAS_FLAG).also {
            it.textSize = 100f
            it.color = Color.WHITE
        })

        /** * 3. Draw a rectangle */ at coordinates 700,700
        mPaint.color = Color.YELLOW
        mPaint.alpha = 100
        canvas.drawRect(300f.300f.700f.700f,mPaint)
        canvas.drawText("3. After cutting".350f.400f,Paint(Paint.ANTI_ALIAS_FLAG).also {
            it.textSize = 30f
            it.color = Color.WHITE
        })
Copy the code

Preservation and restoration of canvas

Save and restore usually appear in pairs. Save can save the current state of canvas, and then carry out a series of operations to change canvas, such as translation and clipping. Finally, restore is used to restore the canvas to the state at the time of save.

int save(a)      // Each time this function is called, the current canvas state is saved and placed on a specific stack
void restore(a)  // Restore the canvas by removing the state from the top of the stack
Copy the code

Save the layer by calling Canvas. save when it is not clipped, and restore the previous layer by calling Canvas. restore after clipping

        /** * 1
        mPaint.color = Color.RED
        mPaint.alpha = 100
        canvas.drawRect(300f,300f,700f,700f,mPaint)
        canvas.drawText("1. The original".400f,600f,Paint(Paint.ANTI_ALIAS_FLAG).also {
            it.textSize = 100f
            it.color = Color.WHITE
        })
        /** * 2. Cut a canvas in the RectF rectangle area
        var rectf2 = RectF(100f, 100f , 500f, 500f);
        // Save the uncropped layers first
        canvas.save()
        canvas.clipRect(rectf2)
        mPaint.color = Color.BLUE
        mPaint.alpha = 100
        canvas.drawColor(mPaint.color)
        canvas.drawText("2.clip".200f,200f,Paint(Paint.ANTI_ALIAS_FLAG).also {
            it.textSize = 100f
            it.color = Color.WHITE
        })

        /** * 3. Draw a rectangle */ at coordinates 700,700
        // Unstack after clipping
        canvas.restore()
        mPaint.color = Color.YELLOW
        mPaint.alpha = 100
        canvas.drawRect(300f,300f,600f,600f,mPaint)
        canvas.drawText("3. After cutting".350f,400f,Paint(Paint.ANTI_ALIAS_FLAG).also {
            it.textSize = 30f
            it.color = Color.WHITE
        })
Copy the code

Canvas and layer

The previous section described how save can be used to save layers. Here are some apis that can also be used to save layers

// Bounds: The holding object corresponding to the area to save
//saveFlags: ALL_SAVE_FLAG indicates that all information is saved
public int saveLayer(RectF bounds,Paint paint,int saveFlags)
public int saveLayer(float left,float top,float right,float bottom,Paint paint,int saveFlags)

Copy the code

Draw a clipped area in red and then comment 2 on Draw a circle viewable. You can only draw in the clipped area, but if you draw in the circle viewable area after calling Canvas. RestoreToCount, you will not be affected by comment 3 on the viewable area.

        /** * 1
        mPaint.color = Color.RED
        mPaint.alpha = 200
        canvas.drawRect(300f,300f,1000f,1000f,mPaint)
        canvas.drawText("1. Crop layers".750f,750f,Paint(Paint.ANTI_ALIAS_FLAG).also {
            it.textSize = 30f
            it.color = Color.WHITE
        })
        /** * 2. Save */
        val saveLayer = canvas.saveLayer(300f, 300f, 1000f, 1000f, mPaint,ALL_SAVE_FLAG)
        mPaint.color = Color.BLUE
        mPaint.alpha = 100
        canvas.drawCircle(500f,500f,300f,mPaint)
        canvas.drawText("2. Draw circles".350f,700f,Paint(Paint.ANTI_ALIAS_FLAG).also {
            it.textSize = 30f
            it.color = Color.WHITE
        })

        /** * 3. Restore layer */
        canvas.restoreToCount(saveLayer)
        / * * * * /
        mPaint.color = Color.BLUE
        mPaint.alpha = 100
        canvas.drawCircle(400f,400f,200f,mPaint)
        canvas.drawText("3. Restore".350f,250f,Paint(Paint.ANTI_ALIAS_FLAG).also {
            it.textSize = 30f
            it.color = Color.WHITE
        })
Copy the code

We have practiced all the commonly used Canvas apis here, and now we will proceed to the last part of this article. We will end the Canvas explanation with a sample demo.

Canvas of actual combat

  1. Draw simple clock

    Let’s take a look at the effect, as follows:

    Code drawing structure:

        override fun onDraw(canvas: Canvas) {
            super.onDraw(canvas)
            //1. Draw the outer circle
            canvas.drawCircle(mX, mY, mR.toFloat(), mPaint!!)
    
            //2. Draw the center
            canvas.drawCircle(mX, mY, 15f, mPaintMinute!!)
    
            //3. Draw the scale
            drawLines(canvas)
    
            //4. Draw the hour point
            drawText(canvas)
    
            //5. Update time
            updateCurrentTime(canvas)
        }
    Copy the code

    Here’s how step 5 works:

    /* * * get the current system time * * @param canvas canvas */
    
        private fun updateCurrentTime(canvas: Canvas) {
            // Get the current system time
            val format = SimpleDateFormat("HH-mm-ss")
            val time = format.format(Date(System.currentTimeMillis()))
            val split = time.split("-".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
            val hour = Integer.parseInt(split[0])
            val minute = Integer.parseInt(split[1])
            val second = Integer.parseInt(split[2])
            // The Angle of the hour hand
            val hourAngle = hour * 30 + minute / 2
            // The Angle of the minute hand
            val minuteAngle = minute * 6 + second / 10
            // The Angle of the second hand
            val secondAngle = second * 6
    
            // Draw the clock with 12 o 'clock as the 0° reference point
            drawLine(canvas, hourAngle, 170, mPaintHour)
    
            // Draw minutes
            drawLine(canvas, minuteAngle, 60, mPaintMinute)
    
            // Draw seconds
            canvas.rotate(secondAngle.toFloat(), mX, mY)
            canvas.drawLine(mX, mY, mX, mY - mR + 80, mPaintSecond!!)
    
            // Refresh the interface every 1s
            postInvalidateDelayed(1000)}fun drawLine(canvas: Canvas, angle: Int, length: Int, mPaint: Paint?). {
            canvas.rotate(angle.toFloat(), mX, mY)
            canvas.drawLine(mX, mY, mX, mY - mR + length, mPaint!!)
            canvas.save()
            canvas.restore()
            // Now that the clock is drawn, we need to turn the canvas back again and continue with 12 o 'clock as the 0° reference point
            canvas.rotate((-angle).toFloat(), mX, mY)
        }
    Copy the code

    It’s actually quite simple. For details, see clockView.kt

  2. Slide to switch color pictures

    Let’s take a look at the final effect first, and then we’ll talk about how it works. Please take a look at the screen below:

Through this exercise you will learn:

  1. A custom Drawable
  2. Canvas Layer processing

Here is the principle to achieve the above effect:

  1. cutout
  2. Mosaic
  3. With the need to cut w according to the slide, H dynamic splicing

Let’s start with an example of custom Drawable code

public class CustomDrawable : Drawable {

    lateinit var unseleter: Drawable
    lateinit var selecter: Drawable

    constructor(unseleter: Drawable, selecter: Drawable) : super() {
        this.selecter = selecter
        this.unseleter = unseleter

    }


    override fun draw(canvas: Canvas) {
        // Get the current rectangle of its Drawable
        val bounds = bounds
        //1. Draw the gray part
        drawGrayDraw(bounds,canvas)
        //2. Draw the color part
        drawColorDraw(bounds,canvas)
    }


    /** * Draw the gray area *@linkGravity can refer to https://www.cnblogs.com/over140/archive/2011/12/14/2287179.html * / you do not understand
    private fun drawGrayDraw(bound: Rect, canvas: Canvas) {
        val rect = Rect()
        Gravity.apply(
            Gravity.LEFT,// Start to matting from the left or right
            bound.width(),  // The width of the target rectangle
            bound.height(), // The height of the target rectangle
            bound,// The rect is pulled out
            rect The rect / / target
        )
        canvas.save() // Save the current canvas
        canvas.clipRect(rect)
        unseleter.draw(canvas)
        canvas.restore()

    }

    /** * draw the colored area */
    private fun drawColorDraw(bounds: Rect, canvas: Canvas) {
        val rect = Rect()
        Gravity.apply(
            Gravity.RIGHT,// Start to matting from the left or right
            bounds.width()/3.// The width of the target rectangle
            bounds.height(), // The height of the target rectangle
            bounds,// The rect is pulled out
            rect The rect / / target
        )
        canvas.save() // Save the current canvas
        canvas.clipRect(rect)
        selecter.draw(canvas)
        canvas.restore()

    }


    override fun setAlpha(alpha: Int){}override fun getOpacity(a): Int  = 0

    override fun setColorFilter(colorFilter: ColorFilter?).{}override fun onBoundsChange(bounds: Rect) {
        super.onBoundsChange(bounds)
        // Reassign after the change
        unseleter.bounds = bounds
        selecter.bounds = bounds
    }

    override fun getIntrinsicHeight(a): Int {
        return Math.max(selecter.intrinsicHeight, unseleter.intrinsicHeight)
    }

    override fun getIntrinsicWidth(a): Int {
        return Math.max(selecter.intrinsicWidth, unseleter.intrinsicWidth)
    }

    override fun onLevelChange(level: Int): Boolean {
        // If the level changes, remind yourself to redraw
        invalidateSelf()
        return super.onLevelChange(level)

    }

}
Copy the code

The running effect is as follows:

This is a static matting + splicing, if we dynamically slide and then calculate the clipping width and height is realized according to the slide to display the corresponding color picture. The demo code.

conclusion

Canvas based API mapping and real example, here is already all over, these apis in fact need not back, AS long AS you know what you need to draw, and then according to the AS automatic prompt, take a look at what then need to fill in the parameters on, parameter meaning is not clear, baidu, don’t draw a circle, a rectangle is baidu, You can’t remember it at all. You must practice more. See you next time!

Thank you for reading, I hope to bring you help!

reference

  • Juejin. Cn/post / 684490…
  • www.cnblogs.com/over140/arc…