Android 自定义粒子连线动画视图实现:打造炫酷背景效果

在移动应用开发中,动态视觉效果往往能为用户带来更优质的体验。本文将解析一个自定义的粒子连线视图ParticleLineView,该视图能够展示动态移动的粒子、粒子间的连接线连线以及粒子连线视图常用于应用的背景展示、欢迎页装饰等场景,通过粒子的随机运动和连线效果营造出科技感和动态美感。

一、核心功能与结构

ParticleLineView主要包含两大核心元素:

  • 粒子(Particle):用于计算和绘制连线
  • 漂浮圆点(FloatingDot):独立的灰色圆点装饰元素

整个视图的实现遵循Android自定义View的标准流程,主要包含以下几个部分:

  1. 自定义属性定义与获取
  2. 画笔(Paint)初始化
  3. 粒子与漂浮圆点的初始化
  4. 动态更新与绘制逻辑
  5. 对外接口(Setter方法)

二、实现细节解析

1. 自定义属性配置

为了使视图具备灵活性,通过attrs.xml定义了一系列可配置属性:

xml 复制代码
<!-- 示例属性定义 -->
<declare-styleable name="ParticleLineView">
    <attr name="particleCount" format="integer"/>
    <attr name="particleSize" format="dimension"/>
    <attr name="lineColor" format="color"/>
    <!-- 更多属性... -->
</declare-styleable>

在代码中通过TypedArray获取这些属性值:

java 复制代码
TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.ParticleLineView);
particleCount = ta.getInt(R.styleable.ParticleLineView_particleCount, particleCount);
particleSize = ta.getDimension(R.styleable.ParticleLineView_particleSize, particleSize);
// 其他属性获取...
ta.recycle();

2. 画笔初始化

分别为线条和漂浮圆点创建了不同的画笔:

java 复制代码
// 线条画笔配置
linePaint.setColor(lineColor);
linePaint.setStrokeWidth(lineWidth);
linePaint.setStyle(Paint.Style.STROKE);
linePaint.setAntiAlias(true);

// 圆点画笔配置
dotPaint.setColor(dotColor);
dotPaint.setStyle(Paint.Style.FILL);
dotPaint.setAntiAlias(true);

3. 粒子系统初始化

等待视图尺寸确定后(通过OnGlobalLayoutListener),初始化粒子和漂浮圆点:

java 复制代码
// 粒子初始化
private void initParticles() {
    particles.clear();
    int width = getWidth();
    int height = getHeight();
    for (int i = 0; i < particleCount; i++) {
        Particle particle = new Particle();
        particle.x = random.nextInt(width);
        particle.y = random.nextInt(height);
        // 随机速度(-max ~ max)
        particle.vx = (random.nextFloat() - 0.5f) * 2 * maxParticleSpeed;
        particle.vy = (random.nextFloat() - 0.5f) * 2 * maxParticleSpeed;
        particles.add(particle);
    }
}

漂浮圆点的初始化逻辑类似,但增加了大小随机化:

java 复制代码
dot.size = minDotSize + random.nextFloat() * (maxDotSize - minDotSize);

4. 动态绘制逻辑

核心逻辑在onDraw()方法中实现,主要包含三个步骤:

  1. 更新位置
java 复制代码
// 更新粒子位置并检测边界
for (Particle particle : particles) {
    particle.x += particle.vx;
    particle.y += particle.vy;
    
    // 边界反弹
    if (particle.x < 0 || particle.x > width) {
        particle.vx = -particle.vx;
    }
    if (particle.y < 0 || particle.y > height) {
        particle.vy = -particle.vy;
    }
}
  1. 绘制连线
java 复制代码
// 只绘制距离小于maxLineDistance的粒子间连线
for (int i = 0; i < particles.size(); i++) {
    Particle p1 = particles.get(i);
    for (int j = i + 1; j < particles.size(); j++) {
        Particle p2 = particles.get(j);
        float distance = calculateDistance(p1, p2);
        if (distance < maxLineDistance) {
            canvas.drawLine(p1.x, p1.y, p2.x, p2.y, linePaint);
        }
    }
}
  1. 绘制漂浮圆点
