View自定义边框-Android

Android开发中最烦的事,就是不停的定义不同角度不同颜色不同样式的边框,不仅很难重复使用,也很难命名管理

现有的样式实现方式大体分为以下几类

1.图片背景代替 (缺点:图片命名难以管理,且适配会存在变形)
2.drawable (缺点:命名难以管理,难以复用)
3.自定义view(缺点:太烦)
4.第三方框架(PS: 实现思路基本还是第三种,缺点:太烦)

需求场景

  需要一个只有一个右上圆弧为3dp的红色背景
  需要一个只有一个左上圆弧为3dp的蓝色背景
  需要一个只有一个右上左上圆弧为5dp的灰色背景
  右上左上圆弧为3dp蓝色背景
  等等等等....

想实现的效果

最近学iOS,对iOS上实现这个功能的简单羡慕不已,Android想实现以下的效果

view.setRadius(strokeWidth = 10f,strokeColor = R.color.redF45265,backgroundColor = R.color.color999999,leftBottomRadius = 10,leftTopRadius = 40,rightBottomRadius = 20,rightTopRadius = 30)
view.setRadius(strokeWidth = 0.6f, strokeColor = R.color.colorCCCCCC, radius = 3)
view1.setRadius(strokeWidth = 0.6f, strokeColor = R.color.colorCCCCCC, radius = 3)
view1.setShadow(shadowColor = R.color.redF45265,shadowWidth = 4f)
效果图.jpg
代码实现

自定义一个Drawable类,在里面重写部分代码,边框这些用paint画出来(canvas小白,又更好的办法,欢迎指出)

class CustomDrawable(val view: View) : Drawable() {

   private var leftTopRadius = 0f
   private var rightTopRadius = 0f
   private var leftBottomRadius = 0f
   private var rightBottomRadius = 0f
   private var radiusArray = floatArrayOf()

   private var shadowColor: Int = R.color.colorPrimaryDark.getColor()
   private var shadowRectF = RectF()         // 阴影部分的背景颜色
   private var shadowWidth = 0f              // 阴影宽度单位为dp  0为不需要阴影

   private var strokeWidth = 0f
   private var strokeColor: Int = R.color.color999999.getColor()

   private var backGroundColor: Int = R.color.white.getColor()    // 背景颜色
   private val paint = Paint(Paint.ANTI_ALIAS_FLAG)

   /**
    * 设置圆角
    * @param backGroundColor  背景色
    * @param leftTopRadius     左上角
    * @param leftBottomRadius  左下角
    * @param rightBottomRadius 右下角
    * @param rightTopRadius    右上角
    * @param strokeWidth 边框宽度 dp f
    * @param strokeColor 边框颜色
    */
   fun setRadius(
       backGroundColor: Int = R.color.white,
       strokeWidth: Float = 0f,
       strokeColor: Int = R.color.colorB3B3B3,
       leftTopRadius: Int = 0,
       rightTopRadius: Int = 0,
       rightBottomRadius: Int = 0,
       leftBottomRadius: Int = 0
   ) {
       clearColorFilter()
       this.leftBottomRadius = leftBottomRadius.dp2px.toFloat()
       this.leftTopRadius = leftTopRadius.dp2px.toFloat()
       this.rightTopRadius = rightTopRadius.dp2px.toFloat()
       this.rightBottomRadius = rightBottomRadius.dp2px.toFloat()
       this.strokeWidth = Screen.dp2Px(strokeWidth).toFloat()
       this.strokeColor = strokeColor.getColor()
       this.backGroundColor = backGroundColor.getColor()
   }

   /**
    * 设置阴影
    * @param shadowWidth 阴影宽度 dp f
    * @param shadowColor 阴影颜色
    */
   fun setShadow(
       shadowWidth: Float = 0f,
       shadowColor: Int = R.color.colorB3B3B3
   ) {
       this.shadowWidth = Screen.dp2Px(shadowWidth).toFloat()
       this.shadowColor = shadowColor.getColor()
   }

