本篇文章已授权微信大众号 guolin_blog (郭霖)独家发布[2022-12-02]

本系列自定义View全部采用kt

体系: mac

android studio: 4.1.3

kotlin version: 1.5.0

gradle: gradle-6.5-bin.zip

本篇作用:

8140FE3CF87738708E0C5D0E4F59704F

前沿

最近在bilibili看到一个跑马灯光圈作用挺好, 参考着思路写了一下.

bilibili地址,美中不足的是这是html代码 QaQ

实现思路

  • 将作用分为3层

    • 第一层: 布景
    • 第二层: 跑马灯光圈
    • 第三层: 展现区

如图所示:

Nov-28-2022 17-19-34

tips: 图片截取自上方bilibili视频

换到android中直接将view当作布景层, 在使用Canvas制作跑马灯层即可

将View圆角化

 // 设置view圆角
 outlineProvider = object : ViewOutlineProvider() {
   override fun getOutline(view: View, outline: Outline) {
     // 设置圆角率为
     outline.setRoundRect(00, view.width, view.height, RADIUS)
  }
 }
 clipToOutline = true

这段代码网上找的,源码还没有看, 有时机再看吧.

image-20221128173221355

来看看当时作用:

CD09F6ED6DBE6895E487C703B7DB64F0

自定义跑马灯光圈

这几个字或许有点抽象,所以来看看要完结的作用:

Nov-28-2022 17-45-34

接下来只需要吧黄框外面和里边的的去掉就完结了旋转的作用:

去掉外面:

Nov-28-2022 17-47-38

去掉里边:

Nov-28-2022 17-47-32

这都是html作用,接下来看看android怎么写:

 class ApertureView @JvmOverloads constructor(
     context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
 ) : View(context, attrs, defStyleAttr) {
     companion object {
         val DEF_WIDTH = 200.dp
         val DEF_HEIGHT = DEF_WIDTH
         private val RADIUS = 20.dp
    }
 ​
     private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
 ​
     private val rectF by lazy {
         val left = 0f + RADIUS / 2f
         val top = 0f + RADIUS / 2f
         val right = left + DEF_WIDTH - RADIUS
         val bottom = top + DEF_HEIGHT - RADIUS
         RectF(lefttoprightbottom)
    }
 ​
     override fun onDraw(canvas: Canvas) {
         val left = rectF.left + rectF.width() / 2f
         val right = rectF.right + rectF.width()
         val top = rectF.top + rectF.height() / 2f
         val bottom = rectF.bottom + rectF.height() / 2f
 ​
         // 制作突变view1
         paint.color = Color.GREEN
         canvas.drawRect(lefttoprightbottom, paint)
 ​
         // 制作突变view2
         paint.color = Color.RED
         canvas.drawRect(lefttop, -right, -bottom, paint)
 ​
    }
 }

这里便是计算偏移量等,都比较简单:

542DD72464B89550F97E8BAD9EFE6FD5

由于咋们是view,并且已经测量了view的宽和高,所以超出的部分就不展现了

跑马灯动起来

这段代码比较简单,直接开一个animator即可

  private val animator by lazy {
    val animator = ObjectAnimator.ofFloat(this"currentSpeed"0f360f)
    animator.repeatCount = -1
    animator.interpolator = null
    animator.duration = 2000L
    animator
  }
 ​
 var currentSpeed = 0f
   set(value) {
     field = value
     invalidate()
  }
         
 override fun onDraw(canvas: Canvas) {
 ​
   // withSave 保存画布
   canvas.withSave {
     
   // 画布中心点旋转
   canvas.rotate(currentSpeed, width / 2f, height / 2f)
     // 制作突变view1 制作突变view2
    ...
  }
 }

14162A8D36FFE0BEB6CD9B9D5A67446F

‘去掉’里边

去除里边部分有2种办法

  • 办法一: 使用 clipOutPath() 来clip掉中心区域, 这个api对版别有要求
  • 办法二: 从头制作一个 RoundRect() 来掩盖掉中心区域

办法一:

 private val path by lazy {
     Path().also { it.addRoundRect(rectF, RADIUS, RADIUS, Path.Direction.CCW) }
 }
 ​
 override fun onDraw(canvas: Canvas) {
 ​
     // withSave 保存画布
     canvas.withSave {
       canvas.clipOutPath(path)
          // 画布中心点旋转
       canvas.rotate(currentSpeed, width / 2f, height / 2f)
       
       // 制作突变view1 ..view2...
    }
 }