java 复制代码
for (FloatingDot dot : floatingDots) {
    canvas.drawCircle(dot.x, dot.y, dot.size, dotPaint);
}
  1. 动画循环
    通过postInvalidateDelayed(16)实现约60fps的动画效果:
java 复制代码
// 重绘(继续动画)
postInvalidateDelayed(16);

5. 对外接口设计

提供了一系列Setter方法允许动态调整视图效果:

java 复制代码
// 调整线条颜色
public void setLineColor(int color) {
    this.lineColor = color;
    linePaint.setColor(color);
    invalidate();
}

// 调整粒子速度
public void setMaxParticleSpeed(int speed) {
    this.maxParticleSpeed = speed;
    // 更新所有粒子速度
    for (Particle particle : particles) {
        particle.vx = (random.nextFloat() - 0.5f) * 2 * maxParticleSpeed;
        particle.vy = (random.nextFloat() - 0.5f) * 2 * maxParticleSpeed;
    }
    invalidate();
}

三、使用方法

  1. 在布局文件中添加:
xml 复制代码
<com.example.ddddddddd.ParticleLineView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:particleCount="50"
    app:lineColor="@color/gray"
    app:maxLineDistance="250"/>
  1. 在代码中动态调整:
java 复制代码
ParticleLineView particleView = findViewById(R.id.particleView);
particleView.setLineColor(Color.BLUE);
particleView.setMaxParticleSpeed(5);

四、优化建议

  1. 性能优化:当前粒子连线采用双重循环计算,当粒子数量较多时(>100)可能影响性能,可考虑使用空间分区算法减少距离计算次数。

  2. 功能扩展

    • 添加粒子点击交互效果
    • 支持粒子连线的渐变色
    • 增加粒子聚合/扩散动画
  3. 属性完善:可增加更多自定义属性,如粒子颜色、连线透明度范围等。

五、完整代码

java 复制代码
package com.example.ddddddddd;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewTreeObserver;

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

public class ParticleLineView extends View {
    /*** 粒子列表 用于计算线条位置 */
    private List<Particle> particles = new ArrayList<>();
    /*** 漂浮的灰色圆点列表 */
    private List<FloatingDot> floatingDots = new ArrayList<>();

    /*** 画笔 */
    private Paint linePaint = new Paint();
    /*** 灰色圆点画笔 */
    private Paint dotPaint = new Paint();

    /*** 粒子属性 */
    private int particleCount = 50;
    private float particleSize = 4f;

    /*** 线条属性 */
    private int lineColor = Color.GRAY;
    private float lineWidth = 1f;
    private int maxLineDistance = 250;

    /*** 粒子移动速度 */
    private int maxParticleSpeed = 3;

    /*** 圆点数量 */
    private int dotCount = 30;
    /*** 最小圆点大小 */
    private float minDotSize = 2f;
    /*** 最大圆点大小 */
    private float maxDotSize = 6f;
    /*** 圆点颜色 */
    private int dotColor = Color.GRAY;
    /*** 圆点最大移动速度 */
    private int maxDotSpeed = 2;

    private Random random = new Random();
    private boolean isInitialized = false;

    public ParticleLineView(Context context) {
        super(context);
        init(null);
    }

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

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

    private void init(AttributeSet attrs) {
        // 读取自定义属性
        if (attrs != null) {
            TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.ParticleLineView);

            particleCount = ta.getInt(R.styleable.ParticleLineView_particleCount, particleCount);
            particleSize = ta.getDimension(R.styleable.ParticleLineView_particleSize, particleSize);

            lineColor = ta.getColor(R.styleable.ParticleLineView_lineColor, lineColor);
            lineWidth = ta.getDimension(R.styleable.ParticleLineView_lineWidth, lineWidth);
            maxLineDistance = ta.getInt(R.styleable.ParticleLineView_maxLineDistance, maxLineDistance);

            maxParticleSpeed = ta.getInt(R.styleable.ParticleLineView_maxParticleSpeed, maxParticleSpeed);

            // 灰色圆点的自定义属性
            dotCount = ta.getInt(R.styleable.ParticleLineView_dotCount, dotCount);
            minDotSize = ta.getDimension(R.styleable.ParticleLineView_minDotSize, minDotSize);
            maxDotSize = ta.getDimension(R.styleable.ParticleLineView_maxDotSize, maxDotSize);
            dotColor = ta.getColor(R.styleable.ParticleLineView_dotColor, dotColor);
            maxDotSpeed = ta.getInt(R.styleable.ParticleLineView_maxDotSpeed, maxDotSpeed);

            ta.recycle();
        }