   fun invalidate() {
       invalidateSelf()
   }

   override fun draw(canvas: Canvas) {
       getRadius()
       setDraw(canvas)
   }

   private fun setDraw(canvas: Canvas) {
       paint.color = strokeColor
       paint.isAntiAlias = true
       paint.style = Paint.Style.STROKE
       paint.strokeWidth = strokeWidth + if (backGroundColor != strokeColor) strokeWidth else 0f
       paint.setShadowLayer(shadowWidth, 0f, 0f, shadowColor)

       val path = Path()
       path.addRoundRect(shadowRectF, radiusArray, Path.Direction.CW)
       canvas.drawPath(path, paint)

       // 渲染内部
       paint.reset()
       paint.color = backGroundColor
       paint.isAntiAlias = true
       paint.style = Paint.Style.FILL
       paint.strokeWidth = strokeWidth

       path.reset()
       path.addRoundRect(shadowRectF, radiusArray, Path.Direction.CW)
       canvas.drawPath(path, paint)
   }

   /**
    * 计算圆角等值
    */
   private fun getRadius() {

       radiusArray = floatArrayOf(
           leftTopRadius, leftTopRadius,
           rightTopRadius, rightTopRadius,
           rightBottomRadius, rightBottomRadius,
           leftBottomRadius, leftBottomRadius
       )

       shadowRectF = RectF(
           shadowWidth + strokeWidth,
           shadowWidth + strokeWidth,
           view.width - shadowWidth - strokeWidth,
           view.height - shadowWidth - strokeWidth
       )

   }

   override fun setAlpha(alpha: Int) {
   }

   override fun setColorFilter(colorFilter: ColorFilter?) {
   }

   override fun getOpacity(): Int {
       return PixelFormat.TRANSLUCENT
   }

}

至此自定义部分已经完成了,接下来看调用

fun View.setShadow(
    shadowWidth: Float = 0f,
    shadowColor: Int = R.color.colorB3B3B3
) {

   if (this is CustomImageView) {
        setImageViewShadow(shadowWidth,shadowColor)
        return
    }

    var newDrawable: CustomDrawable? = null
    // 如果类型是自定义类型drawable 直接修改
    if (backgroundDrawable != null && backgroundDrawable is CustomDrawable) {
        newDrawable = backgroundDrawable as CustomDrawable
    }
    
    // drawable类型不是自定义的CustomDrawable类型,则修改为CustomDrawable
    if (newDrawable == null) {
        newDrawable = CustomDrawable(this)
    }
    
    newDrawable.setShadow(shadowWidth, shadowColor)
    newDrawable.invalidate()
    background = newDrawable
}


fun View.setRadius(
    backgroundColor: Int = R.color.white,
    strokeWidth: Float = 0f,
    strokeColor: Int = R.color.colorB3B3B3,
    leftTopRadius: Int = 0,
    rightTopRadius: Int = 0,
    leftBottomRadius: Int = 0,
    rightBottomRadius: Int = 0
) {

 if (this is CustomImageView) {
        setImageViewRadius(
            backGroundColor = backgroundColor,
            strokeWidth = strokeWidth,
            strokeColor = strokeColor,
            leftTopRadius = leftTopRadius,
            rightTopRadius = rightTopRadius,
            leftBottomRadius = leftBottomRadius,
            rightBottomRadius = rightBottomRadius
        )
        return
    }

    var newDrawable: CustomDrawable? = null
    val oldDrawable = backgroundDrawable

    if (backgroundDrawable != null && oldDrawable is CustomDrawable) {
        newDrawable = oldDrawable
    }

    if (newDrawable == null) {
        newDrawable = CustomDrawable(this)
    }

    newDrawable.setRadius(
        backgroundColor,
        strokeWidth,
        strokeColor,
        leftTopRadius,
        rightTopRadius,
        rightBottomRadius,
        leftBottomRadius
    )
    newDrawable.invalidate()

    // 重新渲染 防止颜色叠加
    requestLayout()
    background = newDrawable
}

