先扯两句
不知道多长时间没有登录了,正好今天不太忙进来看一眼,然后看了好多文章,从我发的东西也能看出来,绝对不肯能是纯技术的,那看的种类那叫一个多啊。发现首页有几篇申请创作者没成功的帖子,再看看自己的创作者,不由老脸一红,还是写一篇对得起平台对我的认可。
正文
随着UI越来越美观,原本直来直去的布局样式越来越不受喜欢,圆角样式,这个最简单的优化方案在越来越多的场合下被应用到。而具体怎么样才能展示出圆角的效果,相比大家都有N多中方案,我这里就列举一些自己常用的方案吧。当然,还有一些第三方的框架支持,这里就不列举了,主要是我个人感觉为了一个简单的效果,集成一个库不太划算,尤其是库中很大一部分功能都用不到的时候。而且第三方的框架贡献者实力不等,有很多都要导入源码进一步优化,分析太麻烦了,一向懒汉自称的我是不可能这么勤快去列举的,大家有兴趣可以自行研究。
样式参考就已自如房型列表的item为例吧:
方案0
首先说明一下,什么叫做方案0,不是因为程序员,计数一定要从0开始的洁癖,单纯是因为这些方案算是比较常用的,大家遇到类似的需求很快就能想到的。这里只是简单介绍一下,给一些新入门的同学看看,在遇到相应的圆角需求的时候,可以最方便的解决问题。
当然,还是同样的,如果一定要找一个第三方框架,一个参数控制去实现也不是不可以,性能各方面我这里也没有去具体评估过,也没办法说一定谁好谁坏。所以对于一切反对意见,我这里的回应都是:“我错了!你说的对!”
1 圆角背景:xml
圆角的背景,可以实现的效果就是上面图中的下半部分
首先我们需要分析这个部分有什么要求:
可以看到有两个地方有要求,分别是:1.白色背景;2.灰色边框。所以对应的xml只需要满足这两点就可以了。
-
创建drawable的xml文件
-
命名,并将对应的跟元素设置为shape(形状)
- 配置属性
其中需要注意的是,图中可以看到,需要有圆角的区域只在下半部分,所以这里配置的时候也要只配置下半部,不然图片与背景的交接处就会有相当怪异。
这里可能有人会说,直接绘制一个白色的全圆角背景放到下面,上面盖上一个圆角的图片不就好了吗?你真是一个小机灵鬼,这个方案当然可行。但是性能稍微有点追求的建议了解一些过度绘制,虽然不是说背景上面叠加一张图片就会对性能造成断崖式影响,但是雪崩之下,可没有哪朵雪花是无辜的。
当然,这里为了看得清晰,页面的背景设置成了黑色,圆角也调整到了20dp,正常情况下也会出现问题,但是不会这么明显,能不能发现就看测试团队和UI团队的审查力度了。
2 纯图片Glide
现在用的比较多的,或者是我工作这么多年用的比较多的图片框架就是glide,想当初在刚开始用glide的时候还不支持圆角,需要额外添加一堆配置,还要导入第三方的库,现在好了,没有几行代码,基本属于一键配置了。
Glide.with(this).load(R.drawable.bg_scene)
.apply(
RequestOptions
.bitmapTransform(RoundedCorners(dip2px(this, 20f)))
).into(findViewById(R.id.test_img))
其中apply中的内容就是圆角的配置,当然,猜也能猜出来,这里只配置了一个角度,显示的时候底部肯定有问题,应该只配置顶部的两个圆角才对,这里给大家找了个链接:Glide 加载部分圆角图片,作为一个懒汉,没有做测试,哪位如果正好赶上有这个需求可以测试一下,我们直接进入下一个原生方案。
3 CardView
UI样式的大趋势,作为Android的幕后大boss google怎么可能不知道呢,所以官方控件里也有这么一个小朋友可以实现这个功能,那就是CardView,直接上代码:
然后看效果:
可以看到,前面的两个问题,在这里都直接搞定了,背景图也不用设置圆角了,glide也不用设置圆角了,在CardView中添加了app:cardCornerRadius="5dp"后,一切问题都迎刃而解了,是不是特别爽,特别开心!
缺点分析
上面虽然说实现了功能,但是能放到方案0上,就代表还有其他在我个人的能力水平内能看到的更好的方案(具体是不是还是要交给大神们评估),那就先说说上面的这些我所看到的缺点吧。
- xml只能对简单的色块等做配置,如果是图片的虽然也可以通过layer-list来完成拼接绘制,但是绘制难度跟在layout的xml中的绘制难度就不是一个量级的(比如位置的相对关系)
- glide针对的主要是图片的处理,另外还有一个问题对于加载中的网络图片的支持可以,但是图片加载前的占位图和图片加载后的缺省图支持的就不是很好,一旦图片请求失败,展示个没有圆角的图,测试绝对会来找你的麻烦
-
CardView看起来可以完美的规避掉上面两种方案的问题,但是可以看到,我们在绘制页面的时候,CardView中需要添加一个子View去绘制其中各个View的相对关系,毕竟CardView的父控件是FrameLayout,又没有重写onLayout方法,所以子View的展示逻辑,可以理解为就是FrameLayout。这样为了实现一个效果就需要多一层View嵌套。且同样,CardView配置圆角的时候,也是四个角同步配置的,如果需要部分配置的时候也是不支持的。
那么我们就进入到为大家准备的其他方案。
方案一:自定义View圆角
这个方案就是通过declare-styleable配置圆角角度,然后在执行onDraw的时候,通过裁剪Carvas实现圆角效果,代码如下:
自定义View代码
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)
/**
* 左上角 圆角大小
*/
private var leftTopRadius: Float = 0f
/**
* 右上角 圆角大小
*/
private var rightTopRadius: Float = 0f
/**
* 左下角 圆角大小
*/
private var leftBottomRadius: Float = 0f
/**
* 右下角 圆角大小
*/
private var rightBottomRadius: Float = 0f
/**
* 全部圆角
*/
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) {
//不知道为啥 不能 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 成对出现,控制上右下左,四个位置圆角
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)
}
}
自定义ViewGroup圆角(onDraw替换为dispatchDraw)
override fun dispatchDraw(canvas: Canvas) {
//1.
//dx,dy 成对出现,控制上右下左,四个位置圆角
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)
}
注:
1. canvas裁剪需要放置在super之前
2. 频繁刷新UI,会导致频繁在onDraw执行canvas裁剪,出现黑色闪屏
方案二:ViewOutlineProvider
其实我较多的时候使用的还是方案一,直到一次的需求需要频繁刷新UI,才发现了黑色闪屏的问题,查找其他方案的时候,才知道的ViewOutlineProvider,参考链接:Material Design :ViewOutlineProvider
demo代码如下:
圆角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
)
}
}
}
}
// 业务代码逻辑
// ...
}
布局:
效果如图:
注:
1. 代码中圆角的位置是会收到padding影响的,如果不想收到影响,去掉setRoundRect中的四个padding参数即可,具体依据具体需求调整
2. 由于ViewOutlineProvider api的限制,暂时能配置的都是四个角统一角度,如果想要分别配置,该方案不适用
方案三 切图覆盖
可以看到,上面的所有方案在特定的情况下都会有所限制,所以在相应的场景下,产品经历还是要求我们实现对应的圆角效果,我们又不能让他闭嘴的时候,只能再想其他方案。
这里的建议就是切图覆盖,也就是让UI切一个颜色与背景色相同的圆角边框,覆盖到需要圆角展示的View上方,假装实现了一个圆角的效果。
圆角图片:
注:为了让圆角图片在文章中显示不站太大的控件,所以尺寸降低到了200,如果需要大尺寸,可以在图片链接中修改.
绘制布局:
圆角效果:
优点:基本可以兼容产品的所有圆角样式需求
缺点:需要多绘制一层View,不够优雅
总结
上面列举的就是我现阶段想到的所有圆角效果的实现方案,在工作场景中,其实大多数情况也都够用了,当然,大家如果有其他的方案,也欢迎分享一下,大家共同进步。
这里也说过了,是没有列举第三方的方案的,都是一些原生的实现方案,虽然说用第三方的方案很爽,但是大多数情况下,还是建议自己学习一下具体的实现,较少一些不必要的引用。
另外,关于最后一条要说几句,其实这个方案是一次看网课有学生提问,老师提到的方案,结果是学生感觉这种方案是在忽悠他,最终不欢而散。其实说实话,这个方案,我个人也不是那么建议使用的,只是有很多情况我们并没有办法很快的实现产品或者UI想要的一些效果,不得不使用一些折中的方案,也算是工作中的一种妥协和无奈。
这里不是建议大家一点不用,但是最好应付过关后,在空闲时间思考一下,还有没有其他好的解决方案丰富自己。
好了,祝大家新年快乐,开年第一天的摸鱼……还有什么可以做的呢,容我想想,哈哈哈!