        // 配置线条画笔 - 设置固定不透明
        linePaint.setColor(lineColor);
        linePaint.setStrokeWidth(lineWidth);
        linePaint.setStyle(Paint.Style.STROKE);
        linePaint.setAntiAlias(true);
        linePaint.setAlpha(255);  // 强制设置为完全不透明

        // 配置灰色圆点画笔
        dotPaint.setColor(dotColor);
        dotPaint.setStyle(Paint.Style.FILL);
        dotPaint.setAntiAlias(true);

        // 等待视图尺寸确定后初始化
        getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                if (!isInitialized) {
                    initParticles();
                    initFloatingDots();
                    isInitialized = true;

                    // 移除监听器
                    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
                        getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    } else {
                        getViewTreeObserver().removeGlobalOnLayoutListener(this);
                    }

                    // 开始动画
                    postInvalidateDelayed(16);
                }
            }
        });
    }

    // 初始化粒子(用于计算线条位置)
    private void initParticles() {
        particles.clear();
        int width = getWidth();
        int height = getHeight();

        for (int i = 0; i < particleCount; i++) {
            Particle particle = new Particle();
            particle.x = random.nextInt(width);
            particle.y = random.nextInt(height);
            particle.vx = (random.nextFloat() - 0.5f) * 2 * maxParticleSpeed;
            particle.vy = (random.nextFloat() - 0.5f) * 2 * maxParticleSpeed;
            particles.add(particle);
        }
    }

    // 初始化漂浮的灰色圆点
    private void initFloatingDots() {
        floatingDots.clear();
        int width = getWidth();
        int height = getHeight();

        for (int i = 0; i < dotCount; i++) {
            FloatingDot dot = new FloatingDot();
            dot.x = random.nextInt(width);
            dot.y = random.nextInt(height);
            dot.size = minDotSize + random.nextFloat() * (maxDotSize - minDotSize);
            dot.vx = (random.nextFloat() - 0.5f) * 2 * maxDotSpeed;
            dot.vy = (random.nextFloat() - 0.5f) * 2 * maxDotSpeed;
            floatingDots.add(dot);
        }
    }

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

        if (particles.isEmpty() || floatingDots.isEmpty()) {
            return;
        }

        int width = getWidth();
        int height = getHeight();

        // 更新粒子位置
        for (Particle particle : particles) {
            particle.x += particle.vx;
            particle.y += particle.vy;

            // 边界检测
            if (particle.x < 0 || particle.x > width) {
                particle.vx = -particle.vx;
            }
            if (particle.y < 0 || particle.y > height) {
                particle.vy = -particle.vy;
            }

            particle.x = Math.max(0, Math.min(particle.x, width));
            particle.y = Math.max(0, Math.min(particle.y, height));
        }

        // 更新灰色圆点位置
        for (FloatingDot dot : floatingDots) {
            dot.x += dot.vx;
            dot.y += dot.vy;

            // 边界检测
            if (dot.x < 0 || dot.x > width) {
                dot.vx = -dot.vx;
            }
            if (dot.y < 0 || dot.y > height) {
                dot.vy = -dot.vy;
            }

            dot.x = Math.max(0, Math.min(dot.x, width));
            dot.y = Math.max(0, Math.min(dot.y, height));
        }

        // 绘制连接线 - 移除透明度变化,保持统一宽度和颜色
        for (int i = 0; i < particles.size(); i++) {
            Particle p1 = particles.get(i);
            for (int j = i + 1; j < particles.size(); j++) {
                Particle p2 = particles.get(j);

                float distance = calculateDistance(p1, p2);
                if (distance < maxLineDistance) {
                    // 移除随距离变化的透明度设置,保持画笔初始设置
                    canvas.drawLine(p1.x, p1.y, p2.x, p2.y, linePaint);
                }
            }
        }

        // 绘制漂浮的灰色圆点
        for (FloatingDot dot : floatingDots) {
            canvas.drawCircle(dot.x, dot.y, dot.size, dotPaint);
        }

        // 重绘(继续动画)
        postInvalidateDelayed(16);
    }

    // 计算两个粒子之间的距离
    private float calculateDistance(Particle p1, Particle p2) {
        float dx = p1.x - p2.x;
        float dy = p1.y - p2.y;
        return (float) Math.sqrt(dx * dx + dy * dy);
    }

    // 粒子类(用于计算线条位置)
    private class Particle {
        float x;
        float y;
        float vx;  // x方向速度
        float vy;  // y方向速度
    }

    // 漂浮的灰色圆点类
    private class FloatingDot {
        float x;
        float y;
        float size;  // 圆点大小
        float vx;    // x方向速度
        float vy;    // y方向速度
    }

    // Setter方法 - 保持原有的调整方法
    public void setParticleCount(int count) {
        this.particleCount = count;
        initParticles();
        invalidate();
    }

    public void setParticleSize(float size) {
        this.particleSize = size;
        invalidate();
    }

    // 调整线条颜色
    public void setLineColor(int color) {
        this.lineColor = color;
        linePaint.setColor(color);
        invalidate();
    }

    // 调整线条宽度
    public void setLineWidth(float width) {
        this.lineWidth = width;
        linePaint.setStrokeWidth(width);
        invalidate();
    }

    public void setMaxLineDistance(int distance) {
        this.maxLineDistance = distance;
        invalidate();
    }

    public void setMaxParticleSpeed(int speed) {
        this.maxParticleSpeed = speed;
        for (Particle particle : particles) {
            particle.vx = (random.nextFloat() - 0.5f) * 2 * maxParticleSpeed;
            particle.vy = (random.nextFloat() - 0.5f) * 2 * maxParticleSpeed;
        }
        invalidate();
    }

    // 灰色圆点的控制方法
    public void setDotCount(int count) {
        this.dotCount = count;
        initFloatingDots();
        invalidate();
    }

    public void setDotColor(int color) {
        this.dotColor = color;
        dotPaint.setColor(color);
        invalidate();
    }

    public void setMaxDotSpeed(int speed) {
        this.maxDotSpeed = speed;
        for (FloatingDot dot : floatingDots) {
            dot.vx = (random.nextFloat() - 0.5f) * 2 * maxDotSpeed;
            dot.vy = (random.nextFloat() - 0.5f) * 2 * maxDotSpeed;
        }
        invalidate();
    }
}

