本篇文章已授权微信大众号 guolin_blog (郭霖)独家发布[2022-12-02]
本系列自定义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 视差动画
- android自定义View: 制作图表(一)
- android 自定义view: 矩形图表(二)
- android 自定义View:仿QQ拖拽作用
- android 浅析RecyclerView回收复用机制及实战(仿探探作用)