办法二:

 override fun onDraw(canvas: Canvas) {
   // withSave 保存画布
   canvas.withSave {
 ​
     // 画布中心点旋转
     canvas.rotate(currentSpeed, width / 2f, height / 2f)
 ​
     // 制作突变view1
 ​
     // 制作突变view2
 ​
  }
 ​
   paint.color = Color.BLACK
   canvas.drawRoundRect(rectF, RADIUS, RADIUS, paint)
 }

来看看当时作用:

B9B3733C51780A7AFB53CBA080582B20

但是现在看起来还是有一点生硬, 能够让view突变一下

 private val color1 by lazy {
   LinearGradient(width * 1f,height / 2f,width * 1f,height * 1f,
     intArrayOf(Color.TRANSPARENTColor.RED), floatArrayOf(0f, 1f),
     Shader.TileMode.CLAMP
  )
 }
 ​
 private val color2 by lazy {
   LinearGradient( width / 2f,height / 2f,width / 2f, 0f,
     intArrayOf(Color.TRANSPARENTColor.GREEN), floatArrayOf(0f, 1f),
     Shader.TileMode.CLAMP
  )
 }
 ​
 override fun onDraw(canvas: Canvas) {
 //
   canvas.withSave {
     canvas.rotate(currentSpeed, width / 2f, height / 2f)
    ...
     // 制作突变view1
     paint.shader = color1
     canvas.drawRect(left1, top1, right1, bottom1, paint)
     paint.shader = null
 ​
     // 制作突变view2
     paint.shader = color2
     canvas.drawRect(left1, top1, -right1, -bottom1, paint)
     paint.shader = null
  }
 ​
   // 中心rect
   canvas.drawRoundRect(rectF, RADIUS, RADIUS, paint)
 }

这样一来,就更有感觉了

作用图:

FBFD3920C18DA5E6821CA08C9CFB8052

根本作用就完结了,那么如何给其他view也能够轻松的添加这个炫酷的边框呢?

很显然,view是办不到的,所以我们只能自定义viewgroup

代码没有改变,只是在自定义viewgroup时,onDraw() 不会回调, 由于viewgroup首要便是用来办理view的,所以要想制作viewgroup最好是重写dispatchDraw()办法,

在dispatchDraw()办法中,需要注意的是 super.dispatchDraw(canvas) , 这个super中会制作children,

所以为了防止 view被跑马灯布景掩盖,需要将super.dispatchDraw(canvas) 写到最终一行

 #ApertureViewGroup.kt
 ​
 override fun dispatchDraw(canvas: Canvas) {
         val left1 = width / 2f
         val top1 = height / 2f
 ​
         val right1 = left1 + width
         val bottom1 = top1 + width
         canvas.withSave {
             canvas.rotate(currentSpeed, width / 2f, height / 2f
             // 制作突变view1
             paint.shader = color1
             canvas.drawRect(left1, top1, right1, bottom1, paint)
             paint.shader = null
 ​
             if (mColor2 != -1) {
                 // 制作突变view2
                 paint.shader = color2
                 canvas.drawRect(left1, top1, -right1, -bottom1, paint)
                 paint.shader = null
            }
        }
 ​
         paint.color = mMiddleColor
         canvas.drawRoundRect(rectF, mBorderAngle, mBorderAngle, paint)
 ​
 // 必定要写到最终一行,否则children会被跑马灯掩盖掉
         super.dispatchDraw(canvas)
    }

最终在调用的时候直接:

 <ApertureViewGroup
     android:layout_width="200dp"
     android:layout_height="200dp"
 ​
     // 边框色彩
     android:background="@color/cccccc"
                                                       
 // 边框宽度                                            
     app:aperture_border_width="50dp"
                       
 // 边框角度
     app:aperture_border_angle="20dp"                                               
 
 // 突变色彩1
     app:aperture_color1="@color/purple_200"
                                                                                               
 // 突变色彩2 如果不写,默认只有一个突变在跑马灯
     app:aperture_color2="@color/color_FFC107"
                                                       
 // 旋转时间
     app:aperture_duration="3000"
                                                       
 // 中心空心色彩
     app:aperture_middle_color="@color/white">
 ​
     <XXXX View
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:gravity="center" />
 </com.example.customviewproject.f.f2.ApertureViewGroup>

本篇代码比较简单,不过这个思路的确挺好玩的!

最终作用:

A051CC6A0481AE320B2371E271889D04

完好代码

原创不易,您的点赞便是对我最大的帮助!

  • android 自定义View 视差动画
  • android自定义View: 制作图表(一)
  • android 自定义view: 矩形图表(二)
  • android 自定义View:仿QQ拖拽作用
  • android 浅析RecyclerView回收复用机制及实战(仿探探作用)