六、总结

ParticleLineView通过面向对象的设计思想,将粒子和漂浮圆点封装为独立的数据结构,实现了一个可配置、可扩展的动态粒子连线效果。该实现遵循了Android自定义View的最佳实践,通过属性配置和Setter方法提供了良好的灵活性,适合作为应用中的动态背景使用。

这种动态视觉效果的实现核心在于理解View的绘制流程和动画循环机制,通过不断更新元素位置并触发重绘,从而形成连续的动画效果。

相关推荐
言慢行善14 小时前
sqlserver模糊查询问题
java·数据库·sqlserver
专吃海绵宝宝菠萝屋的派大星14 小时前
使用Dify对接自己开发的mcp
java·服务器·前端
大数据新鸟14 小时前
操作系统之虚拟内存
java·服务器·网络
Tong Z14 小时前
常见的限流算法和实现原理
java·开发语言
凭君语未可14 小时前
Java 中的实现类是什么
java·开发语言
He少年14 小时前
【基础知识、Skill、Rules和MCP案例介绍】
java·前端·python
克里斯蒂亚诺更新15 小时前
myeclipse的pojie
java·ide·myeclipse
迷藏49415 小时前
**eBPF实战进阶:从零构建网络流量监控与过滤系统**在现代云原生架构中,**网络可观测性**和**安全隔离**已成为
java·网络·python·云原生·架构
迷藏49415 小时前
**发散创新:基于Solid协议的Web3.0去中心化身份认证系统实战解析**在Web3.
java·python·web3·去中心化·区块链