Android知识点-圆角

先扯两句

不知道多长时间没有登录了,正好今天不太忙进来看一眼,然后看了好多文章,从我发的东西也能看出来,绝对不肯能是纯技术的,那看的种类那叫一个多啊。发现首页有几篇申请创作者没成功的帖子,再看看自己的创作者,不由老脸一红,还是写一篇对得起平台对我的认可。

正文

随着UI越来越美观,原本直来直去的布局样式越来越不受喜欢,圆角样式,这个最简单的优化方案在越来越多的场合下被应用到。而具体怎么样才能展示出圆角的效果,相比大家都有N多中方案,我这里就列举一些自己常用的方案吧。当然,还有一些第三方的框架支持,这里就不列举了,主要是我个人感觉为了一个简单的效果,集成一个库不太划算,尤其是库中很大一部分功能都用不到的时候。而且第三方的框架贡献者实力不等,有很多都要导入源码进一步优化,分析太麻烦了,一向懒汉自称的我是不可能这么勤快去列举的,大家有兴趣可以自行研究。
样式参考就已自如房型列表的item为例吧:


样式参考

方案0

首先说明一下,什么叫做方案0,不是因为程序员,计数一定要从0开始的洁癖,单纯是因为这些方案算是比较常用的,大家遇到类似的需求很快就能想到的。这里只是简单介绍一下,给一些新入门的同学看看,在遇到相应的圆角需求的时候,可以最方便的解决问题。
当然,还是同样的,如果一定要找一个第三方框架,一个参数控制去实现也不是不可以,性能各方面我这里也没有去具体评估过,也没办法说一定谁好谁坏。所以对于一切反对意见,我这里的回应都是:“我错了!你说的对!”

1 圆角背景:xml

圆角的背景,可以实现的效果就是上面图中的下半部分


xml可实现部分

首先我们需要分析这个部分有什么要求:


分析

可以看到有两个地方有要求,分别是:1.白色背景;2.灰色边框。所以对应的xml只需要满足这两点就可以了。

  1. 创建drawable的xml文件


    创建目录
  2. 命名,并将对应的跟元素设置为shape(形状)


    image.png
  3. 配置属性


    
    
    
    
    
    

其中需要注意的是,图中可以看到,需要有圆角的区域只在下半部分,所以这里配置的时候也要只配置下半部,不然图片与背景的交接处就会有相当怪异。


圆角设置错误示例

这里可能有人会说,直接绘制一个白色的全圆角背景放到下面,上面盖上一个圆角的图片不就好了吗?你真是一个小机灵鬼,这个方案当然可行。但是性能稍微有点追求的建议了解一些过度绘制,虽然不是说背景上面叠加一张图片就会对性能造成断崖式影响,但是雪崩之下,可没有哪朵雪花是无辜的。
当然,这里为了看得清晰,页面的背景设置成了黑色,圆角也调整到了20dp,正常情况下也会出现问题,但是不会这么明显,能不能发现就看测试团队和UI团队的审查力度了。


xml正确样式

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,直接上代码:



    

        

            

            
        
    

然后看效果:


cardview效果图

可以看到,前面的两个问题,在这里都直接搞定了,背景图也不用设置圆角了,glide也不用设置圆角了,在CardView中添加了app:cardCornerRadius="5dp"后,一切问题都迎刃而解了,是不是特别爽,特别开心!

缺点分析

上面虽然说实现了功能,但是能放到方案0上,就代表还有其他在我个人的能力水平内能看到的更好的方案(具体是不是还是要交给大神们评估),那就先说说上面的这些我所看到的缺点吧。

  1. xml只能对简单的色块等做配置,如果是图片的虽然也可以通过layer-list来完成拼接绘制,但是绘制难度跟在layout的xml中的绘制难度就不是一个量级的(比如位置的相对关系)
  2. glide针对的主要是图片的处理,另外还有一个问题对于加载中的网络图片的支持可以,但是图片加载前的占位图和图片加载后的缺省图支持的就不是很好,一旦图片请求失败,展示个没有圆角的图,测试绝对会来找你的麻烦
  3. CardView看起来可以完美的规避掉上面两种方案的问题,但是可以看到,我们在绘制页面的时候,CardView中需要添加一个子View去绘制其中各个View的相对关系,毕竟CardView的父控件是FrameLayout,又没有重写onLayout方法,所以子View的展示逻辑,可以理解为就是FrameLayout。这样为了实现一个效果就需要多一层View嵌套。且同样,CardView配置圆角的时候,也是四个角同步配置的,如果需要部分配置的时候也是不支持的。


    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
                    )
                }
            }
        }
    }

    // 业务代码逻辑
    // ...
}

布局:


    

        
        
        
    

效果如图:


ViewOutlineProvider效果图

注:
1. 代码中圆角的位置是会收到padding影响的,如果不想收到影响,去掉setRoundRect中的四个padding参数即可,具体依据具体需求调整
2. 由于ViewOutlineProvider api的限制,暂时能配置的都是四个角统一角度,如果想要分别配置,该方案不适用

方案三 切图覆盖

可以看到,上面的所有方案在特定的情况下都会有所限制,所以在相应的场景下,产品经历还是要求我们实现对应的圆角效果,我们又不能让他闭嘴的时候,只能再想其他方案。
这里的建议就是切图覆盖,也就是让UI切一个颜色与背景色相同的圆角边框,覆盖到需要圆角展示的View上方,假装实现了一个圆角的效果。

圆角图片:

圆角图片

注:为了让圆角图片在文章中显示不站太大的控件,所以尺寸降低到了200,如果需要大尺寸,可以在图片链接中修改.

图片尺寸调整

绘制布局:




    

    

圆角效果:


圆角效果

优点:基本可以兼容产品的所有圆角样式需求
缺点:需要多绘制一层View,不够优雅

总结

上面列举的就是我现阶段想到的所有圆角效果的实现方案,在工作场景中,其实大多数情况也都够用了,当然,大家如果有其他的方案,也欢迎分享一下,大家共同进步。
这里也说过了,是没有列举第三方的方案的,都是一些原生的实现方案,虽然说用第三方的方案很爽,但是大多数情况下,还是建议自己学习一下具体的实现,较少一些不必要的引用。
另外,关于最后一条要说几句,其实这个方案是一次看网课有学生提问,老师提到的方案,结果是学生感觉这种方案是在忽悠他,最终不欢而散。其实说实话,这个方案,我个人也不是那么建议使用的,只是有很多情况我们并没有办法很快的实现产品或者UI想要的一些效果,不得不使用一些折中的方案,也算是工作中的一种妥协和无奈。
这里不是建议大家一点不用,但是最好应付过关后,在空闲时间思考一下,还有没有其他好的解决方案丰富自己。
好了,祝大家新年快乐,开年第一天的摸鱼……还有什么可以做的呢,容我想想,哈哈哈!

你可能感兴趣的:(Android知识点-圆角)