Draw round corners on top left top right bottom left bottom right using Path and RectF in Android

戏子无情 提交于 2019-11-28 22:06:45

There is a Path#addRoundRect() overload that takes a float array of eight values wherein we can specify the x- and y-radius for each of the four corners. These values are in [x, y] pairs, starting at the top-left corner, and going clockwise around the rest. For those corners we want rounded, we set both values of the pair to the radius value, and leave them at zero for those we don't.

As an illustrative example, a simple method that will return a Path that can be used in your snippet:

private Path getPath(float radius, boolean topLeft, boolean topRight,
                     boolean bottomRight, boolean bottomLeft) {

    final Path path = new Path();
    final float[] radii = new float[8];

    if (topLeft) {
        radii[0] = radius;
        radii[1] = radius;
    }

    if (topRight) {
        radii[2] = radius;
        radii[3] = radius;
    }

    if (bottomRight) {
        radii[4] = radius;
        radii[5] = radius;
    }

    if (bottomLeft) {
        radii[6] = radius;
        radii[7] = radius;
    }

    path.addRoundRect(new RectF(0, 0, getWidth(), getHeight()),
                      radii, Path.Direction.CW);

    return path;
}

Per your example description, rounding the top-left and top-right corners:

@Override
protected void onDraw(Canvas canvas) {
    float radius = getContext().getResources().getDimension(R.dimen.round_corner_radius);
    Path path = getPath(radius, true, true, false, false);
    canvas.clipPath(path);
    super.onDraw(canvas);
}

As always, I would recommend keeping the onDraw() method as tight as possible, moving anything that doesn't absolutely have to be there elsewhere. The resource value for the radius, for instance, could be retrieved in the constructor, and kept in a field. Furthermore, the Path could be constructed only when necessary; i.e., when the View's size changes, or when the radius or chosen corners change.

Since I put together a simple custom ImageView to test this, I'll include it here, as it demonstrates the above points. This custom View also offers XML attributes that allow the corner radius and the rounded corners to be set in your layout.

public class RoundishImageView extends ImageView {

    public static final int CORNER_NONE = 0;
    public static final int CORNER_TOP_LEFT = 1;
    public static final int CORNER_TOP_RIGHT = 2;
    public static final int CORNER_BOTTOM_RIGHT = 4;
    public static final int CORNER_BOTTOM_LEFT = 8;
    public static final int CORNER_ALL = 15;

    private static final int[] CORNERS = {CORNER_TOP_LEFT,
                                          CORNER_TOP_RIGHT,
                                          CORNER_BOTTOM_RIGHT,
                                          CORNER_BOTTOM_LEFT};

    private final Path path = new Path();
    private int cornerRadius;
    private int roundedCorners;

    public RoundishImageView(Context context) {
        this(context, null);
    }

    public RoundishImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RoundishImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RoundishImageView);
        cornerRadius = a.getDimensionPixelSize(R.styleable.RoundishImageView_cornerRadius, 0);
        roundedCorners = a.getInt(R.styleable.RoundishImageView_roundedCorners, CORNER_NONE);
        a.recycle();
    }

    public void setCornerRadius(int radius) {
        if (cornerRadius != radius) {
            cornerRadius = radius;
            setPath();
            invalidate();
        }
    }

    public int getCornerRadius() {
        return cornerRadius;
    }

    public void setRoundedCorners(int corners) {
        if (roundedCorners != corners) {
            roundedCorners = corners;
            setPath();
            invalidate();
        }
    }

    public boolean isCornerRounded(int corner) {
        return (roundedCorners & corner) == corner;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (!path.isEmpty()) {
            canvas.clipPath(path);
        }

        super.onDraw(canvas);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        setPath();
    }

    private void setPath() {
        path.rewind();

        if (cornerRadius >= 1f && roundedCorners != CORNER_NONE) {
            final float[] radii = new float[8];

            for (int i = 0; i < 4; i++) {
                if (isCornerRounded(CORNERS[i])) {
                    radii[2 * i] = cornerRadius;
                    radii[2 * i + 1] = cornerRadius;
                }
            }

            path.addRoundRect(new RectF(0, 0, getWidth(), getHeight()),
                              radii, Path.Direction.CW);
        }
    }
}

For the XML attributes to work, the following needs to be in your <resources>, which you can do by putting this file in your project's res/values/ folder, or adding to the one that might already be there.

attrs.xml

<resources>
    <declare-styleable name="RoundishImageView">
        <attr name="cornerRadius" format="dimension" />
        <attr name="roundedCorners">
            <flag name="topLeft" value="1" />
            <flag name="topRight" value="2" />
            <flag name="bottomRight" value="4" />
            <flag name="bottomLeft" value="8" />
            <flag name="all" value="15" />
        </attr>
    </declare-styleable>
</resources>

The cornerRadius is a dimension attribute, and should be specified as a dp or px value. The roundedCorners is a flag attribute, and multiple corners can be chosen using the pipe character, |. For example:

<com.mycompany.myapp.RoundishImageView
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/riv"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:adjustViewBounds="true"
    android:scaleType="fitXY"
    android:src="@drawable/magritte"
    app:cornerRadius="@dimen/round_corner_radius"
    app:roundedCorners="topLeft|topRight" />

I also spent a half-day to solve the problem; the key point here is how to use mPath.arcTo make a corner. The basic knowledge is E direction is 0 degree, then the second param means which degree start; the third param means how many degrees to show.

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.DecelerateInterpolator;

public class RectangleConerView extends View {

private Path mPath;
private Paint mPaint;
private PathMeasure mPathMeasure;
private float mAnimatorValue;
private Path mDst;
private float mLength;

private float left = 300;
private float top = 200;

private float width = 800;
private float height = 300;
private float checkWidth = 100;
private float checkHeight = 60;
private float cornerRadius = 30;

public RectangleConerView(Context context) {
    super(context);
}

public RectangleConerView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}

public RectangleConerView(Context context, AttributeSet attrs) 
{
    super(context, attrs);
    mPathMeasure = new PathMeasure();
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setStrokeWidth(5);
    mPath = new Path();

    mPath.moveTo(left + width, top + checkHeight/2);

    //bottom-right
    mPath.lineTo(left + width, top + height - cornerRadius);
    mPath.arcTo(new RectF(left + width-cornerRadius, top + height - cornerRadius, left + width, top + height), 0, 90); //start degree is E direct, then CW 90 degree, which is the bottom-right corner.

    //bottom-left
    mPath.lineTo(left + cornerRadius, top + height);
    mPath.arcTo(new RectF(left, top + height - cornerRadius, left + cornerRadius, top + height), 90, 90);//start degree is the S, then CW 90 degree, which is the bottom-left corner.

    //top-left
    mPath.lineTo(left, top + cornerRadius);
    mPath.arcTo(new RectF(left, top, left + cornerRadius, top + cornerRadius), 180, 90);//start degree W

    //top-right
    mPath.lineTo(left + width - checkWidth/2, top);


    mPathMeasure.setPath(mPath, false);
    mLength = mPathMeasure.getLength();
    mDst = new Path();

    final ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            mAnimatorValue = (float) valueAnimator.getAnimatedValue();
            invalidate();
        }
    });
    valueAnimator.setDuration(1000);
    valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
    valueAnimator.setInterpolator(new DecelerateInterpolator());

    valueAnimator.start();
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    mDst.reset();
    // 硬件加速的BUG
    mDst.lineTo(0,0);
    float stop = mLength * mAnimatorValue;
    mPathMeasure.getSegment(0, stop, mDst, true);
    canvas.drawPath(mDst, mPaint);
}
}

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!