Android实现自定义动态壁纸(附带源码)

一、项目简介

1.1 什么是动态壁纸

动态壁纸(Live Wallpaper)是 Android 平台提供的一种特殊类型的壁纸,它可以在用户主屏幕或锁屏界面上显示动画、交互或实时渲染效果,而不仅仅是静态图片。它可以响应系统事件、传感器输入、触摸等,为用户带来更生动的桌面体验。

1.2 本项目目标

本项目旨在通过自定义 Live Wallpaper,实现一个带有以下功能的动态壁纸示例:

  • 帧动画:定时渲染帧序列,播放循环动画。

  • 触摸交互:响应用户在桌面上的触摸事件,产生涟漪、粒子或其他视觉效果。

  • 传感器响应:可选地响应重力感应(加速度传感器)或陀螺仪,实现视差或倾斜效果。

  • 性能优化:使用 SurfaceHolder + Canvas 或 OpenGL ES 渲染,并控制帧率以节省电量。

  • 可配置参数:通过设置界面(Settings Activity)让用户调整动画速度、颜色、特效开关等。

最终你将获得一个可打包成 APK、安装后可在“壁纸”选择列表中看到并启用的 Live Wallpaper。


二、相关技术与知识点

2.1 WallpaperService 与 Engine

  • WallpaperService:系统用来托管动态壁纸的 Service。

  • WallpaperService.Engine:每个动态壁纸实例的“引擎”,负责处理渲染、触摸、生命周期回调等。

你需要继承 WallpaperService 并在其中返回一个自定义的 Engine 实例。

2.2 SurfaceHolder 与 Canvas 渲染

在 Engine 的 onSurfaceCreatedonSurfaceChangedonSurfaceDestroyed 中管理 SurfaceHolder。通过 holder.lockCanvas() / unlockCanvasAndPost() 获取 Canvas 并绘制。

2.3 Handler 或 Choreographer 控制帧率

  • Handler + Runnable:在 handleMessagepostDelayed 中循环发送绘制请求。

  • Choreographer:精确同步到 vsync,推荐用于高帧率需求。

2.4 触摸事件

在 Engine 中重写 onTouchEvent(MotionEvent event),接收桌面触摸,并将事件用于动画效果。

2.5 传感器管理

通过 SensorManager 注册加速度或陀螺仪监听器,在 Engine 中接收数据,并在绘制时根据传感器数据偏移或变换画布。


三、项目实现思路

  1. 模块划分

    • MyWallpaperService:继承自 WallpaperService,返回 MyEngine

    • MyEngine:继承自 WallpaperService.Engine,管理 Surface、绘制循环、触摸、传感器。

    • SettingsActivity:提供用户界面调整动画参数,并将设置通过 SharedPreferences 传递给 Engine。

  2. 渲染循环

    • 使用 Handler 在固定间隔(如 16ms)发送 MSG_DRAW,触发 drawFrame()

    • drawFrame() 中锁定 Canvas,清屏,绘制当前帧动画或粒子,然后解锁提交。

  3. 动画数据结构

    • 帧动画:将多张 Bitmap 载入内存,按索引循环显示。

    • 粒子效果:维护一个粒子列表,每帧更新位置、透明度等属性并绘制。

  4. 触摸与传感器

    • 触摸:记录触点坐标,生成涟漪或新粒子。

    • 传感器:根据重力方向调整绘制偏移,产生视差。

  5. 生命周期管理

    • onVisibilityChanged:壁纸可见时启动渲染循环,不可见时停止,节省资源。

    • onSurfaceDestroyed:停止线程或 Handler,释放资源。


四、完整项目代码

package com.example.livewallpaper;

import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.*;
import android.service.wallpaper.WallpaperService;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Handler;
import android.os.Message;

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

/**
 * 自定义动态壁纸 Service
 */
public class MyWallpaperService extends WallpaperService {
    @Override
    public Engine onCreateEngine() {
        return new MyEngine();
    }

    private class MyEngine extends Engine implements SensorEventListener {
        private final int MSG_DRAW = 0;
        private SurfaceHolder holder;
        private boolean visible;
        private int width, height;

        private Handler handler = new Handler(msg -> {
            if (msg.what == MSG_DRAW) {
                drawFrame();
                if (visible) {
                    handler.sendEmptyMessageDelayed(MSG_DRAW, frameInterval);
                }
            }
            return true;
        });

        // 动画参数
        private long frameInterval = 16; // ms, ~60fps
        private Bitmap[] frames;
        private int currentFrame = 0;
        private int frameCount;

        // 粒子列表
        private List<Particle> particles = new ArrayList<>();

        // 触摸涟漪效果
        private float touchX, touchY;
        private boolean touched;

        // 传感器
        private SensorManager sensorManager;
        private float accelX, accelY;

        // 配置
        private SharedPreferences prefs;

        MyEngine() {
            holder = getSurfaceHolder();
            prefs = getSharedPreferences("wallpaper_prefs", Context.MODE_PRIVATE);

            // 载入帧动画资源
            frameCount = 10;
            frames = new Bitmap[frameCount];
            for (int i = 0; i < frameCount; i++) {
                int resId = getResources().getIdentifier(
                    "frame_" + i, "drawable", getPackageName());
                frames[i] = BitmapFactory.decodeResource(
                    getResources(), resId);
            }

            // 传感器初始化
            sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
            Sensor accel = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
            sensorManager.registerListener(this, accel, SensorManager.SENSOR_DELAY_GAME);

            setTouchEventsEnabled(true);
        }

