Differences of Custom View Shapes before and after Pie and Examples of Round Corner Implementation

Google updated the Draw Path implementation on the Android Pie system, so compatibility issues arise when creating custom graphics using Xfermode.

Example

The detailed code can be viewed in Example Project.

In this brief introduction, let’s say we need to implement a custom graphic with a bottom corner, as shown below

xfermode with compatible pie

If it is not compatible with Android Pie, its system above Pie is likely to show the following

xfermode with incompatible pie

Here is the implementation of PorterDuffXfermode(PorterDuff.Mode.DST_IN), the principle is also very simple, we regard the original graphics as DST, the custom Path as SRC, then DST_IN is in The original graphic is displayed within the range of the custom Path, and the code is used to express the following

1
2
3
4
val saveCount: Int = canvas?.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), null) ?: 0
super.onDraw(canvas)
paint = getDstInPaint()
canvas?.drawPath(getCustomPath(), paint)

Compatibility issue

In the system below Android Pie, when drawing the Path, the system will first draw a rectangle that can accommodate the Path, and then fill the part outside the Path with transparent pixels. On the Pie system, the system will only draw the graphics in the Path range. This is faster.

However, this has led to compatibility issues with the method of drawing custom graphics used above. In the [issue tracker] (https://issuetracker.google.com/issues/111819103) of the question, the official respondent suggested Three solutions, quoted below

  • Replace the ImageView with a BitmapShader, which is fast (each pixel is drawn once) and more portable way as BitmapShader is supported in API level 1.

    Paint paint = new Paint();

    paint.setColor(Color.BLACK);

    paint.setStyle(Paint.Style.FILL);

    Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.spiderman);

    Shader shader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

    paint.setShader(shader);

    Path corners = new Path();

    corners.addRoundRect(bounds, radii, Path.Direction.CW);

    canvas.drawPath(corners, paint);

  • Draw an inverse path with SRC_OVER on top of the ImageView. This works if the background is a solid color and it is slower because some pixels are drawn twice.

In the sample code, a third scheme is adopted, that is, in the Android Pie system, the inverse path is drawn using transparent pixels.

The first scheme uses ViewOutlineProvider, which is suitable for drawing regular graphics, such as rectangular rectangles, rounded rectangles, and circles. When you need to draw custom graphics, you must ensure that Path is a Complex Path, and the judgment method is Native. Function, so when the shape of the custom graphic is more complicated, this scheme is not recommended, and the common requirements such as rounding are recommended to use ViewOutlineProvider. Its usage is also very simple, as shown below

1
2
3
4
5
6
7
8
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
this.outlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View?, outline: Outline?) {
outline?.setRoundRect(0, 0, width, height, roundCorner)
}
}
this.clipToOutline = true
}

Called in the custom view’s init function, where clipToOutline is used to control whether the outline is cropped or not.

Reference

  1. Issues with XFermodes