动态壁纸是在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
08 <intent-filter android:priority="1" >
09 <action android:name="android.service.wallpaper.WallpaperService" />
10 </intent-filter>
11 <meta-data
12 android:name="android.service.wallpaper"
13 android:resource="@xml/wallpaper" />
14
15 </service>

创建一个XML文件,放置在应用程序目录下的/res/xml/中。它用来描述你的动态壁纸。
1 <?xml version="1.0" encoding="UTF-8"?>
2
3 <wallpaper
4 xmlns:android="http://schemas.android.com/apk/res/android"
5 android:thumbnail="@drawable/thumbnail"
6 android:description="@string/description"
7 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
08 <attr name="thumbnail" format="reference" />
09
10 <!-- Name of the author of this component, e.g. Google. -->
11 <attr name="author" format="reference" />
12
13
14 <!-- Short description of the component's purpose or behavior. -->
15 <attr name="description" />
16 </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=>[url]http://code.google.com/p/androgames-sample/
[/url]



双缓冲是为了防止动画闪烁而实现的一种多线程应用,基于SurfaceView的双缓冲实现很简单,开一条线程并在其中绘图即可。本文介绍基于SurfaceView的双缓冲实现,以及介绍类似的更高效的实现方法。

本文程序运行截图如下,左边是开单个线程读取并绘图,右边是开两个线程,一个专门读取图片,一个专门绘图:

