android 自定义view: 跑马灯-光圈

Posted 史大拿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android 自定义view: 跑马灯-光圈相关的知识,希望对你有一定的参考价值。

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

**系统: **mac

android studio: 4.1.3

**kotlin version:**1.5.0

gradle: gradle-6.5-bin.zip

本篇效果:

前沿

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

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

实现思路

  • 将效果分为3层
    • 第一层: 背景
    • 第二层: 跑马灯光圈
    • 第三层: 展示区

如图所示:

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

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

将View圆角化

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

clipToOutline = true

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

来看看当前效果:

自定义跑马灯光圈

这几个字可能有点抽象,所以来看看要完成的效果:

接下来只需要吧黄框外面和里面的的去掉就完成了旋转的效果:

去掉外面:

去掉里面:

这都是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(left, top, right, bottom)
    

    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(left, top, right, bottom, paint)

        // 绘制渐变view2
        paint.color = Color.RED
        canvas.drawRect(left, top, -right, -bottom, paint)

    

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

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

跑马灯动起来

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

 private val animator by lazy 
   val animator = ObjectAnimator.ofFloat(this, "currentSpeed", 0f, 360f)
   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
    ...
  

'去掉’里面

去除里面部分有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)

来看看当前效果:

但是现在看起来还是有一点生硬, 可以让view渐变一下

private val color1 by lazy 
  LinearGradient(width * 1f,height / 2f,width * 1f,height * 1f,
    intArrayOf(Color.TRANSPARENT, Color.RED), floatArrayOf(0f, 1f),
    Shader.TileMode.CLAMP
  )


private val color2 by lazy 
  LinearGradient( width / 2f,height / 2f,width / 2f, 0f,
    intArrayOf(Color.TRANSPARENT, Color.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)

这样一来,就更有感觉了

效果图:

基本效果就完成了,那么如何给其他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>

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

最终效果:

完整代码

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

以上是关于android 自定义view: 跑马灯-光圈的主要内容,如果未能解决你的问题,请参考以下文章

相机光圈大小怎么区分

如何使用 CAFilter 为相机光圈动画制作动画?

光圈f8和光圈f6哪个大?

相机光圈是如何区分级数的?

曝光三要素光圈快门ISO的关系

单反使用技巧