如何实现一个圆形且带描边的ImageView

345 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第30天,点击查看活动详情

该问题拆解为:自定义View -> 实现正方形效果 -> 切割为圆形 -> 描边

1、自定义CircleImageView继承AppCompatImageView

View有三个构造函数,在创建时不需要全部继承父类的构造函数。只需要继承带三个参数的构造函数,其他两个构造函数链式调用这个构造函数。在进行初始化时,只需要在这个构造函数内初始化即可。

 class CircleImageView : AppCompatImageView {
   constructor(context: Context) : this(context, null)
   constructor(context: Context, attributes: AttributeSet?) : this(context, attributes, 0)
   constructor(context: Context, attributes: AttributeSet?, defStyleAttr: Int) : super(context,attributes, defStyleAttr)
 }
2、正方形的实现

重写View测量方法onMeasure(),设置ImageView的长、宽为测量宽度,实现正方形ImageView

 override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec)
         setMeasuredDimension(measuredWidth, measuredWidth)
     }
3、圆形的实现

重写View绘制方法onDraw(),通过路径将画布裁剪成圆形。这是最便捷的方法,但是处理出来的圆形会有锯齿,只适合小图片。

 class CircleImageView : AppCompatImageView {
   private val path: Path = Path()
   override fun onDraw(canvas: Canvas?) {
           canvas?.save()
           path.reset()
           path.addCircle(width / 2f, height / 2f, width / 2f, Path.Direction.CCW)
           canvas?.clipPath(path)
           super.onDraw(canvas)
           canvas?.restore()
       }
   
   ......
 }
4、描边的实现

描边有颜色和宽度两个属性,需要对这两个属性进行定义。

在资源文件中声明这两个自定义属性,注意属性的类型是尺寸和颜色值。

 <resources>
     <declare-styleable name="CircleImageView">
         <attr name="borderWidth" format="dimension" />
         <attr name="borderColor" format="color" />
     </declare-styleable>
 </resources>

在构造方法中对这两个属性进行获取,注意先判断是否有这两个属性值,如果没有赋默认值。在borderCorlorset方法中调用invalidate(),可以实现动态设置描边颜色。

 class CircleImageView : AppCompatImageView {
   private var borderWidth: Float
   var borderCorlor: Int
         set(value) {
             field=value
             invalidate()
         }
   constructor(context: Context, attributes: AttributeSet?, defStyleAttr: Int) : super(
         context,
         attributes,
         defStyleAttr
     ) {
         val typeArray = context.obtainStyledAttributes(
             attributes,
             R.styleable.CircleImageView,
             defStyleAttr,
             defStyleAttr
         )
         borderWidth = 
             if (typeArray.hasValue(R.styleable.CircleImageView_borderWidth)) {
                 typeArray.getDimension(R.styleable.CircleImageView_borderWidth, 0f)
             } else {
                 0f
             }
         borderCorlor = if (typeArray.hasValue(R.styleable.CircleImageView_borderColor)) {
             typeArray.getColor(
                 R.styleable.CircleImageView_borderColor,
                 resources.getColor(R.color.transparent)
             )
         } else {
             resources.getColor(R.color.transparent)
         }
     }
   
   ......
 }

接着构建画笔,在绘制完图片之后进行描边

 class CircleImageView : AppCompatImageView {
   private val path: Path = Path()
   private val borderPaint: Paint = Paint()
   override fun onDraw(canvas: Canvas?) {
     //裁剪
         canvas?.save()
         path.reset()
         path.addCircle(width / 2f, height / 2f, width / 2f, Path.Direction.CCW)
         canvas?.clipPath(path)
         super.onDraw(canvas)
     //描边
         borderPaint.setColor(borderCorlor)
         borderPaint.isAntiAlias = true
         borderPaint.style = Paint.Style.STROKE
         borderPaint.strokeWidth = borderWidth
         canvas?.drawCircle(width / 2f, height / 2f, width / 2f, borderPaint)
 ​
         canvas?.restore()
     }
 }

以下是效果图,上面是带描边的效果,下面的图是没有描边的。当图片较大时锯齿会很明显。

wecom-temp-a65a26fdd74964f91f72e097fe3042d2.png