对比一下,右边动画的帧速明显比左边的快,左右两者都没使用Thread.sleep()。为什么要开两个线程一个读一个画,而不去开两个线程像左边那样都 “边读边画”呢?因为SurfaceView每次绘图都会锁定Canvas,也就是说同一片区域这次没画完下次就不能画,因此要提高双缓冲的效率,就得开一条线程专门画图,开另外一条线程做预处理的工作。
标签: SurfaceView 双缓冲 Android SDK
代码片段(3)
[图片] 程序运行截图
[代码] main.xml
01 <?xml version="1.0" encoding="utf-8"?>
02 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
03 android:layout_width="fill_parent" android:layout_height="fill_parent"
04 android:orientation="vertical">
05
06 <LinearLayout android:id="@+id/LinearLayout01"
07 android:layout_width="wrap_content" android:layout_height="wrap_content">
08 <Button android:id="@+id/Button01" android:layout_width="wrap_content"
09 android:layout_height="wrap_content" android:text="单个独立线程"></Button>
10 <Button android:id="@+id/Button02" android:layout_width="wrap_content"
11 android:layout_height="wrap_content" android:text="两个独立线程"></Button>
12 </LinearLayout>
13 <SurfaceView android:id="@+id/SurfaceView01"
14 android:layout_width="fill_parent" android:layout_height="fill_parent"></SurfaceView>
15 </LinearLayout>
[代码] TestSurfaceView.java
001 package com.testSurfaceView;
002
003 import java.lang.reflect.Field;
004 import java.util.ArrayList;
005 import android.app.Activity;
006 import android.graphics.Bitmap;
007 import android.graphics.BitmapFactory;
008 import android.graphics.Canvas;
009 import android.graphics.Paint;
010 import android.graphics.Rect;
011 import android.os.Bundle;
012 import android.util.Log;
013 import android.view.SurfaceHolder;
014 import android.view.SurfaceView;
015 import android.view.View;
016 import android.widget.Button;
017
018 public class TestSurfaceView extends Activity {
019 /** Called when the activity is first created. */
020 Button btnSingleThread, btnDoubleThread;
021 SurfaceView sfv;
022 SurfaceHolder sfh;
023 ArrayList<Integer> imgList = new ArrayList<Integer>();
024 int imgWidth, imgHeight;
025 Bitmap bitmap;//独立线程读取,独立线程绘图
026
027 @Override
028 public void onCreate(Bundle savedInstanceState) {
029 super.onCreate(savedInstanceState);
030 setContentView(R.layout.main);
031
032 btnSingleThread = (Button) this.findViewById(R.id.Button01);
033 btnDoubleThread = (Button) this.findViewById(R.id.Button02);
034 btnSingleThread.setOnClickListener(new ClickEvent());
035 btnDoubleThread.setOnClickListener(new ClickEvent());
036 sfv = (SurfaceView) this.findViewById(R.id.SurfaceView01);
037 sfh = sfv.getHolder();
038 sfh.addCallback(new MyCallBack());// 自动运行surfaceCreated以及surfaceChanged
039 }
040
041 class ClickEvent implements View.OnClickListener {
042
043 @Override
044 public void onClick(View v) {
045
046 if (v == btnSingleThread) {
047 new Load_DrawImage(0, 0).start();//开一条线程读取并绘图
048 } else if (v == btnDoubleThread) {
049 new LoadImage().start();//开一条线程读取
050 new DrawImage(imgWidth + 10, 0).start();//开一条线程绘图
051 }
052
053 }
054
055 }
056
057 class MyCallBack implements SurfaceHolder.Callback {
058
059 @Override
060 public void surfaceChanged(SurfaceHolder holder, int format, int width,
061 int height) {
062 Log.i("Surface:", "Change");
063
064 }
065
066 @Override
067 public void surfaceCreated(SurfaceHolder holder) {
068 Log.i("Surface:", "Create");
069
070 // 用反射机制来获取资源中的图片ID和尺寸
071 Field[] fields = R.drawable.class.getDeclaredFields();
072 for (Field field : fields) {
073 if (!"icon".equals(field.getName()))// 除了icon之外的图片
074 {
075 int index = 0;
076 try {
077 index = field.getInt(R.drawable.class);
078 } catch (IllegalArgumentException e) {
079 // TODO Auto-generated catch block
080 e.printStackTrace();
081 } catch (IllegalAccessException e) {
082 // TODO Auto-generated catch block
083 e.printStackTrace();
084 }
085 // 保存图片ID
086 imgList.add(index);
087 }
088 }
089 // 取得图像大小
090 Bitmap bmImg = BitmapFactory.decodeResource(getResources(),
091 imgList.get(0));
092 imgWidth = bmImg.getWidth();
093 imgHeight = bmImg.getHeight();
094 }
095
096 @Override
097 public void surfaceDestroyed(SurfaceHolder holder) {
098 Log.i("Surface:", "Destroy");
099
100 }
101
102 }
103
104 /**
105 * 读取并显示图片的线程
106 */
107 class Load_DrawImage extends Thread {
108 int x, y;
109 int imgIndex = 0;
110
111 public Load_DrawImage(int x, int y) {
112 this.x = x;
113 this.y = y;
114 }
115
116 public void run() {
117 while (true) {
118 Canvas c = sfh.lockCanvas(new Rect(this.x, this.y, this.x
119 + imgWidth, this.y + imgHeight));
120 Bitmap bmImg = BitmapFactory.decodeResource(getResources(),
121 imgList.get(imgIndex));
122 c.drawBitmap(bmImg, this.x, this.y, new Paint());
123 imgIndex++;
124 if (imgIndex == imgList.size())
125 imgIndex = 0;
126
127 sfh.unlockCanvasAndPost(c);// 更新屏幕显示内容
128 }
129 }
130 };
131
132 /**
133 * 只负责绘图的线程
134 */
135 class DrawImage extends Thread {
136 int x, y;
137
138 public DrawImage(int x, int y) {
139 this.x = x;
140 this.y = y;
141 }
142
143 public void run() {
144 while (true) {
145 if (bitmap != null) {//如果图像有效
146 Canvas c = sfh.lockCanvas(new Rect(this.x, this.y, this.x
147 + imgWidth, this.y + imgHeight));
148
149 c.drawBitmap(bitmap, this.x, this.y, new Paint());
150
151 sfh.unlockCanvasAndPost(c);// 更新屏幕显示内容
152 }
153 }
154 }
155 };
156
157 /**
158 * 只负责读取图片的线程
159 */
160 class LoadImage extends Thread {
161 int imgIndex = 0;
162
163 public void run() {
164 while (true) {
165 bitmap = BitmapFactory.decodeResource(getResources(),
166 imgList.get(imgIndex));
167 imgIndex++;
168 if (imgIndex == imgList.size())//如果到尽头则重新读取
169 imgIndex = 0;
170 }
171 }
172 };
173 }

更多相关文章

  1. Android开发: 线程间消息通信 Looper 和Handler
  2. android的surfaceview的用法
  3. Android面试知识点总结-Android篇
  4. Android动态换肤(二、apk免安装插件方式)
  5. Android中生成和扫描二维码
  6. Android(安卓)Handler 异步消息处理机制的妙用 创建强大的图片加
  7. Android(安卓)调用系统Camera 程序照相,获取照片
  8. android 动态改变屏幕方向
  9. android中的Handler(1)

随机推荐

  1. Android(安卓)ListView组件 ArrayAdapter
  2. Android实现一行有好几个TextView 第一个
  3. Android四种常用的消息传递机制/模式的比
  4. 网易云短信android接入
  5. eclipse 上使用recycleview,cardview的办
  6. AndroidManifest.xml 文件里面的内容介绍
  7. (转载) Android(安卓)RecyclerView 使用完
  8. Android(安卓)Service 介绍
  9. 捕获TextView超链接
  10. android 自动弹起键盘