我们开发中会经常遇到实现圆角的需求,本文汇总不同场景下常见的一些方案,欢迎补充。
1.使用shape标签
xml里面利用shape元素实现圆角
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="5dp"/> <!-- 设置圆角弧度 -->
//<corners android:topRightRadius="8dp" android:topLeftRadius="8dp"/> //各个角不同弧度
<solid android:color="@color/bg_color"/> <!-- 设置背景颜色 -->
<stroke android:color="@color/divider_color" android:width="@dimen/y2"/> <!-- 设置边框颜色以及宽度 -->
</shape>
优点:
:方便简洁直观
缺点:
:每个xml只有一种样式
:静态图片作为背景适用,如果动态切割就不合适
2.使用 CardView 、GradientDrawable等现有的图片控件
GradientDrawable方式
private GradientDrawable gradientDrawable = new GradientDrawable();
gradientDrawable.setShape(GradientDrawable.RECTANGLE);
float[] radii = new float[]{//支持各个角设置不同的弧度
DisplayUtils.dp2px(this, 10F), DisplayUtils.dp2px(this, 10F),
0F, 0F,
0F, 0F,
DisplayUtils.dp2px(this, 10F), DisplayUtils.dp2px(this, 10F)
};
gradientDrawable.setCornerRadii(radii);
gradientDrawable.setCornerRadius(radius);
gradientDrawable.setColor(normalColor);
setBackground(gradientDrawable);
CardView方式
<androidx.cardview.widget.CardView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/card_store_normal_margin"
android:layout_marginBottom="@dimen/card_store_normal_margin"
app:cardCornerRadius="@dimen/card_store_icon_radius"
app:cardElevation="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@tools:sample/avatars">
<ImageView
android:id="@+id/app_icon"
android:layout_width="@dimen/card_store_icon_height"
android:layout_height="@dimen/card_store_icon_height"
android:scaleType="centerCrop"
tools:srcCompat="@tools:sample/avatars" />
</androidx.cardview.widget.CardView>
优点:
:可以动态设置角弧度
:同时支持xml或者代码方式定义
缺点:
:灵活度依然略差
3.使用ViewOutlineProvider裁剪view
ViewOutlineProvider是Android 5.x引入的新特性,用于实现View的阴影和轮廓
itemView.outlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
outline.setRoundRect(0, 0, view.width, view.height, 5.dp.toFloat())
}
}
// 打开开关
itemView.clipToOutline = true
outline 还可以画其他的一些内容:
outline.setRect(xxx)// 画矩形
outline.setRoundRect(xxx)// 画圆角矩形
outline.setOval(xxx) // 画椭圆
优点:
:比使用了设置drawable的 xml 少了一层过度绘制。因为省去了设置的 background
:Outline方式是直接作用于RenderNode,然后再渲染流程中进行过滤,不是对Canvas做切割,因此效率相对于其他几种方式可以更高?
缺点:
:只能四个角同时配置不支持每个角单独配置
4.Canvas切割
可以是通过canvas.clipPath切割后绘制目标View,也可是先绘制目标View后使用Xfermode设置混合模式 绘制很自由,可以按照自己的需求切出不同形状,比如可以切出四个圆角不等的效果
clipPath方式:
整个原理就是用Path划出一个圆角矩形区域,调用super.onDraw(canvas)就可以让Drawable 落在那个区域。
@Override
protected void onDraw(Canvas canvas) {
//设置外框的矩形区域,不可再init()初始化,构造器中width和height还未确定,可在onMesure()中获取并设置
mRectF = new RectF(0,0, getWidth(),getHeight());
//path划出一个圆角矩形,容纳图片,图片矩形区域设置比红色外框小,否则会覆盖住外框,随意控制
mPath.addRoundRect(new RectF(10, 10, mRectF.right-10,mRectF.bottom-10), 50, 50, Path.Direction.CW);
canvas.drawRoundRect(mRectF, 50, 50, mPaint); //画出红色外框圆角矩形
canvas.clipPath(mPath);//将canvas裁剪到path设定的区域,往后的绘制都只能在此区域中,
//这一句应该放在canvas.clipPath(path)之后,canvas.clipPath(path)只对裁剪之后的绘制起作用,
// 这个方法在ImageView中会画出xml设置的Drawable,落在刚才设置的path中
super.onDraw(canvas);
}
Xfermode方式:
利用Xfermode的图片渲染合成原理,使用Path构造我们需要的效果范围,在onDraw的时候,将范围外的所有部分过滤掉
@Override
protected void dispatchDraw(Canvas canvas) {
if (clipRect == null || getMeasuredWidth() == 0) {
clipRect = new RectF(padding, 0, getMeasuredWidth() - padding, getMeasuredHeight());
maskBitmap = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Bitmap.Config.ARGB_4444);
Canvas c = new Canvas(maskBitmap);
//在蒙版上画需要覆盖的图形
c.drawRoundRect(clipRect, cornerSize, cornerSize, clipPaint);
}
//保存还没有绘制之前的图层
int layerId = canvas.saveLayer(clipRect, clipPaint, Canvas.ALL_SAVE_FLAG);
//绘制底部图层
super.dispatchDraw(canvas);
//设置混合模式,实现view的四个圆角
clipPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
canvas.drawBitmap(maskBitmap, 0, 0, clipPaint);
clipPaint.setXfermode(null);
//恢复之前的图层,要不然背景是黑色的
canvas.restoreToCount(layerId);
}
5.其他第三方库
Glide
Glide.with(this).load("https://placehoder.com/100")
.apply(RequestOptions.bitmapTransform(RoundedCorners(20)))
.into(iv)
fresco
...