SurfaceView

动态壁纸是在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 }

你可能感兴趣的:(SurfaceView)