android 自定义view: 跑马灯-光圈
创始人
2024-02-26 22:44:44
0

本系列自定义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(0, 0, 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.dpval DEF_HEIGHT = DEF_WIDTHprivate val RADIUS = 20.dp}private val paint = Paint(Paint.ANTI_ALIAS_FLAG)private val rectF by lazy {val left = 0f + RADIUS / 2fval top = 0f + RADIUS / 2fval right = left + DEF_WIDTH - RADIUSval bottom = top + DEF_HEIGHT - RADIUSRectF(left, top, right, bottom)}override fun onDraw(canvas: Canvas) {val left = rectF.left + rectF.width() / 2fval right = rectF.right + rectF.width()val top = rectF.top + rectF.height() / 2fval bottom = rectF.bottom + rectF.height() / 2f// 绘制渐变view1paint.color = Color.GREENcanvas.drawRect(left, top, right, bottom, paint)// 绘制渐变view2paint.color = Color.REDcanvas.drawRect(left, top, -right, -bottom, paint)}
}

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

542DD72464B89550F97E8BAD9EFE6FD5

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

跑马灯动起来

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

 private val animator by lazy {val animator = ObjectAnimator.ofFloat(this, "currentSpeed", 0f, 360f)animator.repeatCount = -1animator.interpolator = nullanimator.duration = 2000Lanimator}var currentSpeed = 0fset(value) {field = valueinvalidate()}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.BLACKcanvas.drawRoundRect(rectF, RADIUS, RADIUS, paint)
}

来看看当前效果:

B9B3733C51780A7AFB53CBA080582B20

但是现在看起来还是有一点生硬, 可以让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)...// 绘制渐变view1paint.shader = color1canvas.drawRect(left1, top1, right1, bottom1, paint)paint.shader = null// 绘制渐变view2paint.shader = color2canvas.drawRect(left1, top1, -right1, -bottom1, paint)paint.shader = null}// 中间rectcanvas.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.ktoverride fun dispatchDraw(canvas: Canvas) {val left1 = width / 2fval top1 = height / 2fval right1 = left1 + widthval bottom1 = top1 + widthcanvas.withSave {canvas.rotate(currentSpeed, width / 2f, height / 2f// 绘制渐变view1paint.shader = color1canvas.drawRect(left1, top1, right1, bottom1, paint)paint.shader = nullif (mColor2 != -1) {// 绘制渐变view2paint.shader = color2canvas.drawRect(left1, top1, -right1, -bottom1, paint)paint.shader = null}}paint.color = mMiddleColorcanvas.drawRoundRect(rectF, mBorderAngle, mBorderAngle, paint)// 一定要写到最后一行,否则children会被跑马灯覆盖掉super.dispatchDraw(canvas)}

最后在调用的时候直接:



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

最终效果:

A051CC6A0481AE320B2371E271889D04

完整代码

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

  • android 自定义View 视差动画

  • android自定义View: 绘制图表(一)

  • android 自定义view: 矩形图表(二)

  • android 自定义View:仿QQ拖拽效果

  • android 浅析RecyclerView回收复用机制及实战(仿探探效果)

相关内容

热门资讯

一个月内收两封监管函,瑞茂通怎... 12月19日,瑞茂通(600180)公告,于2025年12月19日收到监管工作函,监管机构就公司信息...
律师的作用 我以前自己打官司都是当被告,想办法不让对方讹诈就行,感觉律师没啥用,只是在法庭替你发言而已,孩子谢浩...
120多万卡宴只卖60万!海南... 12月20日,话题#海南封关120多万卡宴只要60万#冲上热搜,引发公众热议。 据媒体报道,12月1...
女子醉驾找人“摆平”被骗7万后... 因醉酒驾驶轻信他人“可摆平”的谎言被骗,女子葛某乙不堪压力自杀身亡。在实施诈骗的苏某被判刑并赔偿后,...
政策护航,智能建造企业“出海”... 长沙晚报掌上长沙12月21日讯(全媒体记者 刘嘉)近日,长沙市智能建造产业链推进办公室印发了《关于推...
男子一家三口被发小抢劫杀害!律... 河南中牟男子一家三口被发小抢劫杀害案12月23日将开庭。 受害者家属梁先生称,2025年7月,他的...
尹锡悦辩称对妻子涉嫌受贿“不知... 当地时间12月21日,韩国“金建希特检组”称已完成对前总统尹锡悦及其妻子金建希的面对面讯问,将在一周...
原创 古... 如果你穿越回古代,又穷又病还没家人,会不会直接凉凉? 不要着急,说不定当时的朝廷会给你分房住、发米粮...
湖北一男子当街拦车砸玻璃,警方... 新京报记者 贺俊怡 编辑 罗伟伟 ▲新京报我们视频出品(ID:wevideo) 12月20日,湖北大...