一.遇到的問題
最近在開發中會遇到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.只能四個角同時配置不支持每個角單獨配置
七.源碼位置
鏈接地址