Recommend, my own on a custom view demo

Recommend, my own on a custom view demo

Recommend, my own on a custom view demo

I. Effect introduction

  • Set the four rounded corners to show and hide
  • Control inheritsImageView, you can useImageViewProperties of thesrcandscaleType
  • Set the Angle x and y values, x==y rounded corner, x! = y oval Angle
  • Set the color and width of the border
Images set by SRC will be clipped, and scaleType will take effect with the correct sizeCopy the code

Let’s take a look at the effect

There are two ways to Angle a pictureBitmapShader(Image shader) andPorterDuffXfermode(Rules of image overlay)

By applying the brushPaintSet up theshaderandxfermodeTo achieve the rounded corner effect of the picture.

2. ShapeShaderImageView

BitmapShader implementation of rounded corner images

Fill images or text with Bitmap pixels. Set shder for Paint to use

bitMapPaint.shader = bitmapShader

Custom attributes:

The property name Attribute types meaning The default value
shiv_bg_color color/reference Control background color Color.TRANSPARENT
shiv_border_color color/reference Border color Color.WHITE
shiv_border_width dimension/reference Border width 2dp
shiv_radius dimension/reference The length of a side of a rounded square 5dp
shiv_radius_x dimension/reference The width of a rectangle that is not rounded -1f
shiv_radius_y dimension/reference The length of a rectangle that is not rounded -1f(only valid if x and y are greater than 0)
shiv_top_left boolean Is there an Angle on the upper left true
shiv_top_right boolean Is there an Angle on the top right true
shiv_bottom_left boolean Is there an Angle in the lower left true
shiv_bottom_right boolean Is there an Angle at the bottom right true

Ontouch () rewrite:

Delete spuer.ondraw () and implement your own rounded corner logic

