動態壁紙是在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。