当前位置: 首页 > news >正文

Android 自定义蓝牙扫描动画:多波浪扩散效果

这是一个用于 Android 的自定义 View,模拟蓝牙扫描时的多波浪扩散动画效果。每个波浪的半径逐渐增大,透明度逐渐降低,形成连续的波纹扩散效果。通过调整动画的延迟时间和时长,确保波浪之间的间隙较小,动画流畅且美观。

主要特性
多波浪扩散:

支持多个圆圈(波浪)依次扩散,形成连续的波纹效果。

每个圆圈的半径逐渐增大,透明度逐渐降低。

间隙较小:

通过调整动画的延迟时间和动画时长,确保波浪之间的间隙较小。

自定义View:

使用 Canvas 和 Paint 实现自定义绘制。

使用 ValueAnimator 实现平滑的动画效果。

适用场景:
蓝牙扫描界面。

雷达扫描效果。

其他需要波纹扩散动画的场景。

使用方法:
BluetoothScanView 添加到布局文件中。

在 Activity 中调用 startScan() 启动动画,调用 stopScan() 停止动画。

实现步骤
1. 自定义View

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;

import java.util.ArrayList;
import java.util.List;

public class BluetoothScanView extends View {

    private Paint scanPaint; // 画笔
    private List<Circle> circles; // 存储圆圈的列表
    private List<ValueAnimator> animators; // 存储动画的列表

    public BluetoothScanView(Context context) {
        super(context);
        init();
    }

    public BluetoothScanView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public BluetoothScanView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        // 初始化画笔
        scanPaint = new Paint();
        scanPaint.setColor(Color.BLUE); // 设置颜色
        scanPaint.setStyle(Paint.Style.STROKE); // 设置样式为描边
        scanPaint.setStrokeWidth(5); // 设置描边宽度
        scanPaint.setAntiAlias(true); // 抗锯齿

        // 初始化圆圈和动画列表
        circles = new ArrayList<>();
        animators = new ArrayList<>();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        int centerX = getWidth() / 2; // 中心点X坐标
        int centerY = getHeight() / 2; // 中心点Y坐标

        // 绘制所有圆圈
        for (Circle circle : circles) {
            scanPaint.setAlpha(circle.alpha); // 设置透明度
            canvas.drawCircle(centerX, centerY, circle.radius, scanPaint);
        }
    }

    public void startScan() {
        if (!animators.isEmpty()) {
            return; // 如果动画已经在运行,直接返回
        }

        // 初始化3个圆圈
        circles.clear();
        circles.add(new Circle(0, 255)); // 第一个圆圈
        circles.add(new Circle(0, 255)); // 第二个圆圈
        circles.add(new Circle(0, 255)); // 第三个圆圈

        // 为每个圆圈创建独立的动画
        for (int i = 0; i < circles.size(); i++) {
            final Circle circle = circles.get(i);
            ValueAnimator animator = ValueAnimator.ofFloat(0, 1); // 动画范围从0到1
            animator.setDuration(1500); // 动画时长1.5秒
            animator.setStartDelay(i * 500); // 每个圆圈延迟0.5秒启动
            animator.setRepeatCount(ValueAnimator.INFINITE); // 无限循环
            animator.setRepeatMode(ValueAnimator.RESTART); // 重新开始
            animator.addUpdateListener(animation -> {
                float progress = (float) animation.getAnimatedValue(); // 获取当前进度
                circle.radius = (int) (progress * getWidth() / 2); // 更新半径
                circle.alpha = (int) (255 * (1 - progress)); // 更新透明度
                invalidate(); // 触发重绘
            });
            animators.add(animator); // 添加到动画列表
            animator.start(); // 启动动画
        }
    }

    public void stopScan() {
        // 停止所有动画
        for (ValueAnimator animator : animators) {
            animator.cancel();
        }
        animators.clear();
        circles.clear();
        invalidate(); // 刷新界面
    }

    // 圆圈类,用于存储半径和透明度
    private static class Circle {
        int radius; // 半径
        int alpha; // 透明度

        Circle(int radius, int alpha) {
            this.radius = radius;
            this.alpha = alpha;
        }
    }
}

2. Activity

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    private BluetoothScanView bluetoothScanView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 初始化自定义View
        bluetoothScanView = findViewById(R.id.bluetoothScanView);

        // 确保View尺寸已确定后启动动画
        bluetoothScanView.post(() -> {
            bluetoothScanView.startScan(); // 启动扫描动画
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        bluetoothScanView.stopScan(); // 停止扫描动画
    }
}

3. 布局文件

<?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 毫秒,波浪之间的间隙较小。

连续波纹效果:

当一个圆圈的动画结束时,下一个圆圈的动画立即开始,形成连续的波纹效果。

动画循环:

动画无限循环,波纹效果持续不断。

相关文章:

  • vue启动 localhost无法访问
  • 了解一下HTTP的短连接和长连接
  • 计算机视觉算法实战——手势识别(主页有源码)
  • Linux Shell脚本-实现账户库数据同步到交易库
  • kvm 创建虚拟机核心分析
  • 双指针算法专题之——复写零
  • CLR中的类型转换
  • 玩转python:通俗易懂掌握高级数据结构:collections模块之deque
  • C++中类对象作为类成员(对象成员/成员对象)的一些注意事项
  • vue2的webpack(vue.config.js) 怎么使用请求转发 devServer.proxy
  • AGI大模型(5):提示词工程
  • ubuntu20.04
  • 铁人三项(第五赛区)_2018_rop题解
  • 《算法笔记》8.1小节——搜索专题->深度优先搜索(DFS)问题 D: 【递归入门】n皇后 问题(原始的8皇后问题)
  • 我又又又又又又更新了~~纯手工编写C++画图,有注释~~~
  • 【C#】使用DeepSeek帮助评估数据库性能问题,C# 使用定时任务,每隔一分钟移除一次表,再重新创建表,和往新创建的表追加5万多条记录
  • USER与多组织关联的SQL查询以及几个关键函数用法
  • ​面向对象与面向过程编程:从概念到实战的深度解析
  • ROS学习过程(一)
  • unity几种设计模式(自用)
  • 做公司网站哪里好/seo快速排名工具
  • wordpress oss/seo综合查询站长工具怎么用
  • 淘宝客建设网站/宁波免费建站seo排名
  • 企业做网站400电话作用/网站优化联系
  • 工商局网站查询入口/深圳短视频推广
  • 宣传网站制作方案/怎么做自媒体