/**
 * 添加圆角
 */
fun View.setRadius(
    backgroundColor: Int = R.color.white,
    strokeWidth: Float = 0f,
    strokeColor: Int = backgroundColor,
    radius: Int = 0
) {

   // iamgeView 图层比较特殊,是浮在drawable之上,需要特殊处理 也可以直接调用
   if (this is CustomImageView) {
        setImageViewRadius(
            backGroundColor = backgroundColor,
            strokeWidth = strokeWidth,
            strokeColor = strokeColor,
            leftTopRadius = leftTopRadius,
            rightTopRadius = rightTopRadius,
            leftBottomRadius = leftBottomRadius,
            rightBottomRadius = rightBottomRadius
        )
        return
    }

    setRadius(
        backgroundColor = backgroundColor,
        strokeWidth = strokeWidth,
        strokeColor = strokeColor,
        leftTopRadius = radius,
        rightTopRadius = radius,
        leftBottomRadius = radius,
        rightBottomRadius = radius
    )
}

自定义边框各圆角的imageView


class CustomImageView: ImageView {

    private var leftTopRadius = 0f
    private var rightTopRadius = 0f
    private var leftBottomRadius = 0f
    private var rightBottomRadius = 0f
    private var radiusArray = floatArrayOf()

    private var shadowColor: Int = R.color.colorPrimaryDark.getColor()
    private var shadowRectF = RectF()         // 阴影部分的背景颜色
    private var shadowWidth = 0f              // 阴影宽度单位为dp  0为不需要阴影

    private var strokeWidth = 0f
    private var strokeColor: Int = R.color.color999999.getColor()

    private var backGroundColor: Int = R.color.white.getColor()    // 背景颜色
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    
    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
    constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)

    /**
     * 设置圆角
     * @param backGroundColor  背景色
     * @param leftTopRadius     左上角
     * @param leftBottomRadius  左下角
     * @param rightBottomRadius 右下角
     * @param rightTopRadius    右上角
     * @param strokeWidth 边框宽度 dp f
     * @param strokeColor 边框颜色
     */
    fun setImageViewRadius(
        backGroundColor: Int = R.color.white,
        strokeWidth: Float = 0f,
        strokeColor: Int = R.color.colorB3B3B3,
        leftTopRadius: Int = 0,
        rightTopRadius: Int = 0,
        rightBottomRadius: Int = 0,
        leftBottomRadius: Int = 0
    ) {
        clearColorFilter()
        this.leftBottomRadius = leftBottomRadius.dp2px.toFloat()
        this.leftTopRadius = leftTopRadius.dp2px.toFloat()
        this.rightTopRadius = rightTopRadius.dp2px.toFloat()
        this.rightBottomRadius = rightBottomRadius.dp2px.toFloat()
        this.strokeWidth = Screen.dp2Px(strokeWidth).toFloat()
        this.strokeColor = strokeColor.getColor()
        this.backGroundColor = backGroundColor.getColor()
    }

    /**
     * 设置阴影
     * @param shadowWidth 阴影宽度 dp f
     * @param shadowColor 阴影颜色
     */
    fun setImageViewShadow(
        shadowWidth: Float = 0f,
        shadowColor: Int = R.color.colorB3B3B3
    ) {
        this.shadowWidth = Screen.dp2Px(shadowWidth).toFloat()
        this.shadowColor = shadowColor.getColor()
    }

    override fun onDraw(canvas: Canvas?) {
        getRadius()
        setDraw(canvas)
        super.onDraw(canvas)
    }

    private fun setDraw(canvas: Canvas?) {
        paint.color = strokeColor
        paint.isAntiAlias = true
        paint.style = Paint.Style.STROKE
        paint.strokeWidth = strokeWidth + if (backGroundColor != strokeColor) strokeWidth else 0f
        paint.setShadowLayer(shadowWidth, 0f, 0f, shadowColor)

        val path = Path()
        path.addRoundRect(shadowRectF, radiusArray, Path.Direction.CW)
        canvas?.drawPath(path, paint)
        canvas?.clipPath(path)
    }

    /**
     * 计算圆角等值
     */
    private fun getRadius() {
        radiusArray = floatArrayOf(
            leftTopRadius, leftTopRadius,
            rightTopRadius, rightTopRadius,
            rightBottomRadius, rightBottomRadius,
            leftBottomRadius, leftBottomRadius
        )
        shadowRectF = RectF(
            shadowWidth + strokeWidth,
            shadowWidth + strokeWidth,
            width - shadowWidth - strokeWidth,
            height - shadowWidth - strokeWidth
        )
    }

}
使用及效果
 <Button
            android:id="@+id/button"
            android:layout_width="200dp"
            android:layout_marginTop="20dp"
            android:text="button"
            android:gravity="center"
            android:layout_height="wrap_content"/>


    <com.wdslove.book.view.CustomImageView
            android:id="@+id/imageView"
            android:layout_width="200dp"
            android:text="customImageView"
            android:gravity="center"
            android:src="@mipmap/empty_loading"
            android:layout_marginTop="20dp"
            android:layout_height="200dp"/>

    <TextView
            android:id="@+id/textView"
            android:layout_width="200dp"
            android:text="textView"
            android:gravity="center"
            android:layout_marginTop="20dp"
            android:layout_height="40dp"/>

    <LinearLayout
            android:layout_width="200dp"
            android:id="@+id/linearLayout"
            android:layout_height="50dp"
            android:layout_marginTop="@dimen/rc_dimen_size_50"
            android:orientation="horizontal" />

