Android 佈局圓角方案總結

一.遇到的問題

    最近在開發中會遇到Android佈局切圓角的需求,大多數是對一個layout佈局切下圓角。這裏和圖片切圓角有一些雷同的地方,可以相互借鑑,但是也不不全一樣。圖片切圓角的一些總結和實踐準備下次有空再寫出來。

    假設我們要對一種LinearLayout佈局切圓角,不知道你能想出來哪些辦法。我這裏先提供下我的思路,親自實踐過的主要包括下面五種:

   1.利用xml背景文件配置shape屬性實現切圓角

   2.利用GradientDrawable實現切圓角

   3.利用clipPath實現切圓角

   4.利用CardView實現切圓角

    5.利用ViewOutlineProvider實現切圓角

下面分別對這幾種方式進行實踐與優缺點分析,原理講解較少,感興趣可以自己再深入瞭解下。

 

二.利用xml背景文件配置shape屬性實現

2.1方案實現

    這種方式相比大家都比較熟悉了,就是在drawable文件夾下新建一個App Action XML File,然後作爲background屬性添加到Layout XML File中。

    如果要生成四個角都爲10dp圓角的形狀我們可以在drawable文件夾下新建如下shape_10dp_corners.xml文件

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <corners android:radius="10dp" />
    <solid android:color="@color/colorAccent" />
</shape>

    如果僅要生成上方兩個10dp圓角的形狀我們可以在drawable文件夾下新建如下shape_10dp_top_corners.xml文件

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="@color/colorPrimary"/>
    <corners
        android:topLeftRadius="10dp"
        android:topRightRadius="10dp" />
</shape>

    如果僅要生成上方兩個10dp圓角的形狀我們可以在drawable文件夾下新建如下shape_10dp_bottom_right_corners.xml文件

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <corners android:bottomRightRadius="10dp"/>
    <solid android:color="@color/colorAccent"/>
</shape>

    然後在XmlShapeActivity的佈局XML文件中爲對應的三個LinearLayout分別設置background屬性如下

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".xmlshape.XmlShapeActivity">

    <LinearLayout
        android:id="@+id/lly1"
        android:layout_width="0dp"
        android:layout_height="100dp"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:background="@drawable/shape_10dp_corners"
        android:orientation="vertical"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="四個圓角10dp"
            android:textSize="24sp"
            android:textColor="#FFFFFF" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/lly2"
        android:layout_width="0dp"
        android:layout_height="100dp"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:background="@drawable/shape_10dp_top_corners"
        android:orientation="vertical"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/lly1" >
        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="上方圓角10dp"
            android:textSize="24sp"
            android:textColor="#FFFFFF"/>
    </LinearLayout>

    <LinearLayout
        android:id="@+id/lly3"
        android:layout_width="0dp"
        android:layout_height="100dp"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:background="@drawable/shape_10dp_bottom_right_corners"
        android:orientation="vertical"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/lly2" >
        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="右下圓角10dp"
            android:textSize="24sp"
            android:textColor="#FFFFFF"/>
    </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

    然後勉爲其難地貼一下打醬油的XmlShapeActivity代碼

package com.openld.roundcornerdemmo.xmlshape;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

import com.openld.roundcornerdemmo.R;

public class XmlShapeActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_xml_shape);
    }
}

2.1運行效果

2.3優缺點

優點:

1.最簡單

2.四個圓角可以單獨定製,也可以統一處理

3.還可以一併設置背景顏色等屬性

4.圓角無鋸齒

 

缺點:

1.圓角屬性是提前確定的並卸載客戶端本地的drawable中,如果是動態下發再去配置的話這種方式無法支持。

2.如果子View長寬都match_parent並且設置一個有色background屬性的話,圓角就消失了。

 

三.利用GradientDrawable實現切圓角

3.1 方案實現

    有沒有想過動態地在Java代碼中爲layout設置圓角,那就想到了GradientDrawable,其實是和第一種XML方式呼應的,只不過沒有一個地方讓你動態去配置XML,那就用GradientDrawable吧,XML shape中能設置的屬性這裏也能哦!

    這裏先貼一下GradientDrawableActivity的佈局XML

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".gradientdrawable.GradientDrawableActivity">

    <LinearLayout
        android:id="@+id/lly1"
        android:layout_width="0dp"
        android:layout_height="100dp"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:orientation="vertical"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="四個圓角10dp"
            android:textSize="24sp" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/lly2"
        android:layout_width="0dp"
        android:layout_height="100dp"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:orientation="vertical"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/lly1">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="左邊兩個10dp圓角"
            android:textSize="24sp" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/lly3"
        android:layout_width="0dp"
        android:layout_height="100dp"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:orientation="vertical"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/lly2">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="左上角10dp圓角"
            android:textSize="24sp" />
    </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

    再貼一下GradientDrawableActivity的代碼

