Android knowledge points – rounded corners

Time:2022-6-2

Let’s start with two sentences

I don’t know how long I haven’t logged in to developepper. I happened to be not too busy today. I came in to have a look, and then read a lot of articles. From what I sent, I can see that it can’t be pure technology. The type of reading is too many. I found several posts on the home page that failed to apply for a developer creator. Let’s take a look at my developer creator. Instead of getting red in the face, I wrote an article worthy of the platform’s recognition.

text

As the UI becomes more and more beautiful, the original straightforward layout style becomes less and less popular. The fillet style, the simplest optimization scheme, is applied in more and more occasions. As for how to show the effect of fillet, compared with many other schemes, I will list some commonly used schemes. Of course, there are also some third-party framework support, which will not be listed here. The main reason is that I personally feel that it is not cost-effective to integrate a library for a simple effect, especially when a large part of the functions in the library are not used. Moreover, the third-party framework contributors have different strengths. Many of them need to import the source code for further optimization. It is too troublesome to analyze. I can’t be so diligent to enumerate those who have always claimed to be lazy. If you are interested, you can study them by yourself.
Take the item in the room type list as an example:

Style reference

Scenario 0

First of all, let’s explain what is called scheme 0. It’s not because programmers have to start counting from 0. It’s simply because these schemes are commonly used. When you encounter similar needs, you can quickly think of them. Here is just a brief introduction. Let’s show some new students that they can solve the problem most conveniently when they meet the corresponding fillet requirements.
Of course, it’s the same. If you have to find a third-party framework, it’s not impossible to implement a parameter control. I haven’t specifically evaluated the performance here, and I can’t say who is good and who is bad. So for all the objections, my response here is: “I’m wrong! You’re right!”

1 fillet background: XML

The rounded background can achieve the effect of the lower part in the above figure

XML realizable part

First, we need to analyze the requirements of this part:

analysis

It can be seen that there are two requirements: 1 White background; 2. gray border. So the corresponding XML only needs to meet these two points.

  1. Create drawable XML file

    Create directory
  2. Name and set the corresponding element to shape

    image.png
  3. Configuration properties
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- White solid background in the middle -- >
    <solid android:color="#FFFFFF" />
    <!-- Gray border, color value and width -- >
    <stroke
        android:width="0.5dp"
        android:color="#999999" />
    <!-- Fillet angle: lower left and lower right -- >
    <corners
        android:bottomLeftRadius="5dp"
        android:bottomRightRadius="5dp" />
</shape>

It should be noted that in the figure, the area with rounded corners is only in the lower half, so only the lower half should be configured here, otherwise the interface between the picture and the background will be quite strange.

Example of fillet setting error

Someone might say that it would be good to draw a white full rounded background and put it below, and then cover it with a rounded image? You are such a smart boy. Of course this plan is feasible. However, it is recommended to understand some over drawing. Although it does not mean that superimposing a picture on the background will have a cliff like impact on the performance, no snowflake is innocent under the avalanche.
Of course, in order to see clearly, the background of the page is set to black, and the rounded corner is adjusted to 20dp. Normally, problems will occur, but it will not be so obvious. It depends on the review efforts of the test team and UI team.

XML correct style

2 pure picture glide

The image framework that I use more now, or that I have worked with for so many years, is glide. When I first started using glide, rounded corners were not supported. I need to add a bunch of configurations and import third-party libraries. Now, there are few lines of code, and it basically belongs to one click configuration.

Glide.with(this).load(R.drawable.bg_scene)
            .apply(
                RequestOptions
                    .bitmapTransform(RoundedCorners(dip2px(this, 20f)))
            ).into(findViewById(R.id.test_img))

Among them, the content in apply is the configuration of fillets. Of course, you can guess by guessing. There is only one angle configured here. There must be a problem at the bottom when it is displayed. Only the two fillets at the top should be configured. Here is a link for you:Glide loading partial fillet image, as a lazy man, he didn’t do the test. If anyone happens to have this requirement, he can test it. Let’s go directly to the next native solution.

3 CardView

As the big boss behind Android, how could Google not know about the general trend of UI style? Therefore, there is also a child in the official control who can implement this function, that is, cardview, which can be directly added to the code:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/item_tv"
    android:layout_width="200dp"
    android:layout_height="wrap_content"
    android:background="#fefefe"
    android:padding="15dp"
    tools:ignore="SpUsage"
    tools:viewBindingIgnore="true">

    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:cardCornerRadius="5dp">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <androidx.appcompat.widget.AppCompatImageView
                android:id="@+id/test_img"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:adjustViewBounds="true"
                android:scaleType="centerInside"
                android:src="@drawable/bg_scene" />

            <View
                android:layout_width="match_parent"
                android:layout_height="80dp"
                android:background="20dp" />
        </LinearLayout>
    </androidx.cardview.widget.CardView>
</FrameLayout>

Then see the effect:

Cardview rendering