// 使用
button.setRadius(strokeWidth = 10f,strokeColor = R.color.redF45265,backgroundColor = R.color.color999999,leftBottomRadius = 10,leftTopRadius = 40,rightBottomRadius = 20,rightTopRadius = 30)
imageView.setRadius(strokeWidth = 2f,strokeColor = R.color.redF45265,backgroundColor = R.color.color999999,leftBottomRadius = 10,leftTopRadius = 40,rightBottomRadius = 100,rightTopRadius = 30)
imageView.setShadow(shadowColor = R.color.color333333,shadowWidth = 5f)
textView.setRadius(strokeWidth = 0.6f, strokeColor = R.color.colorCCCCCC, radius = 3)
textView.setShadow(shadowColor = R.color.redF45265,shadowWidth = 4f)
linearLayout.setRadius(strokeWidth = 0.6f, strokeColor = R.color.colorCCCCCC, radius = 3)
linearLayout.setShadow(shadowColor = R.color.redF45265,shadowWidth = 4f)
效果图.png

不创建大量的drawable,也不用为太多的背景图命名烦恼,基本能满足开发的大部分需求, 欢迎指出问题和改进。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,692评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,482评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,995评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,223评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,245评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,208评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,091评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,929评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,346评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,570评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,739评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,437评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,037评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,677评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,833评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,760评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,647评论 2 354

推荐阅读更多精彩内容

  • 【Android 自定义View 圆形圆角图片】 基于Xfermode 实现 1、概述 在很久以前也写过一个利用X...
    Rtia阅读 6,886评论 1 10
  • 前言 自定义 View 有几种实现类型,分别为: 继承自 View 完全自定义; 继承自现有控件(如 ImageV...
    Little丶Jerry阅读 623评论 0 0
  • 在android开发中 ,我们需要通过drawable图片来改变控件的背景或者样式。如果我们没有呢,就需要我们在控...
    zzj丶阅读 930评论 0 6
  • 最近做项目碰到一个需求,需要实现如下图的一个自定义的view。 如上图,还需实现一个动画效果,中间的圆形image...
    goforlondon阅读 684评论 0 2
  • 【Android 自定义View之绘图】 基础图形的绘制 一、Paint与Canvas 绘图需要两个工具,笔和纸。...
    Rtia阅读 11,667评论 5 34