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 inherits
ImageView
, you can useImageView
Properties of thesrc
andscaleType
- 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 brushPaint
Set up theshader
andxfermode
To 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 obtain
bitmap
Object,drawable
toBitmapDrawable
To obtainbitmap
object - The statement
BitmapShader
Object that needs to be setbitmap
, and the image extension mode beyond the endpointTileMode
And the picturematrix
- to
paint
Set up theshader
After the usecanvas
thedrawXXX
Method to draw the desired graphics, you must use Settingsshader
thepaint
- Set the border, here
borderPaint
Set the color, fill mode and stroke width to draw normally. ShapeBitmaoshaderImageView
inheritanceAppCompatImageView
Support someImageView
, for examplesrc
.scaleType
And 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 same
Paint
Instance drawing - Set up the
xfermode
Before thedrawBitmap
Is to draw the target diagram, followed by the source diagram - Here,
PorterDuff.Mode
isDST_IN
Value that preserves the intersection of the target and source graphs - call
createSrcBitmap(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