动态壁纸是在Android 2.1新增的一个功能。动态壁纸可以添加到Android的桌面,具有交互式的动画背景效果。在本教程中,我们将教会你如何去制作一个交互式的动态壁纸。
动态壁纸是一个Android应用程序,包括一个服务(WallpaperService)。该服务必须包括一个引擎(WallpaperService.Engine)。该引擎是连接用户、桌面、系统之间的桥梁。它也可以绘制桌面壁纸。
首先,必须由内在的Engine类创建一个WallpaperService类。该服务必须在AndroidManifest.xml中声明为"android.service.wallpaper.WallpaperService",这样它才会作为动态壁纸被手机识别。而且还要在服务配置中附加"android.permission.BIND_WALLPAPER"的权限许可:
01 | < service |
02 | android:name = "LiveWallpaperService" |
03 | android:enabled = "true" |
04 | android:icon = "@drawable/icon" |
05 | android:label = "@string/app_name" |
06 | android:permission = "android.permission.BIND_WALLPAPER" > |
07 | < intent-filter android:priority = "1" > |
08 | < action android:name = "android.service.wallpaper.WallpaperService" /> |
09 | </ intent-filter > |
10 | < meta-data |
11 | android:name = "android.service.wallpaper" |
12 | android:resource = "@xml/wallpaper" /> |
13 | </ service > |
创建一个XML文件,放置在应用程序目录下的/res/xml/中。它用来描述你的动态壁纸。
1 | <? xml version = "1.0" encoding = "UTF-8" ?> |
2 | < wallpaper |
3 | xmlns:android = "http://schemas.android.com/apk/res/android" |
4 | android:thumbnail = "@drawable/thumbnail" |
5 | android:description = "@string/description" |
6 | android:settingsActivity = "PreferenceActivity" /> |
再创建一个xml的属性文件 attrs.xml ,代码如下:
01 | < declare-styleable name = "Wallpaper" > |
02 | <!-- Component name of an activity that allows the user to modify |
03 | the current settings for this wallpaper. --> |
04 | < attr name = "settingsActivity" /> |
05 | |
06 | <!-- Reference to a the wallpaper's thumbnail bitmap. --> |
07 | < attr name = "thumbnail" format = "reference" /> |
08 | |
09 | <!-- Name of the author of this component, e.g. Google. --> |
10 | < attr name = "author" format = "reference" /> |
11 | |
12 | <!-- Short description of the component's purpose or behavior. --> |
13 | < attr name = "description" /> |
14 | </ declare-styleable > |
动态壁纸的服务代码如下:
001 | package net.androgames.blog.sample.livewallpaper; |
002 | |
003 | import android.content.SharedPreferences; |
004 | import android.service.wallpaper.WallpaperService; |
005 | import android.view.MotionEvent; |
006 | import android.view.SurfaceHolder; |
007 | |
008 | /** |
009 | * Android Live Wallpaper Archetype |
010 | * @author antoine vianey |
011 | * under GPL v3 : http://www.gnu.org/licenses/gpl-3.0.html |
012 | */ |
013 | public class LiveWallpaperService extends WallpaperService { |
014 | |
015 | @Override |
016 | public Engine onCreateEngine() { |
017 | return new SampleEngine(); |
018 | } |
019 | |
020 | @Override |
021 | public void onCreate() { |
022 | super .onCreate(); |
023 | } |
024 | |
025 | @Override |
026 | public void onDestroy() { |
027 | super .onDestroy(); |
028 | } |
029 | |
030 | public class SampleEngine extends Engine { |
031 | |
032 | private LiveWallpaperPainting painting; |
033 | |
034 | SampleEngine() { |
035 | SurfaceHolder holder = getSurfaceHolder(); |
036 | painting = new LiveWallpaperPainting(holder, |
037 | getApplicationContext()); |
038 | } |
039 | |
040 | @Override |
041 | public void onCreate(SurfaceHolder surfaceHolder) { |
042 | super .onCreate(surfaceHolder); |
043 | // register listeners and callbacks here |
044 | setTouchEventsEnabled( true ); |
045 | } |
046 | |
047 | @Override |
048 | public void onDestroy() { |
049 | super .onDestroy(); |
050 | // remove listeners and callbacks here |
051 | painting.stopPainting(); |
052 | } |
053 | |
054 | @Override |
055 | public void onVisibilityChanged( boolean visible) { |
056 | if (visible) { |
057 | // register listeners and callbacks here |
058 | painting.resumePainting(); |
059 | } else { |
060 | // remove listeners and callbacks here |
061 | painting.pausePainting(); |
062 | } |
063 | } |
064 | |
065 | @Override |
066 | public void onSurfaceChanged(SurfaceHolder holder, int format, |
067 | int width, int height) { |
068 | super .onSurfaceChanged(holder, format, width, height); |
069 | painting.setSurfaceSize(width, height); |
070 | } |
071 | |
072 | @Override |
073 | public void onSurfaceCreated(SurfaceHolder holder) { |
074 | super .onSurfaceCreated(holder); |
075 | // start painting |
076 | painting.start(); |
077 | } |
078 | |
079 | @Override |
080 | public void onSurfaceDestroyed(SurfaceHolder holder) { |
081 | super .onSurfaceDestroyed(holder); |
082 | boolean retry = true ; |
083 | painting.stopPainting(); |
084 | while (retry) { |
085 | try { |
086 | painting.join(); |
087 | retry = false ; |
088 | } catch (InterruptedException e) {} |
089 | } |
090 | } |
091 | |
092 | @Override |
093 | public void onOffsetsChanged( float xOffset, float yOffset, |
094 | float xStep, float yStep, int xPixels, int yPixels) { |
095 | } |
096 | |
097 | @Override |
098 | public void onTouchEvent(MotionEvent event) { |
099 | super .onTouchEvent(event); |
100 | painting.doTouchEvent(event); |
101 | } |
102 | |
103 | } |
104 | |
105 | } |
当壁纸的显示、状态或大小变化是,会调用Engine的onCreate, onDestroy, onVisibilityChanged,onSurfaceChanged, onSurfaceCreated 和 onSurfaceDestroyed方法。有了这些方法,动态壁纸才能展现出动画效果。而通过设置setTouchEventsEnabled(true),并且调用onTouchEvent(MotionEvent event)方法,来激活触摸事件。
我们在绘画墙纸的时候,也会使用一个单独的绘画线程:
001 | package net.androgames.blog.sample.livewallpaper; |
002 | |
003 | import android.content.Context; |
004 | import android.graphics.Canvas; |
005 | import android.view.MotionEvent; |
006 | import android.view.SurfaceHolder; |
007 | |
008 | /** |
009 | * Android Live Wallpaper painting thread Archetype |
010 | * @author antoine vianey |
011 | * GPL v3 : http://www.gnu.org/licenses/gpl-3.0.html |
012 | */ |
013 | public class LiveWallpaperPainting extends Thread { |
014 | |
015 | /** Reference to the View and the context */ |
016 | private SurfaceHolder surfaceHolder; |
017 | private Context context; |
018 | |
019 | /** State */ |
020 | private boolean wait; |
021 | private boolean run; |
022 | |
023 | /** Dimensions */ |
024 | private int width; |
025 | private int height; |
026 | |
027 | /** Time tracking */ |
028 | private long previousTime; |
029 | private long currentTime; |
030 | |
031 | public LiveWallpaperPainting(SurfaceHolder surfaceHolder, |
032 | Context context) { |
033 | // keep a reference of the context and the surface |
034 | // the context is needed if you want to inflate |
035 | // some resources from your livewallpaper .apk |
036 | this .surfaceHolder = surfaceHolder; |
037 | this .context = context; |
038 | // don't animate until surface is created and displayed |
039 | this .wait = true ; |
040 | } |
041 | |
042 | /** |
043 | * Pauses the live wallpaper animation |
044 | */ |
045 | public void pausePainting() { |
046 | this .wait = true ; |
047 | synchronized ( this ) { |
048 | this .notify(); |
049 | } |
050 | } |
051 | |
052 | /** |
053 | * Resume the live wallpaper animation |
054 | */ |
055 | public void resumePainting() { |
056 | this .wait = false ; |
057 | synchronized ( this ) { |
058 | this .notify(); |
059 | } |
060 | } |
061 | |
062 | /** |
063 | * Stop the live wallpaper animation |
064 | */ |
065 | public void stopPainting() { |
066 | this .run = false ; |
067 | synchronized ( this ) { |
068 | this .notify(); |
069 | } |
070 | } |
071 | |
072 | @Override |
073 | public void run() { |
074 | this .run = true ; |
075 | Canvas c = null ; |
076 | while (run) { |
077 | try { |
078 | c = this .surfaceHolder.lockCanvas( null ); |
079 | synchronized ( this .surfaceHolder) { |
080 | currentTime = System.currentTimeMillis(); |
081 | updatePhysics(); |
082 | doDraw(c); |
083 | previousTime = currentTime; |
084 | } |
085 | } finally { |
086 | if (c != null ) { |
087 | this .surfaceHolder.unlockCanvasAndPost(c); |
088 | } |
089 | } |
090 | // pause if no need to animate |
091 | synchronized ( this ) { |
092 | if (wait) { |
093 | try { |
094 | wait(); |
095 | } catch (Exception e) {} |
096 | } |
097 | } |
098 | } |
099 | } |
100 | |
101 | /** |
102 | * Invoke when the surface dimension change |
103 | */ |
104 | public void setSurfaceSize( int width, int height) { |
105 | this .width = width; |
106 | this .height = height; |
107 | synchronized ( this ) { |
108 | this .notify(); |
109 | } |
110 | } |
111 | |
112 | /** |
113 | * Invoke while the screen is touched |
114 | */ |
115 | public void doTouchEvent(MotionEvent event) { |
116 | // handle the event here |
117 | // if there is something to animate |
118 | // then wake up |
119 | this .wait = false ; |
120 | synchronized ( this ) { |
121 | notify(); |
122 | } |
123 | } |
124 | |
125 | /** |
126 | * Do the actual drawing stuff |
127 | */ |
128 | private void doDraw(Canvas canvas) {} |
129 | |
130 | /** |
131 | * Update the animation, sprites or whatever. |
132 | * If there is nothing to animate set the wait |
133 | * attribute of the thread to true |
134 | */ |
135 | private void updatePhysics() { |
136 | // if nothing was updated : |
137 | // this.wait = true; |
138 | } |
139 | |
140 | } |
如果桌面壁纸是可见状态下,系统服务通知有新的东西,这个类会优先把它绘制在画布上。如果没有动画了,updatePhysics会通知线程去等待。通常SurfaceView在有两个画布交替绘制的时候,会在画布上绘制上一次......
如果要让你的动态墙纸有配置功能,只要创建一个PreferenceActivity,并将它在wallpaper.xml文件中声明。同时让SharedPreference对象可以找到你的配置选项。
教程就写到这里,如果还有什么不懂,你可以通过Eclipse来浏览完整的源代码:SampleLiveWallpaper。