一、项目简介
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 的 onSurfaceCreated
、onSurfaceChanged
、onSurfaceDestroyed
中管理 SurfaceHolder
。通过 holder.lockCanvas()
/ unlockCanvasAndPost()
获取 Canvas
并绘制。
2.3 Handler 或 Choreographer 控制帧率
-
Handler + Runnable:在
handleMessage
或postDelayed
中循环发送绘制请求。 -
Choreographer:精确同步到 vsync,推荐用于高帧率需求。
2.4 触摸事件
在 Engine 中重写 onTouchEvent(MotionEvent event)
,接收桌面触摸,并将事件用于动画效果。
2.5 传感器管理
通过 SensorManager
注册加速度或陀螺仪监听器,在 Engine 中接收数据,并在绘制时根据传感器数据偏移或变换画布。
三、项目实现思路
-
模块划分
-
MyWallpaperService
:继承自WallpaperService
,返回MyEngine
。 -
MyEngine
:继承自WallpaperService.Engine
,管理 Surface、绘制循环、触摸、传感器。 -
SettingsActivity
:提供用户界面调整动画参数,并将设置通过SharedPreferences
传递给 Engine。
-
-
渲染循环
-
使用
Handler
在固定间隔(如 16ms)发送MSG_DRAW
,触发drawFrame()
。 -
在
drawFrame()
中锁定 Canvas,清屏,绘制当前帧动画或粒子,然后解锁提交。
-
-
动画数据结构
-
帧动画:将多张
Bitmap
载入内存,按索引循环显示。 -
粒子效果:维护一个粒子列表,每帧更新位置、透明度等属性并绘制。
-
-
触摸与传感器
-
触摸:记录触点坐标,生成涟漪或新粒子。
-
传感器:根据重力方向调整绘制偏移,产生视差。
-
-
生命周期管理
-
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 中处理多指事件,产生更多复杂交互。 |
网络数据驱动 | 从网络获取实时数据(如天气、时间),根据数据动态改变壁纸。 |
动态壁纸插件化 | 将特效模块化,用户可在市场下载或切换不同的特效插件。 |