Android动态壁纸的制作教程
动态壁纸是在Android 2.1新增的一个功能。动态壁纸可以添加到Android的桌面,具有交互式的动画背景效果。在本教程中,我们将教会你如何去制作一个交互式的动态壁纸。
动态壁纸是一个Android应用程序,包括一个服务(WallpaperService)。该服务必须包括一个引擎(WallpaperService.Engine)。该引擎是连接用户、桌面、系统之间的桥梁。它也可以绘制桌面壁纸。
首先,必须由内在的Engine类创建一个WallpaperService类。该服务必须在AndroidManifest.xml中声明为"android.service.wallpaper.WallpaperService",这样它才会作为动态壁纸被手机识别。而且还要在服务配置中附加"android.permission.BIND_WALLPAPER"的权限许可:
<service android:name="LiveWallpaperService" android:enabled="true" android:icon="@drawable/icon" android:label="@string/app_name" android:permission="android.permission.BIND_WALLPAPER"> <intent-filter android:priority="1" > <action android:name="android.service.wallpaper.WallpaperService" /> </intent-filter> <meta-data android:name="android.service.wallpaper" android:resource="@xml/wallpaper" /> </service>
创建一个XML文件,放置在应用程序目录下的/res/xml/中。它用来描述你的动态壁纸。
<?xml version="1.0" encoding="UTF-8"?> <wallpaper xmlns:android="http://schemas.android.com/apk/res/android " android:thumbnail="@drawable/thumbnail" android:description="@string/description" android:settingsActivity="PreferenceActivity"/>
再创建一个xml的属性文件 attrs.xml ,代码如下:
<declare-styleable name="Wallpaper"> <!-- Component name of an activity that allows the user to modify the current settings for this wallpaper. --> <attr name="settingsActivity" /> <!-- Reference to a the wallpaper's thumbnail bitmap. --> <attr name="thumbnail" format="reference" /> <!-- Name of the author of this component, e.g. Google. --> <attr name="author" format="reference" /> <!-- Short description of the component's purpose or behavior. --> <attr name="description" /> </declare-styleable>
动态壁纸的服务代码如下:
package net.androgames.blog.sample.livewallpaper; import android.content.SharedPreferences; import android.service.wallpaper.WallpaperService; import android.view.MotionEvent; import android.view.SurfaceHolder; /** * Android Live Wallpaper Archetype * @author antoine vianey * under GPL v3 : http://www.gnu.org/licenses/gpl-3.0.html */ public class LiveWallpaperService extends WallpaperService { @Override public Engine onCreateEngine() { return new SampleEngine(); } @Override public void onCreate() { super.onCreate(); } @Override public void onDestroy() { super.onDestroy(); } public class SampleEngine extends Engine { private LiveWallpaperPainting painting; SampleEngine() { SurfaceHolder holder = getSurfaceHolder(); painting = new LiveWallpaperPainting(holder, getApplicationContext()); } @Override public void onCreate(SurfaceHolder surfaceHolder) { super.onCreate(surfaceHolder); // register listeners and callbacks here setTouchEventsEnabled(true); } @Override public void onDestroy() { super.onDestroy(); // remove listeners and callbacks here painting.stopPainting(); } @Override public void onVisibilityChanged(boolean visible) { if (visible) { // register listeners and callbacks here painting.resumePainting(); } else { // remove listeners and callbacks here painting.pausePainting(); } } @Override public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) { super.onSurfaceChanged(holder, format, width, height); painting.setSurfaceSize(width, height); } @Override public void onSurfaceCreated(SurfaceHolder holder) { super.onSurfaceCreated(holder); // start painting painting.start(); } @Override public void onSurfaceDestroyed(SurfaceHolder holder) { super.onSurfaceDestroyed(holder); boolean retry = true; painting.stopPainting(); while (retry) { try { painting.join(); retry = false; } catch (InterruptedException e) {} } } @Override public void onOffsetsChanged(float xOffset, float yOffset, float xStep, float yStep, int xPixels, int yPixels) { } @Override public void onTouchEvent(MotionEvent event) { super.onTouchEvent(event); painting.doTouchEvent(event); } } }
当壁纸的显示、状态或大小变化是,会调用Engine的onCreate, onDestroy, onVisibilityChanged, onSurfaceChanged, onSurfaceCreated 和 onSurfaceDestroyed方法。有了这些方法,动态壁纸才能展现出动画效果。而通过设置setTouchEventsEnabled(true),并且调用onTouchEvent(MotionEvent event)方法,来激活触摸事件。
我们在绘画墙纸的时候,也会使用一个单独的绘画线程:
package net.androgames.blog.sample.livewallpaper; import android.content.Context; import android.graphics.Canvas; import android.view.MotionEvent; import android.view.SurfaceHolder; /** * Android Live Wallpaper painting thread Archetype * @author antoine vianey * GPL v3 : http://www.gnu.org/licenses/gpl-3.0.html */ public class LiveWallpaperPainting extends Thread { /** Reference to the View and the context */ private SurfaceHolder surfaceHolder; private Context context; /** State */ private boolean wait; private boolean run; /** Dimensions */ private int width; private int height; /** Time tracking */ private long previousTime; private long currentTime; public LiveWallpaperPainting(SurfaceHolder surfaceHolder, Context context) { // keep a reference of the context and the surface // the context is needed if you want to inflate // some resources from your livewallpaper .apk this.surfaceHolder = surfaceHolder; this.context = context; // don't animate until surface is created and displayed this.wait = true; } /** * Pauses the live wallpaper animation */ public void pausePainting() { this.wait = true; synchronized(this) { this.notify(); } } /** * Resume the live wallpaper animation */ public void resumePainting() { this.wait = false; synchronized(this) { this.notify(); } } /** * Stop the live wallpaper animation */ public void stopPainting() { this.run = false; synchronized(this) { this.notify(); } } @Override public void run() { this.run = true; Canvas c = null; while (run) { try { c = this.surfaceHolder.lockCanvas(null); synchronized (this.surfaceHolder) { currentTime = System.currentTimeMillis(); updatePhysics(); doDraw(c); previousTime = currentTime; } } finally { if (c != null) { this.surfaceHolder.unlockCanvasAndPost(c); } } // pause if no need to animate synchronized (this) { if (wait) { try { wait(); } catch (Exception e) {} } } } } /** * Invoke when the surface dimension change */ public void setSurfaceSize(int width, int height) { this.width = width; this.height = height; synchronized(this) { this.notify(); } } /** * Invoke while the screen is touched */ public void doTouchEvent(MotionEvent event) { // handle the event here // if there is something to animate // then wake up this.wait = false; synchronized(this) { notify(); } } /** * Do the actual drawing stuff */ private void doDraw(Canvas canvas) {} /** * Update the animation, sprites or whatever. * If there is nothing to animate set the wait * attribute of the thread to true */ private void updatePhysics() { // if nothing was updated : // this.wait = true; } }
如果桌面壁纸是可见状态下,系统服务通知有新的东西,这个类会优先把它绘制在画布上。如果没有动画了,updatePhysics会通知线程去等待。通常SurfaceView在有两个画布交替绘制的时候,会在画布上绘制上一次......
如果要让你的动态墙纸有配置功能,只要创建一个PreferenceActivity,并将它在wallpaper.xml文件中声明。同时让SharedPreference对象可以找到你的配置选项。
教程就写到这里,如果还有什么不懂,你可以通过Eclipse来浏览完整的源代码:SampleLiveWallpaper。