As you can see, the two problems above have been solved directly here. There is no need to set rounded corners for the background image and glide. After adding app:cardcornerradius= “5dp” to the cardview, all the problems have been solved. Is it particularly cool? Don’t be happy!

Defect analysis

Although it is said that the function has been realized, if it can be put into scheme 0, it means that there are other better schemes that can be seen within my personal ability level (whether it should be evaluated by the great gods or not). Let’s talk about the shortcomings I see above first.

  1. XML can only be used to configure simple color patches. In the case of images, the layer list can also be used to complete the splicing drawing, but the drawing difficulty is not the same order of magnitude as that in the layout XML (such as the relative relationship between positions)
  2. Glide is mainly aimed at image processing. In addition, there is a problem that the support for network images in loading is OK, but the bitmap before image loading and the default image after image loading are not well supported. Once the image request fails, show a diagram without rounded corners, and the test will definitely trouble you
  3. It seems that the cardview can perfectly avoid the problems of the above two schemes, but it can be seen that when we draw a page, we need to add a child view to the cardview to draw the relative relationship of each view. After all, the parent control of the cardview is FrameLayout, and the onlayout method has not been overridden, so the presentation logic of the child view can be understood as FrameLayout. In this way, in order to achieve an effect, one more layer of view nesting is required. Similarly, when the cardview is configured with rounded corners, the four corners are configured synchronously. If partial configuration is required, it is not supported.

    CardView

So let’s move on to other programs for you.

Scheme 1: custom view fillet

In this scheme, the fillet angle is configured through declare styleable, and then when OnDraw is executed, the fillet effect is achieved by cutting carvas. The code is as follows:

<declare-styleable name="XXXView">
    <!--  General fillet configuration -- >
    <attr format="dimension" name="radius"/>
    <!--  Upper right fillet -- >
    <attr format="dimension" name="rightTopRadius"/>
    <!--  Upper left fillet -- >
    <attr format="dimension" name="leftTopRadius"/>
    <!--  Lower right fillet -- >
    <attr format="dimension" name="rightBottomRadius"/>
    <!--  Lower left fillet -- >
    <attr format="dimension" name="leftBottomRadius"/>
</declare-styleable>

Custom view code

class XXXView @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AppCompatImageView(context, attrs, defStyleAttr) {

    private val mPath = Path()
    private val mPaint = Paint(Paint.ANTI_ALIAS_FLAG)

    /**
     *Top left corner fillet size
     */
    private var leftTopRadius: Float = 0f

    /**
     *Top right corner fillet size
     */
    private var rightTopRadius: Float = 0f

    /**
     *Lower left corner fillet size
     */
    private var leftBottomRadius: Float = 0f

    /**
     *Lower right corner fillet size
     */
    private var rightBottomRadius: Float = 0f

    /**
     *All fillets
     */
    private var radius: Float = 0f

    private val clipRectF = RectF()

    init {
        val obtainStyledAttributes = getContext().obtainStyledAttributes(attrs, R.styleable.XXXView)
        radius= obtainStyledAttributes.getDimension(R.styleable.XXXView_radius, radius)
        if (radius != 0f) {
            //I don't know why I can't lefttopradius=righttopradius=rightbottomradius=leftbottomradius = radius
            leftTopRadius = radius
            rightTopRadius = radius
            rightBottomRadius = radius
            leftBottomRadius = radius
        } else {
            leftTopRadius = obtainStyledAttributes.getDimension(R.styleable.XXXView_leftTopRadius, leftTopRadius)
            rightTopRadius = obtainStyledAttributes.getDimension(R.styleable.XXXView_rightTopRadius, rightTopRadius)
            leftBottomRadius = obtainStyledAttributes.getDimension(R.styleable.XXXView_leftBottomRadius, leftBottomRadius)
            rightBottomRadius = obtainStyledAttributes.getDimension(R.styleable.XXXView_rightBottomRadius, rightBottomRadius)
        }
        obtainStyledAttributes.recycle()
    }

    override fun onDraw(canvas: Canvas?) {
        //1.
        //DX, Dy appear in pairs, control the upper right, lower left, four position fillets
        val array = floatArrayOf(leftTopRadius, leftTopRadius, rightTopRadius, rightTopRadius, rightBottomRadius, rightBottomRadius, leftBottomRadius, leftBottomRadius);
        clipRectF.set(0f, 0f, width.toFloat(), height.toFloat())
        mPath.addRoundRect(clipRectF, array, Path.Direction.CW)
        //2.
        //3.
        canvas?.clipPath(mPath)
        //4.
        super.onDraw(canvas)
    }
}

Custom ViewGroup fillet (OnDraw replaced by dispatchdraw)