        @Override
        public void onVisibilityChanged(boolean visible) {
            this.visible = visible;
            if (visible) {
                handler.sendEmptyMessage(MSG_DRAW);
            } else {
                handler.removeMessages(MSG_DRAW);
            }
        }

        @Override
        public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            this.width = width;
            this.height = height;
            super.onSurfaceChanged(holder, format, width, height);
        }

        @Override
        public void onSurfaceDestroyed(SurfaceHolder holder) {
            super.onSurfaceDestroyed(holder);
            visible = false;
            handler.removeCallbacksAndMessages(null);
            sensorManager.unregisterListener(this);
        }

        @Override
        public void onTouchEvent(MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                touchX = event.getX();
                touchY = event.getY();
                touched = true;
                // 生成一些粒子
                for (int i = 0; i < 20; i++) {
                    particles.add(new Particle(touchX, touchY));
                }
            }
            super.onTouchEvent(event);
        }

        private void drawFrame() {
            Canvas canvas = null;
            try {
                canvas = holder.lockCanvas();
                if (canvas != null) {
                    // 清屏
                    canvas.drawColor(Color.BLACK);

                    // 根据传感器数据偏移画布,制造视差
                    canvas.save();
                    float offsetX = accelX / SensorManager.GRAVITY_EARTH * 50;
                    float offsetY = accelY / SensorManager.GRAVITY_EARTH * 50;
                    canvas.translate(offsetX, offsetY);

                    // 绘制帧动画
                    Bitmap bmp = frames[currentFrame];
                    float cx = (width - bmp.getWidth()) / 2f;
                    float cy = (height - bmp.getHeight()) / 2f;
                    canvas.drawBitmap(bmp, cx, cy, null);
                    currentFrame = (currentFrame + 1) % frameCount;

                    // 绘制粒子
                    for (int i = particles.size() - 1; i >= 0; i--) {
                        Particle p = particles.get(i);
                        if (p.isAlive()) {
                            p.update();
                            p.draw(canvas);
                        } else {
                            particles.remove(i);
                        }
                    }

                    canvas.restore();
                }
            } finally {
                if (canvas != null) {
                    holder.unlockCanvasAndPost(canvas);
                }
            }
        }

        @Override
        public void onAccuracyChanged(Sensor sensor, int accuracy) { }

        @Override
        public void onSensorChanged(SensorEvent event) {
            accelX = event.values[0];
            accelY = event.values[1];
        }
    }

    /**
     * 简单粒子类,用于触摸产生的效果
     */
    private static class Particle {
        float x, y;
        float vx, vy;
        float alpha;
        Paint paint = new Paint();

        Particle(float startX, float startY) {
            x = startX; y = startY;
            vx = (float)(Math.random() * 10 - 5);
            vy = (float)(Math.random() * 10 - 5);
            alpha = 255;
            paint.setColor(Color.WHITE);
        }

        void update() {
            x += vx;
            y += vy;
            alpha -= 5;
            if (alpha < 0) alpha = 0;
            paint.setAlpha((int)alpha);
        }

        boolean isAlive() {
            return alpha > 0;
        }

        void draw(Canvas canvas) {
            canvas.drawCircle(x, y, 8, paint);
        }
    }
}

五、代码解读

  • MyWallpaperService & MyEngine 构造

    • 载入帧动画资源数组;

    • 初始化传感器监听与触摸事件;

    • 读取用户设置(若有)。

  • onVisibilityChanged

    • 控制渲染循环的启动与停止,避免壁纸不可见时仍占用 CPU。

  • onSurfaceChanged / onSurfaceDestroyed

    • 获取画布宽高,停止渲染并释放传感器。

  • onTouchEvent

    • 捕获 ACTION_DOWN,记录触点并生成粒子,触发交互效果。

  • drawFrame

    • 锁定 Canvas,清屏;

    • 根据加速度传感器偏移画布,制造视差;

    • 绘制中心帧动画;

    • 遍历并更新粒子列表,绘制存活粒子;

    • 解锁并提交 Canvas。

  • Particle 类

    • 保存位置、速度、透明度;

    • 每帧更新位置与透明度,并绘制圆点;

    • 透明度耗尽后标记死亡,移出列表。


六、项目总结与拓展

6.1 项目亮点

  • 多种动态效果:帧动画、粒子系统、视差交互;

  • 性能可控:基于 SurfaceHolder + Handler,可根据需要调整帧率;

  • 用户可交互:支持触摸和传感器,增强沉浸感;

  • 结构清晰:Service → Engine → 渲染循环 → 效果模块分离。

6.2 可拓展方向

拓展项实现思路
OpenGL ES 渲染使用 GLSurfaceView 或 EGLContext 提升渲染效率,支持 3D 效果。
设置界面提供 SettingsActivity,允许用户调整帧率、特效开关、背景色等。
多点触控支持onTouchEvent 中处理多指事件,产生更多复杂交互。
网络数据驱动从网络获取实时数据(如天气、时间),根据数据动态改变壁纸。
动态壁纸插件化将特效模块化,用户可在市场下载或切换不同的特效插件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值