package com.openld.roundcornerdemmo.gradientdrawable;

import android.graphics.drawable.GradientDrawable;
import android.os.Bundle;
import android.widget.LinearLayout;

import androidx.appcompat.app.AppCompatActivity;

import com.openld.roundcornerdemmo.R;
import com.openld.roundcornerdemmo.utils.DisplayUtils;

public class GradientDrawableActivity extends AppCompatActivity {
    private LinearLayout mLly1;

    private LinearLayout mLly2;

    private LinearLayout mLly3;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_gradient_drawable);

        initWidgets();
    }

    private void initWidgets() {
        mLly1 = findViewById(R.id.lly1);
        GradientDrawable gradientDrawable1 = new GradientDrawable();
        gradientDrawable1.setShape(GradientDrawable.RECTANGLE);
        gradientDrawable1.setCornerRadius(DisplayUtils.dp2px(this, 10F));
        gradientDrawable1.setColor(getResources().getColor(R.color.colorPrimary));
        mLly1.setBackground(gradientDrawable1);

        mLly2 = findViewById(R.id.lly2);
        GradientDrawable gradientDrawable2 = new GradientDrawable();
        gradientDrawable2.setShape(GradientDrawable.RECTANGLE);
        gradientDrawable2.setColor(getResources().getColor(R.color.colorAccent));
        float[] radii = new float[]{
                DisplayUtils.dp2px(this, 10F), DisplayUtils.dp2px(this, 10F),
                0F, 0F,
                0F, 0F,
                DisplayUtils.dp2px(this, 10F), DisplayUtils.dp2px(this, 10F)
        };
        gradientDrawable2.setCornerRadii(radii);
        mLly2.setBackground(gradientDrawable2);

        mLly3 = findViewById(R.id.lly3);
        GradientDrawable gradientDrawable3 = new GradientDrawable();
        gradientDrawable3.setShape(GradientDrawable.RECTANGLE);
        gradientDrawable3.setColor(getResources().getColor(R.color.colorPrimary));
        float[] radii1 = new float[]{
                DisplayUtils.dp2px(this, 10F), DisplayUtils.dp2px(this, 10F),
                0F, 0F,
                0F, 0F,
                0F, 0F
        };
        gradientDrawable3.setCornerRadii(radii1);
        mLly3.setBackground(gradientDrawable3);
    }
}

    核心代碼都在initWidgets()方法中,裏面實例化一個GradientDrawable對象,設置一下對應的屬性。setCornerRadii()或者se'tCornerRadius()方法即爲設置圓角,具體可以看下API,這裏不再贅述。

 

3.2 運行效果

 

3.3 優缺點

優點:

1.支持動態配置啊

2.四個圓角可以單獨定製,也可以統一處理

3.還可以一併設置背景顏色等屬性

4.圓角無鋸齒

 

缺點

1.如果子View長寬都match_parent並且設置一個有色background屬性的話,圓角就消失了。

 

四. 利用clipPath實現切圓角

4.1 方案實現

    繼承現有的佈局,在draw()方法中使用clipPath()方法切割你想要的形狀,只要切割出一個圓角矩形即可。

比如新建一個繼承LinearLayout的CornersLinearLayout

package com.openld.roundcornerdemmo.clippath;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.widget.LinearLayout;

import androidx.annotation.Nullable;

import com.openld.roundcornerdemmo.R;

/**
 * author: lllddd
 * created on: 2020/6/9 10:55
 * description:
 */
public class CornersLinearLayout extends LinearLayout {
    private Context mContext;

    private float mCorners;
    private float mLeftTopCorner;
    private float mRightTopCorner;
    private float mLeftBottomCorner;
    private float mRightBottomCorner;

    private int mWidth;
    private int mHeight;

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

