`

LiveWallPaper 动态壁纸程序开发【转】

阅读更多

    通常手机屏幕的壁纸就是一张静态的图片,看上去已经挺不错的了,但它不会有变化。

 

      LiveWallPaper 动态壁纸是从Android2.1就开始带有的一个新的特性。它让我们能够将本来毫无生气的静态的手机屏幕背景替换成 从 随着音乐的活力和脉动而跳跃的声线 到 手指抚过能激起阵阵涟漪的静默的池塘。显示当前天气情况、展示幻灯片甚至是表现烟火特效也只是android百变动态壁纸的冰山一角。现在让我们揭开它神秘的面纱。看看这些神奇的应用是如何做到的。

  

                                                                              图一

                        创建我们的CS——LiveWallpaper项目

  在这个例子中,我们将创造一个活生生的墙纸,使用OpenGL显示一个旋转的立方体。最后的程序运行结果就如图一所示。首先创建一个新的android项目,在其的向导wizard中使用这些值:

Project  name:  CS_Livewallpaper
Build  Target:  Android  2.2
Application  name:  Wallpaper
Package  name:  classroom.studio
Min  SDK  Version:  8

    Activity的名字可以先空着。关闭Activity项左边的复选框。如下图二所示。

                                                                                  图二

   创建项目后,我们需要为新项目中的androidmanifest.xml文件中添加新的属性。它看起来就像下面这样。

 

1 <manifest xmlns:android="http://schemas.android.com/apk/res/android" 2 package="classroom.studio" 3 android:versionCode="1" 4 android:versionName="1.0"> 5 <application android:label="@string/app_name"> 6 <service android:name=".Wallpaper" 7 android:label="@string/service_name" 8 android:permission="android.permission.BIND_WALLPAPER"> 9 <intent-filter> 10 <action android:name= 11 "android.service.wallpaper.WallpaperService" /> 12 </intent-filter> 13 <meta-data android:name="android.service.wallpaper" 14 android:resource="@xml/wallpaper" /> 15 </service> 16 </application> 17 <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="8" /> 18 </manifest> 19

 

     <service>这个标签是新的。它定义了一个,将运行在背景上并且对事件有响应的Android服务。 android:permission= attribute的意思是任何程序调用我们的服务都需要有一个指定的权限。 Android的Home程序已经具备了该权限,所以它会工作得很好。

 

      < intent-filter >这个标签告诉Android它是个什么样的服务,并且< meta-data >标签让它知道在哪能找到关于wallpaper的额外信息。 android:resource被设为"@xml/wallpaper"这个值最好改成res/xml/wallpaper.xml 文件。以下便是该xml文件的内容。

 

 

1 <?xml version="1.0" encoding="utf-8"?> 2 <wallpaper xmlns:android="http://schemas.android.com/apk/res/android" 3 android:author="@+string/author" 4 android:description="@string/description" 5 android:thumbnail="@drawable/thumbnail" 6 />

 

   这个 wallpaper的metadata(元数据)指定了这个程序的作者的名字、该程序的介绍以及一个缩略图。所有这些分别存储在string.xml以及drawable文件中。他们将在使用者选择该使用哪个动态壁纸应用的时候以列表的形式显示出来。因此我们还需要创建一个string.xml文件。如下所示。 

 

string.xml

   我们可以AVD中看到这个文件实际所起的作用。如下图三所示

                                     

                                                                  图三

 

  同时我们也需要删除掉一个Layout中的文件 res/layout/main.xml。因为我们不会在这个程序中使用它。在我们具体了解了Android的一些服务之后我们将填写Wallpaper类。 

 

 

   书接前文,我们先要介绍Android的services。Android的一个与众不同的特征就是具有可在背景中运行程序的能力。为了将他们与前景的activities分开,Android把这些程序叫做services。

 

   Service类由一个service的main java类继承.Services像 activities一样 也有个生命周期,但较之要简单得多。 在这个周期中,当我们第一次创建service类时, 需要调用onCreate()的方法。而当我们消除该service时,则调用了onDestroy()方法。

 

   而在这两者之间,当客户发出开始service的请求时,Android将调用 onStartCommand()方法 。当然Android也为我们提供一些其他的方法以备不时需,例如在内存低情况下使用的onLowMemory()方法,以及下表一所示。 

                                                                                        表一

公开的部分的方法
void           onRebind(Intent intent)
如果之前通过onUnbind(Intent)方法已经通告解除所有联系后,
当新的clients再与service取得联系时,该方法被调用。
Boolean onUnbind(Intent intent)
当所用Clients都已与一个service发出的特点界面断开联系时,调用该方法。
final void stopSelf()
如果之前它开始过,则自己停止该service。
final void            startForeground(int id, Notification notification)
该方法使该service能够运行在前台(前景上),
并在这个状态下支持将ongoing的通知显示给用户。
final void stopForeground(boolean removeNotification)
从前景中去除该service,并允许在内存低时杀死service。
 
想了解更多方法请参考: http://d.android.com/reference/android/app/Service.html


  
 当然在我们这个例子中,不需要关心这些方法,因为他们都将由Service的子类WallpaperService 来处理。

   我们的main类需要扩展成Wallpaper,就像下面这样。

1 package classroom。studio; 2  import android.service.wallpaper.WallpaperService; 3  public class Wallpaper extends WallpaperService { 4  private class MyEngine extends Engine { 5  // 从这里实现引擎。。。 6  } 7 @Override 8  public Engine onCreateEngine() { 9  return new MyEngine(); 10 } 11 } 12  

 

   我们现在要做的就是实现只有一行代码的onCreateEngine()方法。它的唯一目的就是要创建并返回另一个叫做MyEngine的类。

 

            构建一个绘图引擎

     因为这个MyEngine一定要是Wallpaper里的一个类,所以在类的封闭的大括号中声明了它。MyEngine 扩展了由Android提供的Engine类。以下的代码是带有所有要用到的方法的MyEngine 纲要。

 

wallpaper.java
1 private class MyEngine extends Engine { 2 @Override 3  public void onCreate(final SurfaceHolder holder) { 4  super.onCreate(holder); 5 } 6 @Override 7  public void onDestroy() { 8  super.onDestroy(); 9 }; 10 @Override 11  public void onSurfaceCreated(final SurfaceHolder holder) { 12  super.onSurfaceCreated(holder); 13 } 14 @Override 15  public void onSurfaceDestroyed(final SurfaceHolder holder) { 16  super.onSurfaceDestroyed(holder); 17 }@Override 18  public void onSurfaceChanged(final SurfaceHolder holder, 19 final int format, final int width, final int height) { 20 super.onSurfaceChanged(holder, format, width, height); 21 } 22 @Override 23 public void onVisibilityChanged(final boolean visible) { 24 super.onVisibilityChanged(visible); 25 } 26 @Override 27 public void onOffsetsChanged(final float xOffset, 28 final float yOffset, final float xOffsetStep, 29 final float yOffsetStep, final int xPixelOffset, 30 final int yPixelOffset) { 31 super.onOffsetsChanged(xOffset, yOffset, xOffsetStep, 32 yOffsetStep, xPixelOffset, yPixelOffset); 33 } 34 }

 

   需要注意的是每个方法应该总是调用其基类方法。

   在Engine的整个生命周期中,Android会在特定的命令下调用这些方法。下面就是整个句子。

onCreate
     onSurfaceCreated
                  onSurfaceChanged (1+ calls in any order)
                  onOffsetsChanged (0+ calls in any order)
                  onVisibilityChanged (0+ calls in any order)
     onSurfaceDestroyed
onDestroy

  接下来,我们将填写这些方法。不过我们还需要几个声明来防止编译出错,当我们编写代码时我们可以让Eclipse帮我们来创建这些语句(使用 Content Assist(Ctrl+空格)或是 Quick Fix(Ctrl+1) 亦或是通过Source > Organize Imports command ( Ctrl+Shift+O )实现批处理。但如果您愿意的话,也可以自己敲,下面是完整的列表。

 

1 import java.util.concurrent.ExecutorService; 2 import java.util.concurrent.Executors; 3 import javax.microedition.khronos.egl.EGL10; 4 import javax.microedition.khronos.egl.EGL11; 5 import javax.microedition.khronos.egl.EGLConfig; 6 import javax.microedition.khronos.egl.EGLContext; 7 import javax.microedition.khronos.egl.EGLDisplay; 8 import javax.microedition.khronos.egl.EGLSurface; 9 import javax.microedition.khronos.opengles.GL10; 10 import android.service.wallpaper.WallpaperService; 11 import android.view.SurfaceHolder;

 

  下面让我们开始画我们的立方体吧。初始效果如图四所示。OpenGL ES

 

 

                                                图四

 

 

 

先要了解一下何为OpenGL 以及OpenGL ES。

 

   OpenGL的前世今生

 

    1992年Silicon Graphics 开发出了OpenGL。它从此开始为程序员提供一个统一的平台让他们能够驾驭来自不同生产商的硬件。它的核心,OpenGL迎合了三维图形开发的一些经典概念如viewpoints和lighting并试图让开发者可以基本不去考虑错综复杂的硬件层就能实现3D的效果。您可以浏览http://www.opengl.org了解OpenGL的更多内容。

    不过正是因为当初它是为工作站设计的,所以OpenGL对于一部手机来说实在是太大了。所以Google Android采用了OpenGL的一个子集——(OpenGL for Embedded Systems 针对嵌入式系统的OpenGL 简称OpenGL ES)。这个标准是由Intel、AMD、Nividia、Nokia、SONY、三星等行业巨头共同支持的 Khronos Group行业协会提出来的,包括Android、塞班和Iphone在内的主要手机平台都采用了这个库。虽然他们彼此之间还是有着细微的差别。您可以浏览http://www.khronos.org/opengles了解OpenGL ES的更多内容。

    几乎每种计算机语言都有他自己与OpenGL ES相绑定的部分。当然JAVA也不例外。在JAVA Specification Request(JSR)239中定义了这部分绑定。

 

    Android的OpenGL ES

 

    OpenGL ES 1.0 基于完整的OpenGL 1.3,而 ES 1.1 基于 OpenGL 1.5。JSR 239 有两个版本:一个原有的1.0 和正式的1.0.1。而我们这次使用的便是OpenGL ES1.1

    不过从Android2.2开始,OpenGL ES 2.0开始通过android.opengl 包得到支持。您也可以通过NDK来调用它。OpenGL ES 2.0  。不过还没有对应于OpenGL ES 2.0的JSR标准。如下图五所示

  

                                                     图五

 

    现在让我们用OpenGL ES来建立我们自己的立方体模型吧。 

 

分享到:
评论
1 楼 w11h22j33 2011-01-26  
GLCube.java
1  package  org.example.opengl;
2  -
3  -        import  java.nio.ByteBuffer;
4  -        import  java.nio.ByteOrder;
5  5        import  java.nio.IntBuffer;
6  -
7  -        import  javax.microedition.khronos.opengles.GL10;
8  -
9  -        import  android.content.Context;
10  10        import  android.graphics.Bitmap;
11  -        import  android.graphics.BitmapFactory;
12  -        import  android.opengl.GLUtils;
13  -
14  -        class  GLCube  {
15         private  final  IntBuffer  mVertexBuffer;
16  -       public  GLCube()  {
17  -       int  one  =  65536;
18  -       int  half  =  one  /  2;
19  -       int  vertices[]  =  {
20               //  前
21  -      -half,  -half,  half,  half,  -half,  half,
22  -      -half,  half,  half,  half,  half,  half,
23  -              //  后
24  -      -half,  -half,  -half,  -half,  half,  -half,
25         half,  -half,  -half,  half,  half,  -half,
26  -       //  左
27  -     -half,  -half,  half,  -half,  half,  half,
28  -     -half,  -half,  -half,  -half,  half,  -half,
29  -      //  右
30       half,  -half,  -half,  half,  half,  -half,
31  -     half,  -half,  half,  half,  half,  half,
32  -      //  顶
33  -      -half,  half,  half,  half,  half,  half,
34  -      -half,  half,  -half,  half,  half,  -half,
35          //  底
36  -      -half,  -half,  half,  -half,  -half,  -half,
37  -      half,  -half,  half,  half,  -half,  -half,  };
38  -

45     ByteBuffer  vbb  =  ByteBuffer.allocateDirect(vertices.length  *  4);
46  -   vbb.order(ByteOrder.nativeOrder());
47  -   mVertexBuffer  =  vbb.asIntBuffer();
48  -   mVertexBuffer.put(vertices);
49  -   mVertexBuffer.position(0);
50  5   }- public  void  draw(GL10  gl)  {
51  -   gl.glVertexPointer(3,  GL10.GL_FIXED,  0,  mVertexBuffer);
52  -
53     gl.glColor4f(1,  1,  1,  1);
54  -    gl.glNormal3f(0,  0,  1);
55  -   gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP,  0,  4);
56  -   gl.glNormal3f(0,  0,  -1);
57  -   gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP,  4,  4);
58
59  -   gl.glColor4f(1,  1,  1,  1);
60  -   gl.glNormal3f(-1,  0,  0);
61  -   gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP,  8,  4);
62  -   gl.glNormal3f(1,  0,  0);
63     gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP,  12,  4);
64  -
65  -   gl.glColor4f(1,  1,  1,  1);
66 -   gl.glNormal3f(0,  1,  0);
67 -   gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP,  16,  4);
68      gl.glNormal3f(0,  -1,  0);
69 -   gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP,  20,  4);
70 -   }
71 -   }

     在上述代码的第19行的矩阵通过点坐标确定了立方体的各个顶点。我们都知道立方体的每一个面都是个正方形。而这个正方形又是由两个等腰直角三角形组成的。 我们可以使用OpenGL中一个常用的绘图模型——GL_TRIANGLE_STRIP来画这个正方形。在这个模型中,我们指定两个起始点,随后标出的每一个点都将与起始点确定一个三角形。这是在图形加速硬件上绘出大量几何图形的一种十分快捷的方法。需要注意的是每个点都有x、y、z三个坐标。x轴指向屏幕右端、y轴指向屏幕上端而z轴则指向屏幕外朝向用户的视角。在代码第45行绘图方法中,我们使用构造的VertexBuffer并且为立方体的六条边画出六个不同向的等腰直角三角形。在项目实战中,您可能会把几个调用组合在一或两个strips中,因为您代码中的OpenGL命令调用越少,您的程序运行起来越快。

  光、动作与质地

         在真实的的生活中,我们周围有很多光源如太阳、灯光、火炬或是萤火虫的生物光。为了打造虚拟的3D世界,OpenGL让我们可以在场景中给出8种光源。并且如果我们想要它成为一个动态的背景,那么就一定要加入动作。当然这还不够,如果不给它加入质地(材质)的话,没人会相信它是个真东西。那在接下来的代码中我们将一一实现这些功能。
 


GlRenderer.java
  1 package org.example.opengl;
  2
  3 import javax.microedition.khronos.egl.EGLConfig;
  4 import javax.microedition.khronos.opengles.GL10;
  5
  6 import android.content.Context;
  7 import android.opengl.GLSurfaceView;
  8 import android.opengl.GLU;
  9 import android.util.Log;
10
11 class GLRenderer implements GLSurfaceView.Renderer {
12    private static final String TAG = "GLRenderer";
13    private final Context context;
14   
15   
16    private final GLCube cube = new GLCube();
17   
18   
19    private long startTime;
20    private long fpsStartTime;
21    private long numFrames;
22   
23   
24
25    GLRenderer(Context context) {
26       this.context = context;
27    }
28
29   
30   
31    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
32      

37       boolean SEE_THRU = false;
38       // 定义了一个布尔变量用于设置是否使程序执行第79行使立方体变得透明的语句
39      
40       startTime = System.currentTimeMillis();
41       fpsStartTime = startTime;
42       numFrames = 0;
43      
44
45       // 定义光源
46      
47       float lightAmbient[] = new float[] { 0.2f, 0.2f, 0.2f, 1 };
48       float lightDiffuse[] = new float[] { 1, 1, 1, 1 };
49       float[] lightPos = new float[] { 1, 1, 1, 1 };
50       gl.glEnable(GL10.GL_LIGHTING);
51       gl.glEnable(GL10.GL_LIGHT0);
52       gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, lightAmbient, 0);
53       gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, lightDiffuse, 0);
54       gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightPos, 0);
55      
56
57       // 立方体的材质,这可以决定光照在它上面的效果 漫反射还是镜面反射。
58      
59       float matAmbient[] = new float[] { 1, 1, 1, 1 };
60       float matDiffuse[] = new float[] { 1, 1, 1, 1 };
61       gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT,
62             matAmbient, 0);
63       gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE,
64             matDiffuse, 0);
65      
66
67      
68       // 设置我们需要的各种参数
69       gl.glEnable(GL10.GL_DEPTH_TEST);
70       gl.glDepthFunc(GL10.GL_LEQUAL);
71       gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
72
73       // 选项:禁用抖动特效,以提高性能。
74       // gl.glDisable(GL10.GL_DITHER);
75      
76
77      
78       // 如上面提到的使透明的语句
79       if (SEE_THRU) {
80          gl.glDisable(GL10.GL_DEPTH_TEST);
81          gl.glEnable(GL10.GL_BLEND);
82          gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE);
83       }
84      
85      
86       // 启用纹理特效
87       gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
88       gl.glEnable(GL10.GL_TEXTURE_2D);
89
90       // 将一张点阵图加载为立方体的纹理。
91       GLCube.loadTexture(gl, context, R.drawable.android);
92      
93      
94      
95      
96    }
97   
98   
99
100   
101    public void onSurfaceChanged(GL10 gl, int width, int height) {
102      
103       // ...
104      
105      
106       // 定义视角
107       gl.glViewport(0, 0, width, height);
108       gl.glMatrixMode(GL10.GL_PROJECTION);
109       gl.glLoadIdentity();
110       float ratio = (float) width / height;
111       GLU.gluPerspective(gl, 45.0f, ratio, 1, 100f);
112      
113    }
114   
115
116   
117   
118   
119    public void onDrawFrame(GL10 gl) {
120      
121       // ...
122      
123      
124      
125      
126       // 清到黑屏
127       gl.glClear(GL10.GL_COLOR_BUFFER_BIT
128             | GL10.GL_DEPTH_BUFFER_BIT);
129
130       // ...
131       gl.glMatrixMode(GL10.GL_MODELVIEW);
132       gl.glLoadIdentity();
133       gl.glTranslatef(0, 0, -3.0f);
134
135       // 您的其它的绘图命令可以写在这里。
136      
137      
138       // 根据时间设置每次旋转的角度
139       long elapsed = System.currentTimeMillis() - startTime;
140       gl.glRotatef(elapsed * (25f / 1000f), 0, 1, 0);  //每秒钟它都会沿Y轴转25度
141       gl.glRotatef(elapsed * (10f / 1000f), 1, 0, 0);  //每秒钟它沿x轴转10度
142
143      
144       // 画出模型
145       cube.draw(gl);
146      
147      
148       // 随时跟踪得出的帧数
149      
150       numFrames++;
151       long fpsElapsed = System.currentTimeMillis() - fpsStartTime;
152       if (fpsElapsed > 3 * 1000) { // 每3秒一次
153          float fps = (numFrames * 1000.0F) / fpsElapsed;
154          Log.d(TAG, "Frames per second: " + fps + " (" + numFrames
155                + " frames in " + fpsElapsed + " ms)");
156          fpsStartTime = System.currentTimeMillis();
157          numFrames = 0;
158       }
159      
160      
161      
162      
163      
164    }
165   
166   
167   
168 }
169
   
在代码的第69行,我们设置了一组OpenGL 参数。当然OpenGL那几十个参数都是通过glEnable( ) 来启用,通过 glDisable( )来禁用。在下面列出的是最常用到的参数选项。
      选项说明:

GL_BLEND 允许将新进来的颜色值与已经在缓冲区中的颜色值融合。
GL_CULL_FACE
Ignore polygons(多边形) based on their
winding (clockwise or counterclockwise
(顺时针还是逆时针)) in window coordinates.
This is a cheap way to eliminate back faces.
(正在琢磨如何解释)
GL_DEPTH_TEST
进行更深入的比较,并且更新缓冲区的深度。
忽略掉像素数已经远高于之前绘制好的。
GL_LIGHTi Include light number i when figuring out an object’s
brightness and color.(正在琢磨如何解释)
GL_LIGHTING 开启照明和材质计算。
GL_LINE_SMOOTH 绘制抗锯齿线(无锯齿线)。
GL_MULTISAMPLE 启动多重采样抗锯齿和其他效果。
GL_POINT_SMOOTH 绘制抗锯齿点。
GL_TEXTURE_2D 使用纹理绘制表面。
   
    除了GL_DITHER和GL_MULTISAMPLE以外其他选项都是默认关闭的。请注意,我们启动的任何一项特性都会带有一定的性能开销。
  
    请注意一下第150行开始的代码,请问我们为什么要检测每秒钟的帧数呢?

    为的是检测画面的流畅程度

    我们如何界定流畅程度。对一个游戏或对图形加速能力要求较高的程序,可以通过它刷洗屏幕的速度来看出它的流畅度。我们经常使用FPS即每秒帧数来衡量。不同的人对于流畅与否的尺度是不同的。一般来讲 15–30FPS对于普通玩家来说就可以接受了。但较骨灰级的玩家来说可能要60FPS或是更高才能让他们满意。作为专业的程序员,我们应该向着60FPS的目标而努力。但这可能不太现实,毕竟一些服务于低端的Android手机,他们的3D硬件加速相对于他们的分辨率要弱一些。当然对于越快的设备,达到这个标准越是没有问题。高帧频是具有挑战性的,因为我们要在60fps下 即1/60th秒(16.67毫秒之间)调用onDrawFrame()做一切需要做的事情,包括任何动画、物理计算、游戏计算,再加上花费在实际绘制图形上的时间。而唯一能够检验程序的FPS是否达到了我们设计的预期的方法,就是就是通过程序自己来测量它。

     如上面的代码所示。每三秒它就会将您的平均FPS值发到Android系统的Log Messages上。如果这个数字低于您的预期,您就可以调整您的算法并重试。当然AVD毕竟是模拟。我们最终还是要看实际真机测试。

相关推荐

Global site tag (gtag.js) - Google Analytics