1. Introduction

Before, I share in making an open source library: https://github.com/GcsSloop/rclayout, the main purpose of this library is rounded demand, rapid implementation of Android, for example.

When I shared this library, I thought it might be useful, but I didn’t realize that there were over 800 stars. It seems that many people like me are struggling with the need for rounded corners.

Rounded corners are one of the more common requirements, most often applied to images, so you can find a number of custom rounded imageViews, and some of the most popular image-loading frameworks support rounded corners, such as Fresco and Glide.

In addition to pictures, another common is the rounded background, such as TextView background or Button background, for the background of the rounded corner is also easy to achieve, generally use a picture or write a shape, for example:

<? The XML version = "1.0" encoding = "utf-8"? > <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="#66CCFF" /> <corners android:topLeftRadius="6dp" android:topRightRadius="6dp" android:bottomRightRadius="6dp" android:bottomLeftRadius="6dp"/> </shape>Copy the code

All of the above are common requirements that are easy to implement, but there is also a slightly special common requirement that looks like this:

An entry composed of multiple controls, including a corner icon in the upper left corner, text, text background, and picture.

As you can see from the image above, the image has rounded corners, but the corners and text background are still square, which is obviously not what we want.

My previous approach to this combination of controls looks like this:

Corner label: Recut the design into rounded images or display it in an ImageView that supports rounded corners.

Text background: Make an image with rounded corners, or write a Shape with rounded corners at the bottom.

There is no problem in this display, the only trouble is that when changing the demand, if you need to adjust the size of the rounded corner, you need to adjust three places: the rounded corner of the picture, the rounded corner of the corner mark, and the rounded corner of the text background. If an adjustment is forgotten in one place, the rounded corners will not align.

Because of this, I came up with the idea of developing a rounded layout that wraps them around so that not only do I not have to deal with the corners and text backgrounds, but I can change the requirements in only one place.

2. Rounded corner layout principle

The so-called rounded corner layout, or square in nature, is just to let the part outside the rounded corner is not displayed, simply speaking, is to cut the canvas, can also be understood as setting a mask, so that the content outside the target area is not displayed.

2.1 clipPath

In the initial design, the method of Canvas clipPath was used. This method is simple and fast to implement, and the principle is as follows:

2.1.1 Constructing a Path with rounded Corners

Initialize fillet information and Path:

Top left, top right, bottom-right, bottom-leftPrivate Float [] radii = new float[8]; private Path mPath; / / 2. Get rounded corners of information through the custom properties (in the upper left corner, for example) TypedArray ta = context. ObtainStyledAttributes (attrs, R.s tyleable. RCRelativeLayout); mRoundCorner = ta.getDimensionPixelSize(R.styleable.RCRelativeLayout_round_corner, 0); radii[0] = mRoundCorner; radii[1] = mRoundCorner; // 3. Create an empty PathmPath = new Path();Copy the code

Create a rounded Path based on the View size in the onSizeChanged method, and pay attention to handling the padding value here.

@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);
    mRectF.left = getPaddingLeft();
    mRectF.top = getPaddingTop();
    mRectF.right = w - getPaddingRight();
    mRectF.bottom = h - getPaddingBottom();
    mPath.addRoundRect(mRectF, radii, Path.Direction.CW);
}Copy the code

2.1.2 Tailoring the canvas

Use the Canvas clipPath method directly for clipping.