override fun dispatchDraw(canvas: Canvas) {
        //1.
        //DX, Dy appear in pairs, control the upper right, lower left, four position fillets
        val array = floatArrayOf(leftTopRadius, leftTopRadius, rightTopRadius, rightTopRadius, rightBottomRadius, rightBottomRadius, leftBottomRadius, leftBottomRadius);
        mPath.addRoundRect(RectF(0f, 0f, width.toFloat(), height.toFloat()), array, Path.Direction.CW)
        //2.
        //3.
        canvas.clipPath(mPath)
        //4.
        super.dispatchDraw(canvas)
    }

Note:
1. canvas clipping needs to be placed before Super
2. frequent UI refresh will lead to frequent canvas clipping in OnDraw, and a black flashing screen will appear

Scheme 2: viewoutlineprovider

In fact, I often use scheme 1. I didn’t find the problem of black flashing screen until I needed to refresh the UI frequently. I didn’t know the viewoutlineprovider until I looked for other schemes. Refer to the following link:Material Design :ViewOutlineProvider

The demo code is as follows:

<declare-styleable name="XXXView">
    <!--  Fillet angle -- >
    <attr format="dimension" name="radius"/>
</declare-styleable>

Fillet view

class XXXView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null
) : FrameLayout(context, attrs) {

    private var radius = 0f

    init {
        val obtainStyledAttributes =
            getContext().obtainStyledAttributes(attrs, R.styleable.XXXView)
        radius =
            obtainStyledAttributes.getDimension(R.styleable.XXXView_radius, radius)
        obtainStyledAttributes.recycle()
        clipToOutline = true
        outlineProvider = object : ViewOutlineProvider() {
            override fun getOutline(view: View?, outline: Outline?) {
                view?.let {
                    outline?.setRoundRect(
                        0 + paddingLeft,
                        0 + paddingTop,
                        it.width - paddingRight,
                        it.height - paddingBottom,
                        radius
                    )
                }
            }
        }
    }

    //Business code logic
    // ...
}

Layout:

<com.example.demo.widget.radius.RadiusView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="20dp"
    android:padding="15dp"
    app:radius="50dp">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <!-- Just look for one picture on the Internet -->
        <androidx.appcompat.widget.AppCompatImageView
            android:id="@+id/test_img"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:adjustViewBounds="true"
            android:scaleType="centerInside"
            android:src="@drawable/bg_scene" />
        <View
            android:layout_width="match_parent"
            android:layout_height="80dp"
            android:background="@android:color/holo_purple" />
    </LinearLayout>
</com.example.demo.widget.radius.RadiusView>

The effect is shown in the figure:

Viewoutlineprovider rendering

Note:
1. the position of fillets in the code will be affected by padding. If you don’t want to be affected, you can remove the four padding parameters in setroundrect. The details will be adjusted according to specific needs
2. due to the limitations of the viewoutlineprovider API, all the angles that can be configured are unified. If you want to configure them separately, this scheme is not applicable

Scheme III tangent coverage

It can be seen that all the above schemes are limited under certain circumstances. Therefore, in the corresponding scenarios, the product experience still requires us to achieve the corresponding fillet effect. When we can’t shut him up, we can only think of other schemes.
The suggestion here is to cover the cut image, that is, let the UI cut a rounded border with the same color as the background color, cover it above the view that needs to be displayed with rounded corners, and pretend to achieve the effect of a rounded corner.

Fillet image:

Fillet image

Note: in order to make the rounded corner image display not too large in the article, the size is reduced to 200. If you need a large size, you can modify it in the image link

Picture resizing

Draw layout:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/item_tv"
    android:layout_width="200dp"
    android:layout_height="200dp"
    tools:viewBindingIgnore="true">

    <androidx.appcompat.widget.AppCompatImageView
        android:id="@+id/test_img"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:adjustViewBounds="true"
        android:scaleType="centerCrop"
        android:src="@drawable/bg_scene" />

    <View
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/mask" />
</FrameLayout>

Fillet effect:

Fillet effect

Advantages: basically compatible with all fillet style requirements of the product
Disadvantages: it needs to draw one more layer of view, which is not elegant enough

summary

The above lists are all the implementation schemes of fillet effect I have thought of at this stage. In fact, in most cases, they are enough in the working scenario. Of course, if you have other schemes, you are welcome to share them and make common progress.
As mentioned here, there are no third-party solutions listed. They are all native implementation solutions. Although it is cool to use a third-party solution, in most cases, it is recommended that you learn about the specific implementation and use fewer unnecessary references.
In addition, I want to say a few words about the last item. In fact, this program is a program that the teacher mentioned when the students asked questions in the online class. As a result, the students felt that this program was fooling him, and finally they broke up unhappily. In fact, to tell the truth, I personally don’t recommend this solution, but in many cases, we can’t quickly achieve some of the desired effects of the product or UI, and have to use some compromise solutions, which is also a compromise and helplessness in our work.
I don’t suggest that you don’t use it at all, but it’s best to think about other good solutions to enrich yourself in your spare time.
Well, I wish you all a happy new year, fishing on the first day of the new year… What else can you do? Let me think, hahaha!