override fun onDraw(canvas: Canvas?) { canvas? . DrawColor (bgColor) // .save() val bitmap = (drawable as BitmapDrawable).bitmap val bitmapShader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) val matrix = setBitmapMatrixAndPath(width.toFloat(), height.toFloat(), bitmap) bitmapShader.setLocalMatrix(matrix) bitMapPaint.shader = bitmapShader canvas? .drawPath(clipPath, bitMapPaint) canvas? .restore() borderPaint.style = Paint.Style.STROKE canvas? .drawPath(borderPath, borderPaint) if (! cornerTopLeftAble) { borderPaint.style = Paint.Style.FILL canvas? .drawRect(suppleRectF, borderPaint) } }Copy the code
  • To obtainbitmapObject,drawabletoBitmapDrawableTo obtainbitmapobject
  • The statementBitmapShaderObject that needs to be setbitmap, and the image extension mode beyond the endpointTileModeAnd the picturematrix
  • topaintSet up theshaderAfter the usecanvasthedrawXXXMethod to draw the desired graphics, you must use Settingsshaderthepaint
  • Set the border, hereborderPaintSet the color, fill mode and stroke width to draw normally.
  • ShapeBitmaoshaderImageViewinheritanceAppCompatImageViewSupport someImageView, for examplesrc.scaleTypeAnd so on.
Note: In the actual test, it was found that the beginning and end of the frame could not be connected, so a square whose side length was half of the width of the frame and color was the color of the frame should be drawn at the upper left of the starting point to supplement the blank part. Repair code and effect:Copy the code
if (! cornerTopLeftAble) { borderPaint.style = Paint.Style.FILL canvas? .drawRect(suppleRectF, borderPaint) }Copy the code
Before the repair After the repair

SetBitmapMatrixAndPath (w,h,bitmap) sets image scaling, panning

According to the enumerated value of ScaleType, the image can be scaled and panned to achieve the ScaleType effect of ImageView.

Private fun setBitmapMatrixAndPath(w: Float, h: Float, bitmap: bitmap): Val scaleX: Float val scaleY: Float val scaleY: Float Float val bh: Float var transX = 0f var transY = 0f if (isSetSize) {when(scaleType) {scaletype. FIT_XY -> {Float var transX = 0f var transY = 0f if (isSetSize) {scaleType. ScaleX = w/bitmap.width scaleY = h/bitmap.height matrix. SetScale (scaleX, scaleY) setPath(borderWidth, BorderWidth, W-BorderWidth, H-borderWidth)} scaletype. FIT_CENTER -> {// The fitCenter image scales to the width or height of the View (take the minimum width and height), Center shows val Scale: Float if (w < h) { scale = w / bitmap.width transY = (h - bitmap.height * scale) / 2 } else { scale = h / bitmap.height transX = (w - bitmap.width * scale) / 2 } matrix.setScale(scale, scale) matrix.postTranslate(transX, transY) bw = bitmap.width * scale bh = bitmap.height * scale val left = if (transX < 0) borderWidth else transX + borderWidth val top = if (transY < 0) borderWidth else transY + borderWidth val right = if (transX < 0) w - borderWidth else transX + bw -borderWidth val bottom = if (transY < 0) h - borderWidth else transY + bh - borderWidth setPath(left, Top, right, bottom)} scaletype. FIT_START -> {// Scale the image to the width or height of the View (take the minimum width and height), Val scale = if (w < h) {w/bitmap.width} else {h/bitmap.height} matrix.setScale(scale, scale) bw = bitmap.width * scale bh = bitmap.height * scale val left = borderWidth val top = borderWidth val right = if (w < bw) w - borderWidth else bw - borderWidth val bottom = if (h < bh) h - borderWidth else bh - borderWidth SetPath (left, top, right, bottom)} scaletype. FIT_END -> {// Scale the image to the width or height of the View (take the minimum width and height) and then to the bottom or right to display val scale: Float if (w < h) { scale = w / bitmap.width transY = h - bitmap.height * scale } else { scale = h / bitmap.height transX  = w - bitmap.width * scale } matrix.setScale(scale, scale) matrix.postTranslate(transX, transY) bw = bitmap.width * scale bh = bitmap.height * scale val left = if (transX < 0) borderWidth else transX + borderWidth val top = if (transY < 0) borderWidth else transY + borderWidth val right = if (transX < 0) w - borderWidth else transX + bw - borderWidth val bottom = if (transY < 0) h - borderWidth else transY + bh - borderWidth setPath(left, } top, right, bottom)} scaletype. CENTER -> { TransX = (w-bitmap.width) / 2 transY = (h-bitmap.height) / 2 matrix. PostTranslate (transX, transY) setPath(if (transX < 0) borderWidth else transX + borderWidth, if (transY < 0) borderWidth else transY + borderWidth, if (transX < 0) w - borderWidth else transX + bitmap.width - borderWidth, if (transY < 0) h - borderWidth else transY + bitmap.height - borderWidth) } ScaleType.CENTER_INSIDE -> { // CenterInside's goal is to display the original image completely, so scale the original image to show val Scale in the center: Float if (w < h) { scale = w / bitmap.width transY = (h - bitmap.height * scale) / 2 } else { scale = h / bitmap.height transX = (w - bitmap.width * scale) / 2 } matrix.setScale(scale, scale) matrix.postTranslate(transX, transY) bw = bitmap.width * scale bh = bitmap.height * scale val left = if (transX < 0) borderWidth else transX + borderWidth val top = if (transY < 0) borderWidth else transY + borderWidth val right = if (transX < 0) w - borderWidth else transX + bw - borderWidth val bottom = if (transY < 0) h - borderWidth else transY + bh -borderWidth setPath(left, Top, right, bottom)} scaletype. CENTER_CROP -> {// centerCrop aims to fill the ImageView, so scale the original image to show val scale in the center: Float if (w > h) { scale = w / bitmap.width transY = (h - bitmap.height * scale) / 2 } else { scale = h / bitmap.height transX = (w -bitmap.width * scale) / 2 } matrix.setScale(scale, scale) matrix.postTranslate(transX, transY) bw = bitmap.width * scale bh = bitmap.height * scale val left = if (transX < 0) borderWidth else transX + borderWidth val top = if (transY < 0) borderWidth else transY + borderWidth val right = if (transX < 0) w - borderWidth else transX + bw - borderWidth val bottom = if (transY < 0) h - borderWidth else transY + bh -borderWidth setPath(left, Top, right, bottom)} scaletype. MATRIX -> {// Draw from the top left corner of the original size, Bw = if (w < bitmap.width) w else bitmap.width.tofloat () bh = if (h < bitmap.height) h else bitmap.height.toFloat() setPath(borderWidth, borderWidth, bw - borderWidth, bh - borderWidth) } else -> {} } } else { scaleX = w / bitmap.width scaleY = h / bitmap.height matrix.setScale(scaleX, scaleY) setPath(borderWidth, borderWidth, w - borderWidth, h -borderWidth) } return matrix }Copy the code

ScaleType:

ScaleType meaning
FIT_XY Fill the entire view regardless of image size
FIT_CENTER The image is scaled to the width or height of the View (whichever is the minimum width and height) and centered
FIT_START The image is scaled to the width or height of the View (whichever is the minimum width and height), top or left
FIT_END The image is scaled to the width or height of the View (whichever is the minimum width and height) and displayed bottom or right
CENTER According to the original size of the picture, center display, redundant parts cropped
CENTER_INSIDE The goal is to display the original image completely, so scale the original image and center it
CENTER_CROP The goal is to fill the view, so scale the original image and center it
MATRIX Draw from the upper left corner of the original size, cut out the excess

SetPath (left, right, top, bottom) sets the clipping path and border path

SetPath mainly combines the clipping path and border path according to the value of the clipping rectangle passed in.

Border quadrangle = the size of the borderwidth expanded outward by the four edges of the clipping frame;Copy the code
Border Angle value = Clipped box Angle value + BorderWidth / 2Copy the code
/** * set the clipping path and border path * @param left clipping left * @param top clipping top * @param right Clipping right * @param bottom Clipping bottom */ private fun setPath(left: Float, top: Float, right: Float, bottom: Float) { clipPath.reset() borderPath.reset() val w = right - left val h = bottom - top setRadius(w, h) val borderLeft = left - borderWidth / 2 val borderTop = top - borderWidth / 2 val borderRight = right + borderWidth /  2 val borderBottom = bottom + borderWidth / 2 val borderRadiusX = radiusX + borderWidth / 2 val borderRadiusY = radiusY  + borderWidth / 2 val bw = borderRight - borderLeft val bh = borderBottom - borderTop suppleRectF.left = borderLeft - borderWidth / 2 suppleRectF.top = borderTop - borderWidth / 2 suppleRectF.right = borderLeft suppleRectF.bottom = BorderTop // Rectangles with rounded or elliptical corners val topLeftRectF = RectF() val topRightRectF = RectF() val bottomLeftRectF = RectF() val BottomRightRectF = RectF() if (radiusX <= 0 && radiusY <= 0) {// No rounded corners clipPath. Path.Direction.CW) borderPath.addRect(borderLeft, borderTop, borderRight, borderBottom, Path.direction.cw)} else {// Have rounded corners if (cornerTopLeftAble) {// Clipout // top left topleftRectf. left = left topleftRectf. top = top  topLeftRectF.right = left + radiusX * 2 topLeftRectF.bottom = top + radiusY * 2 clipPath.addArc(topLeftRectF, 180f, Topleftrectf. left = borderLeft topleftRectf. top = borderTop TopleFTRectf. right = borderLeft + borderRadiusX * 2 topLeftRectF.bottom = borderTop + borderRadiusY * 2 borderPath.moveTo(borderLeft, borderTop + borderRadiusY) borderPath.addArc(topLeftRectF, 180f, 90f) borderPath.moveTo(borderLeft + borderRadiusX, borderTop) } else { clipPath.moveTo(left, top) borderPath.moveTo(borderLeft, borderTop) } clipPath.lineTo(if (cornerTopRightAble) right - radiusX else right , top) if (bw ! = borderRadiusX * 2) { borderPath.lineTo(if (cornerTopRightAble) borderRight - borderRadiusX else borderRight , BorderTop)} if (cornerTopRightAble) {// Top rightRectf. left = right-Radiusx * 2 ToprightRectf. top = top topRightRectF.right = right topRightRectF.bottom = top + radiusY * 2 clipPath.addArc(topRightRectF, 270f, 90f) // Top right corner ToprightRectf. left = borderRight - borderRadiusX * 2 ToprightRectf. top = borderTop ToprightRectf. right = borderRight topRightRectF.bottom = borderTop + borderRadiusY * 2 borderPath.addArc(topRightRectF, 270f, 90f) borderPath.moveTo(borderRight, borderTop + borderRadiusY) } clipPath.lineTo(right, if (cornerBottomRightAble) bottom - radiusY else bottom) if (bh ! = borderRadiusY * 2) { borderPath.lineTo(borderRight, If (cornerBottomRightAble) borderBottom - borderRadiusY else borderBottom)} if (cornerBottomRightAble) {// bottom right corner bottomRightRectF.left = right - radiusX * 2 bottomRightRectF.top = bottom - radiusY * 2 bottomRightRectF.right = right bottomRightRectF.bottom = bottom clipPath.addArc(bottomRightRectF, 0f, Bottomrightrectf. left = borderRight-borderRadiusx * 2 BottomRightRectf. top = Borderbottom-borderRadiusy * 2 bottomRightRectF.right = borderRight bottomRightRectF.bottom = borderBottom borderPath.addArc(bottomRightRectF, 0f, 90f) borderPath.moveTo(borderRight - borderRadiusX ,borderBottom) } clipPath.lineTo(if (cornerBottomLeftAble) left + radiusX else left, bottom) if (bw ! = borderRadiusX * 2) { borderPath.lineTo(if (cornerBottomLeftAble) borderLeft + borderRadiusX else borderLeft, BorderBottom)} if (bottom Leftable) {bottom LeftRectf. left = left bottom Leftrectf. top = bottom-radiusy * 2 bottomLeftRectF.right = left + radiusX * 2 bottomLeftRectF.bottom = bottom clipPath.addArc(bottomLeftRectF, 90f, Bottomleftrectf. left = borderLeft BottomLeftRectF. top = borderBottom - borderRadiusY * 2 bottomLeftRectF.right = borderLeft + borderRadiusX * 2 bottomLeftRectF.bottom = borderBottom borderPath.addArc(bottomLeftRectF, 90f, 90f) borderPath.moveTo(borderLeft, borderBottom - borderRadiusY) } clipPath.lineTo(left, if (cornerTopLeftAble) top + radiusY else top) if (cornerTopLeftAble) { clipPath.lineTo(left, top + radiusY) } if (cornerTopRightAble) { clipPath.lineTo(right - radiusX, top) } if (cornerBottomRightAble) { clipPath.lineTo(right, bottom - radiusY) } if (cornerBottomLeftAble) { clipPath.lineTo(left + radiusX, bottom) } if (bh ! = borderRadiusY * 2) { borderPath.lineTo(borderLeft, if (cornerTopLeftAble) borderTop + borderRadiusY else borderTop) } } }Copy the code

SetRadius (w,h) Set the rounded Angle value

/** * private fun setRadius(w: Float, h: Float) Float) { if (radiusX < 0 || radiusY < 0) { if (cornerRadius < 0) { cornerRadius = 0f } radiusX = cornerRadius radiusY = cornerRadius } if (radiusX > w / 2) { radiusX = w / 2 } if (radiusY > h / 2) { radiusY = h / 2 } }Copy the code

If Angle x, y is set and greater than or equal to 0, otherwise use rounded corner values (>=0)

Source code portal

3. ShapeXfermodeImageView

PorterDuffXfermode implementation of rounded corner pictures

PorterDuffXfermode specifies the overlay rule for the target image and the source image. Set xferMode to Paint. Note that PorterDuffXfermode specifies the Paint rule.

bitMapPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN)

Note: The clipping path and border Settings are the same for xferMode and BitmapShader Angle images. The difference is that XferMode needs to use the clipping path to generate the source graph, and the set image is used as the target graph for drawing, while BitMapShader uses the clipping path directly.Copy the code

ShapeXfermodeImageView custom attributes:

The property name Attribute types meaning The default value
sxiv_bg_color color/reference Control background color Color.TRANSPARENT
sxiv_border_color color/reference Border color Color.WHITE
sxiv_border_width dimension/reference Border width 2dp
sxiv_radius dimension/reference The length of a side of a rounded square 5dp
sxiv_radius_x dimension/reference The width of a rectangle that is not rounded -1f
sxiv_radius_y dimension/reference The length of a rectangle that is not rounded -1f(only valid if x and y are greater than 0)
sxiv_top_left boolean Is there an Angle on the upper left true
sxiv_top_right boolean Is there an Angle on the top right true
sxiv_bottom_left boolean Is there an Angle in the lower left true
sxiv_bottom_right boolean Is there an Angle at the bottom right true

Ontouch () rewrite:

Delete spuer.ondraw () and implement your own rounded corner logic

override fun onDraw(canvas: Canvas?) { canvas? DrawColor (bgColor) // BitmapShader implementation val saved = canvas? .saveLayer(null, null, Canvas.ALL_SAVE_FLAG) val dstBitmap = (drawable as BitmapDrawable).bitmap val matrix = setBitmapMatrixAndPath(width.toFloat(), height.toFloat(), dstBitmap) val srcBitmap = createSrcBitmap(width, height) canvas? .drawBitmap(dstBitmap, matrix, bitMapPaint) bitMapPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN) canvas? .drawBitmap(srcBitmap, 0f, 0f, bitMapPaint) bitMapPaint.xfermode = null canvas? .restoreToCount(saved? : 0) borderPaint.style = Paint.Style.STROKE canvas? .drawPath(borderPath, borderPaint) if (! cornerTopLeftAble) { borderPaint.style = Paint.Style.FILL canvas? .drawRect(suppleRectF, borderPaint) } }Copy the code
  • Use the samePaintInstance drawing
  • Set up thexfermodeBefore thedrawBitmapIs to draw the target diagram, followed by the source diagram
  • Here,PorterDuff.ModeisDST_INValue that preserves the intersection of the target and source graphs
  • callcreateSrcBitmap(w,h)And draw the source graph according to the clipping path

Porterduff.modej

createSrcBitmap(w,h)

Draw the source diagram according to the clipping path

/** * Private fun createSrcBitmap(w: Int, h: Int): Bitmap { val srcBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888) srcBitmap.eraseColor(Color.TRANSPARENT) val canvas = Canvas(srcBitmap) val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = Color.WHITE style = Paint.Style.FILL } canvas.drawPath(clipPath, paint) return srcBitmap }Copy the code

The setBitmapMatrixAndPath(), setPath(), setRadius() and ShpaeShaderImageView methods are the same

Source code portal

This is how BitmapShader and Xfermode implement Angle images

The end of the

Learn about BitmapShader and Xfermodede, download custom View details to watch APK