Android custom ImageView to achieve rounded corners

Description of Requirement:

        Implementing an imageView with rounded corners requires a custom view method.

demand analysis:

        We can customize a view, inherit ImageView, and only need to rewrite the onDraw() method of ImageView. Rounded corners means that the four top corners of the control should be cut. Since the normal drawing of the imageview body cannot be affected, it is thought that the canvas can be cropped before executing the super.onDraw() method. In this way, the controls drawn next will only be within the clipping range. Thus fulfilling our needs.

Code:

Implement process steps

1. Define the attributes used by the custom view;

2. Customize the view, make it inherit ImageView, and obtain the custom attribute value in its constructor;

3. Rewrite the onDraw() method of ImageView, use Path to create a path object before executing super.onDraw(), and then use the canvas to cut along the path path;

4. Call super.onDraw() to draw the main content of imageview

1. Define custom view attributes:

The statement in attrs.xml is as follows

<resources>
    <declare-styleable name="RoundImageView">
        <attr name="radius" format="dimension"/>
        <attr name="leftTopRadius" format="dimension"/>
        <attr name="rightTopRadius" format="dimension"/>
        <attr name="leftBottomRadius" format="dimension"/>
        <attr name="rightBottomRadius" format="dimension"/>
    </declare-styleable>
    ...
</resources>

        One thing to note here is that the type of the size-related properties of the control needs to use dimension instead of int or float.

2. Customize the view to get the attribute value & rewrite the onDraw method

class RoundImageView: AppCompatImageView {

    private var rightBottomRadius: Int
    private var leftBottomRadius: Int
    private var rightTopRadius: Int
    private var leftTopRadius: Int
    private var radius: Int =0

    constructor(context: Context): this(context, null)
    constructor(context: Context, attributeSet: AttributeSet?): this(context, attributeSet, 0)
    constructor(context: Context, attributeSet: AttributeSet?, defStyleAttr: Int)
            :super(context, attributeSet, defStyleAttr) {
        var typeArray = context.obtainStyledAttributes(attributeSet, R.styleable.RoundImageView)
        val defaultRadius = 0
        radius = typeArray.getDimensionPixelOffset(R.styleable.RoundImageView_radius, defaultRadius)
        leftTopRadius = typeArray.getDimensionPixelOffset(
            R.styleable.RoundImageView_leftTopRadius,
            defaultRadius
        )
        rightTopRadius = typeArray.getDimensionPixelOffset(
            R.styleable.RoundImageView_rightTopRadius,
            defaultRadius
        )
        leftBottomRadius = typeArray.getDimensionPixelOffset(
            R.styleable.RoundImageView_leftBottomRadius,
            defaultRadius
        )
        rightBottomRadius = typeArray.getDimensionPixelOffset(
            R.styleable.RoundImageView_rightBottomRadius,
            defaultRadius
        )
        if (radius != 0) {
            if (leftTopRadius == 0) {
                leftTopRadius = radius
            }
            if (rightTopRadius == 0) {
                rightTopRadius = radius;
            }
            if (leftBottomRadius == 0) {
                leftBottomRadius = radius;
            }
            if (rightBottomRadius == 0) {
                rightBottomRadius = radius;
            }
        }
        typeArray.recycle()
    }

    override fun onDraw(canvas: Canvas?) {
        // 保证图片宽高大于圆角宽高, 获取圆角的宽高
        // 取横着大的长度
        val maxLeft = Math.max(leftTopRadius, leftBottomRadius)
        val maxRight = Math.max(rightTopRadius, rightBottomRadius)
        val minWidth = maxLeft + maxRight
        // 取竖着大的长度
        val maxTop = Math.max(leftTopRadius, rightTopRadius)
        val maxBottom = Math.max(leftBottomRadius, rightBottomRadius)
        val minHeight = maxTop + maxBottom
        if (width > minWidth && height > minHeight) {
            val path = Path()
            //四个角:右上,右下,左下,左上
            path.moveTo(leftTopRadius.toFloat(), 0F)

            path.lineTo((width - rightTopRadius).toFloat() , 0F)
            path.quadTo(width.toFloat(), 0F, width.toFloat(), rightTopRadius.toFloat())

            path.lineTo(width.toFloat(), (height - rightBottomRadius).toFloat())
            path.quadTo(width.toFloat(), height.toFloat(), (width - rightBottomRadius).toFloat(), height.toFloat())

            path.lineTo(leftBottomRadius.toFloat(), height.toFloat())
            path.quadTo(0F, height.toFloat(), 0F, (height - leftBottomRadius).toFloat())

            path.lineTo(0F, leftTopRadius.toFloat())
            path.quadTo(0F, 0F, leftTopRadius.toFloat(), 0F)

            canvas!!.clipPath(path)

            /*val paint = Paint()
            paint.setColor(Color.BLUE)
            paint.style = Paint.Style.STROKE
            paint.strokeWidth = 2f
            canvas!!.drawPath(path, paint)*/
        }
        super.onDraw(canvas)
    }

}

1. When the radius attribute is declared, and the radiusX attribute in a certain direction is not declared, use the radius value to replace the dropped radiusX;

2. Canvas cutting needs to be executed before super.onDraw(canvas);

3. This program has some restrictions on cutting (if it does not meet your needs, you can remove these restrictions and write your restrictions)

Restriction 1: The width of the custom view control must be greater than (the sum of the maximum radius of the left half of the view and the maximum radius of the right half of the view), that is

Conditions must be met:

Width: view width > (maxLeft + maxRight)

Height: same

//左半边的最大半径
val maxLeft = Math.max(leftTopRadius, leftBottomRadius)
//右半边的最大半径
val maxRight = Math.max(rightTopRadius, rightBottomRadius)

//maxLeft + maxRight 就是view需要满足的最小宽度,同理height

4. Create a Path object to describe the path along which the canvas should be cut. Execute canvas.clipPath(), and then the drawing operation on the canvas will only be drawn in this part of the area.

It is emphasized here that the Bezier curve is used to draw the rounded corners (if you have not touched it, you can refer to the brief introduction of the Bezier curve ).

3. Reference custom view in xml

<LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:gravity="center"
        >
        <com.example.helloworld.views.RoundImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:leftTopRadius="70dp"
            app:leftBottomRadius="60dp"
            app:rightTopRadius="25dp"
            app:rightBottomRadius="60dp"
            android:src="@drawable/maps"
            />
</LinearLayout>

Original image:

Program effect:

 explain:

1. You can see that the pixel value of the drawable image is 419 x 418;

2. The width and height of RoundImageView in xml are set to wrap_content, which means that the width and height of the control are determined according to the size of the content loaded inside the control. In the onDraw method, the width and height of the RoundImageView have been measured, that is, the width and height of the image (the system has already measured the size during onMeasure). The radius values ​​of the four directions set in xml meet the conditions of the program, that is, the width of the view > (maxLeft + maxRight)===>419 > (70 + 60)x2, here it is multiplied by 2 because xml uses dp is used as the unit of length, 1dp=2px of the test machine, and the height is the same . So the four top corners of the RoundImageView will be cropped.

Guess you like

Origin blog.csdn.net/liuqinhou/article/details/125691186