    public CornersLinearLayout(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CornersLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        mContext = context;

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CornersLinearLayout);
        mCorners = typedArray.getDimension(R.styleable.CornersLinearLayout_corner, 0F);
        mLeftTopCorner = typedArray.getDimension(R.styleable.CornersLinearLayout_leftTopCorner, 0F);
        mRightTopCorner = typedArray.getDimension(R.styleable.CornersLinearLayout_rightTopCorner, 0F);
        mRightBottomCorner = typedArray.getDimension(R.styleable.CornersLinearLayout_rightBottomCorner, 0F);
        mLeftBottomCorner = typedArray.getDimension(R.styleable.CornersLinearLayout_leftBottomCorner, 0F);
        typedArray.recycle();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();
        setMeasuredDimension(mWidth, mHeight);
    }

    @Override
    public void draw(Canvas canvas) {
        canvas.save();

        Path path = new Path();
        RectF rectF = new RectF(0, 0, mWidth, mHeight);
        if (mCorners > 0F) {
            path.addRoundRect(rectF, mCorners, mCorners, Path.Direction.CCW);
        } else {
            float[] radii = new float[]{
                    mLeftTopCorner, mLeftTopCorner,
                    mRightTopCorner, mRightTopCorner,
                    mRightBottomCorner, mRightBottomCorner,
                    mLeftBottomCorner, mLeftBottomCorner
            };
            path.addRoundRect(rectF, radii, Path.Direction.CCW);
        }
        canvas.clipPath(path);

        super.draw(canvas);
    }
}

    相關的屬性聲明在style.xml文件中配置,這裏不涉及。draw()方法是在onDraw()之前的一個方法,在這裏可以使用clipPath()裁剪出一個圓角矩形輪廓。調用canvas.clipPath(path)生效,之後再執行super方法即可。

 

4.2 運行效果

 

4.3 優缺點

優點:

1.整個外層輪廓都切成圓角矩形,子View的background不會影響到圓角

2.四個角可以單獨配置也可以統一配置

 

缺點:

1.無法抗鋸齒,在一些老的手機上面效果可能差強人意

2.需要實現自己的自定義佈局

 

五.利用CardView實現切圓角

5.1 方案實現

    CardView是v7包中的組件(ViewGroup),主要用來設置佈局的邊框爲圓角、z軸的偏移量(這個是5.0以後纔有的概念,也就是陰影的效果)。這裏我們僅僅使用圓角功能。

    實現一個帶CardView的佈局

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".cardview.CardViewActivity">

    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginBottom="16dp"
        app:cardBackgroundColor="@color/colorAccent"
        app:cardCornerRadius="10dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="10dp的圓角"
            android:textColor="#FFFFFF"
            android:textSize="24sp" />
    </androidx.cardview.widget.CardView>

</androidx.constraintlayout.widget.ConstraintLayout>

    對應activity的代碼就沒必要貼了,XML佈局中

app:cardCornerRadius="10dp"

    即配置四個角爲10dp的圓角

 

5.2 運行效果

 

5.3 優缺點

優點:

1.Google的控件,效果和穩定性都是槓槓的,還支持陰影等其他的配置

2.抗鋸齒

 

缺點:

1.四個角要一起配置,不支持其中若干個角單獨配置

2.使用時外層要嵌套CardView

 

六.利用ViewOutlineProvider實現切圓角

6.1 方案實現

    利用ViewOutlineProvider設置輪廓切圓角。

    對應ViewOutlineProviderActivity的佈局XML如下

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".viewoutlineprovider.ViewOutlineProviderActivity">

    <LinearLayout
        android:id="@+id/lly"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginBottom="16dp"
        android:orientation="vertical"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:background="@color/colorAccent">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="10dp的圓角"
            android:textSize="24sp" />
    </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

    ViewOutlineProviderActivity代碼如下

package com.openld.roundcornerdemmo.viewoutlineprovider;

import android.graphics.Outline;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.widget.LinearLayout;

import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;

import com.openld.roundcornerdemmo.R;
import com.openld.roundcornerdemmo.utils.DisplayUtils;

public class ViewOutlineProviderActivity extends AppCompatActivity {
    private LinearLayout mLly;

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_view_outline_provider);

        initWidgets();
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void initWidgets() {
        mLly = findViewById(R.id.lly);

        mLly.setOutlineProvider(new ViewOutlineProvider() {
            @Override
            public void getOutline(View view, Outline outline) {
                outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), DisplayUtils.dp2px(ViewOutlineProviderActivity.this, 10F));
            }
        });
        mLly.setClipToOutline(true);
    }
}

    關鍵就是mLly.setOutlineProvider()與mLly.setClipToOutline()兩個方法的使用。

 

6.2 運行效果

 

6.3 優缺點

優點:

1.支持動態下發與配置

 

缺點:

1.只能四個角同時配置不支持每個角單獨配置

 

七.源碼位置

鏈接地址

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章