这是一个用于 Android 的自定义 View,模拟蓝牙扫描时的多波浪扩散动画效果。每个波浪的半径逐渐增大,透明度逐渐降低,形成连续的波纹扩散效果。通过调整动画的延迟时间和时长,确保波浪之间的间隙较小,动画流畅且美观。
主要特性:
多波浪扩散:
支持多个圆圈(波浪)依次扩散,形成连续的波纹效果。
每个圆圈的半径逐渐增大,透明度逐渐降低。
间隙较小:
通过调整动画的延迟时间和动画时长,确保波浪之间的间隙较小。
自定义View:
使用 Canvas 和 Paint 实现自定义绘制。
使用 ValueAnimator 实现平滑的动画效果。
适用场景:
蓝牙扫描界面。
雷达扫描效果。
其他需要波纹扩散动画的场景。
使用方法:
将 BluetoothScanView 添加到布局文件中。
在 Activity 中调用 startScan() 启动动画,调用 stopScan() 停止动画。
实现步骤
1. 自定义View
BluetoothScanView.kt
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View
class BluetoothScanView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val scanPaint = Paint().apply {
color = Color.BLUE
style = Paint.Style.STROKE
strokeWidth = 5f
isAntiAlias = true
}
private val circles = mutableListOf<Circle>()
private val animators = mutableListOf<ValueAnimator>()
private fun init() {
// 初始化圆圈和动画列表
circles.clear()
animators.clear()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val centerX = width / 2
val centerY = height / 2
// 绘制所有圆圈
for (circle in circles) {
scanPaint.alpha = circle.alpha
canvas.drawCircle(centerX.toFloat(), centerY.toFloat(), circle.radius.toFloat(), scanPaint)
}
}
fun startScan() {
if (animators.isNotEmpty()) return
init()
// 初始化3个圆圈
repeat(3) {
circles.add(Circle(0, 255))
}
// 为每个圆圈创建独立的动画
for ((i, circle) in circles.withIndex()) {
val animator = ValueAnimator.ofFloat(0f, 1f).apply {
duration = 1500
startDelay = i * 500L
repeatCount = ValueAnimator.INFINITE
repeatMode = ValueAnimator.RESTART
addUpdateListener {
val progress = animatedValue as Float
circle.radius = (progress * width / 2).toInt()
circle.alpha = (255 * (1 - progress)).toInt()
invalidate()
}
}
animators.add(animator)
animator.start()
}
}
fun stopScan() {
animators.forEach { it.cancel() }
animators.clear()
circles.clear()
invalidate()
}
// 圆圈类,用于存储半径和透明度
private data class Circle(var radius: Int, var alpha: Int)
}
注意:在Kotlin中,我们使用了@JvmOverloads注解来支持Java中的多构造函数特性。同时,通过使用apply、let、repeat等作用域函数简化了代码,并利用Kotlin的数据类(data class)特性定义了Circle类。此外,也对一些变量声明进行了调整,使其更符合Kotlin的习惯用法。
Activity代码:
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private lateinit var bluetoothScanView: BluetoothScanView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 初始化自定义View
bluetoothScanView = findViewById(R.id.bluetoothScanView)
// 确保View尺寸已确定后启动动画
bluetoothScanView.post {
bluetoothScanView.startScan() // 启动扫描动画
}
}
override fun onDestroy() {
super.onDestroy()
bluetoothScanView.stopScan() // 停止扫描动画
}
}
布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<!-- 自定义蓝牙扫描View -->
<com.example.BluetoothScanView
android:id="@+id/bluetoothScanView"
android:layout_width="300dp"
android:layout_height="300dp"
android:layout_centerInParent="true" />
</RelativeLayout>
运行效果
波浪扩散:
页面加载后,第一个圆圈开始扩散,随后第二个、第三个圆圈依次开始。
每个圆圈的半径逐渐增大,透明度逐渐降低。
间隙较小:
每个波浪之间的启动间隔为 500 毫秒,动画时长为 1500 毫秒,波浪之间的间隙较小。
连续波纹效果:
当一个圆圈的动画结束时,下一个圆圈的动画立即开始,形成连续的波纹效果。
动画循环:
动画无限循环,波纹效果持续不断。