Overrideprotected Boolean drawChild(Canvas, View child, long drawingTime) {// Draw Canvas. ClipPath (mPath); return super.drawChild(canvas, child, drawingTime); }Copy the code

This creates a rounded layout.

2.2 setXfermode

Because clipPath doesn’t support anti-aliasing, the edges look rough on some lower-resolution devices, so we switched to using brush patterns to crop the content area.

About the brush model can refer to: http://www.gcssloop.com/customview/Color

2.2.1 Paint

Create a brush and set its mode:

private Paint mPaint; // new Paint(); mPaint.setColor(Color.WHITE); mPaint.setAntiAlias(true); // Draw with mPaint. SetStyle (paint.style.fill); // The blending mode is DST_IN, that is, only the part of the intersection between the currently drawn region and the background region is displayed, and only the background content is displayed. mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));Copy the code

2.2.2 draw

Create the same content with the rounded Path, but this time move the drawing part inside dispatchDraw. You can also place it inside the drawChild, as long as you pay attention to the drawing order.

Because the brush mode is SDT_IN, the intersection between the original content area and the rounded Path area is displayed, and only the original content is displayed

@Override protected void dispatchDraw(Canvas canvas) { canvas.saveLayer(new RectF(0, 0, canvas.getWidth(), canvas.getHeight()), null, Canvas.ALL_SAVE_FLAG); // Draw the child control super.dispatchDraw(canvas); // Draw canvas. DrawPath (mPath, mPaint) with rounded corners; canvas.restore(); }Copy the code

2.3 Circular

Knowing that the original drawing ideal supports circles, it is easy to change the Path with rounded rectangles to the Path with circles.

To support circles, I define a roundAsCircle property that adds a circle to the Path whenever it is detected, otherwise a rounded rectangle.

In support round when there is a need to pay attention to the pit, is to control the aspect ratio of inconsistent cases, because it is calculated according to the edge of the shortest, so on the aspect ratio of inconsistent cases, directly to add circular Path, the Path is unable to fill the canvas, may occur when drawing circle still have content is mapped, So two moveTo operations are used to fill the canvas with Path (comments in the code below).

@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); RectF areas = new RectF(); areas.left = getPaddingLeft(); areas.top = getPaddingTop(); areas.right = w - getPaddingRight(); areas.bottom = h - getPaddingBottom(); mClipPath.reset(); if (mRoundAsCircle) { float d = areas.width() >= areas.height() ? areas.height() : areas.width(); float r = d / 2; PointF center = new PointF(w / 2, h / 2); mClipPath.addCircle(center.x, center.y, r, Path.Direction.CW); mClipPath.moveTo(0, 0); MClipPath. MoveTo (w, h); } else { mClipPath.addRoundRect(areas, radii, Path.Direction.CW); } Region clip = new Region((int) areas.left, (int) areas.top, (int) areas.right, (int) areas.bottom); mAreaRegion.setPath(mClipPath, clip); }Copy the code

2.4 Support stroke

Support for stroke is even simpler, which is the basic draw operation, as follows:

@Override protected void dispatchDraw(Canvas canvas) { canvas.saveLayer(new RectF(0, 0, canvas.getWidth(), canvas.getHeight()), null, Canvas .ALL_SAVE_FLAG); super.dispatchDraw(canvas); If (mStrokeWidth > 0) {mPaint. SetXfermode (null); mPaint.setStrokeWidth(mStrokeWidth * 2); mPaint.setColor(mStrokeColor); mPaint.setStyle(Paint.Style.STROKE); canvas.drawPath(mStrokePath, mPaint); } // Clipping mPaint. SetXfermode (new PorterDuffXfermode(porterduff.mode.dst_in)); mPaint.setStrokeWidth(0); mPaint.setStyle(Paint.Style.FILL); canvas.drawPath(mClipPath, mPaint); canvas.restore(); }Copy the code

2.5 Limit click areas

Although the display of rounded corners and circles has been realized above, the part that has not been drawn can still be clicked, so it is necessary to limit the clicking area and do not respond to the part beyond the display area.

This principle is not complicated, mainly using the event distribution mechanism and Region contains method.

2.5.1 Obtaining clickable areas

// Define Region, that is, content Region private Region mAreaRegion; mAreaRegion = new Region(); // Create a Region based on the Path of the content Region. Region = New Region((int) areas. Left, (int) areas. Top, (int) areas. mAreaRegion.setPath(mStrokePath, clip);Copy the code

2.5.2 Events outside the zone are not processed

Use the CONTAINS method to determine whether the location of the event is in a region. If not, return false, indicating no processing.

@Override public boolean dispatchTouchEvent(MotionEvent ev) { if (! mAreaRegion.contains((int) ev.getX(), (int) ev.getY())) { return false; } return super.dispatchTouchEvent(ev); }Copy the code

That concludes the core of the universal rounded corner layout.

3. The conclusion

The principle of universal rounded corner layout is not complicated, and the code implementation is also very simple, interested in Github can take a look at the source code, the core code is only about 100 lines.

RCLayout: https://github.com/GcsSloop/rclayout

Copy the link above or click to read the original article to view the open source library.

About the author

GcsSloop, a 2.5 dimensional mage.