本節書摘來自異步社區《Android 應用案例開發大全(第3版)》一書中的第2章,第2.4節壁紙的實現,作者 吳亞峰 , 蘇亞光 , 于復興,更多章節內容可以訪問云棲社區“異步社區”公眾號查看
2.4 壁紙的實現
上一節介紹了壁紙的框架,讓讀者對3D動態壁紙的整體框架有了初步認識,本節將要對動態壁紙的實現服務類GLWallpaperService和OpenGLES2WallpaperService以及自定義場景渲染器類MySurfaceView的開發進行詳細介紹。
2.4.1 壁紙服務類——OpenGLES2WallpaperService
這兩個類是本項目中最基礎的類,沒有這兩個類就不可能使用壁紙。GLWallpaperService類為開發人員提供了壁紙服務,OpenGLES2WallpaperService通過繼承GLWallpaperService類,重寫此類中的方法來實現壁紙的后續開發。下面著重介紹一下OpenGLES2WallpaperService類中的onCreate方法和GLWallpaperService類中的觸控響應事件onTouchEvent。
(1)首先是OpenGLES2WallpaperService類中的onCreate方法,onCreate方法是OpenGLES2 WallpaperService類的核心部分,其中包括獲取當前手機的配置信息,并且判斷其是否支持OPENGL ES2.0渲染技術等。具體代碼如下所示。
1 public void onCreate(SurfaceHolder surfaceHolder) {
2 super.onCreate(surfaceHolder);
3 final ActivityManager activityManager = //創建Activity管理器
4 (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
5 final ConfigurationInfo configurationInfo = //獲取當前機器配置信息
6 activityManager.getDeviceConfigurationInfo();
7 final boolean supportsEs2 = //獲取判斷結果
8 configurationInfo.reqGlEsVersion >= 0x20000;
9 if (supportsEs2) {
10 setEGLContextClientVersion(2); //設置使用OPENGL ES2.0
11 setPreserveEGLContextOnPause(true);//EGL跨越暫停/恢復界限來嘗試和保存環境
12 setRenderer(getNewRenderer()); //設置渲染器
13 } else {return;}
14 }
第3~8行用于創建Activity管理器,獲取配置信息,判斷當前手機是否支持OPENGL ES2.0渲染技術,并將結果存儲在supportsEs2中。
第9~13行用于判斷supportsEs2中的值,如果當前手機支持OPENGL ES2.0渲染技術,則設置使用OPENGL ES2.0進行繪制,并且讓EGL跨越暫?;蚧謴徒缦迊韲L試和保護環境,然后設置場景渲染器;如果不支持OPENGL ES2.0,則退出繪制。
(2)下面將對屏幕觸控的響應事件進行介紹。屏幕的觸控事件分為3部分:第一部分是滑動屏幕使背景圖跟著屏幕左右移動,第二部分是點擊屏幕下方修改標志位,最后一部分是手指抬起時判斷是否進行喂食,具體代碼如下所示。
1 private float mPreviousY; //上次的觸控位置Y坐標
2 private float mPreviousX; //上次的觸控位置X坐標
3 @Override
4 public void onTouchEvent(MotionEvent e) {
5 float y = e.getY(); //獲取觸控點Y坐標
6 float x = e.getX(); //獲取觸控點X坐標
7 switch (e.getAction()) {
8 case MotionEvent.ACTION_DOWN:
9 Constant.feeding=true;break; //喂食標志位設為true
10 case MotionEvent.ACTION_MOVE:
11 float dy = y - mPreviousY; //計算觸控筆Y位移
12 float dx = x - mPreviousX; //計算觸控筆X位移
13 if (dx < 0){ //摸左邊x為正,摸右邊x為負
14 if (Constant.CameraX <Constant.MaxCameraMove) { //判斷是否超出移動范圍
15 if(dx<-Constant.Thold){ Constant.feeding = false; }//喂食標志位置為false
16 Constant.CameraX = Constant.CameraX - dx / Constant.CameraMove_SCALE ;
17 Constant.TargetX=Constant.CameraX; //移動攝像機的坐標
18 }} else {
19 if (Constant.CameraX >-Constant.MaxCameraMove) { //判斷是否超出移動范圍
20 if(dx>Constant.Thold){ Constant.feeding =false;} //喂食標志位置為false
21 Constant.CameraX = Constant.CameraX - dx / Constant.CameraMove_SCALE ;
22 Constant.TargetX=Constant.CameraX; //移動攝像機的坐標
23 }}
24 MatrixState.setCamera( //將攝像機的位置信息存入到矩陣中
25 Constant.CameraX, Constant.CameraY, Constant.CameraZ,//攝像機的X、Y、Z位置
26 Constant.TargetX, Constant.TargetY, Constant.TargetZ,//觀測點的X、Y、Z位置
27 Constant.UpX, Constant.UpY, Constant.UpZ); //up向量的X、Y、Z分量
28 break;
29 case MotionEvent.ACTION_UP:
30 if (Constant.feeding) { //標志位,開始喂食
31 if (Constant.isFeed) {
32 Constant.isFeed = false; //把標志位置為false
33 float[] AB = IntersectantUtil.calculateABPosition( //通過矩陣變換獲取世界坐標系中的點
34 x, y, //觸控點X、Y坐標
35 Constant.SCREEN_WIDTH, Constant.SCREEN_HEGHT, //屏幕寬、長度
36 Constant.leftABS, Constant.topABS, //視角left、top值
37 Constant.nearABS, Constant.farABS); //視角near、far值
38 Vector Start = new Vector(AB[0], AB[1], AB[2]); //起點
39 Vector End = new Vector(AB[3], AB[4], AB[5]); //終點
40 if (MySurfaceView.feedfish != null) { //判斷不為空啟動
41 MySurfaceView.feedfish.startFeed(Start, End); //開始喂食
42 }}}
43 break;
44 }
45 mPreviousY = y; //記錄觸控筆Y位置
46 mPreviousX = x; //記錄觸控筆X位置
47 super.onTouchEvent(e);
48 }
第1~9行首先創建變量,用于記錄觸控筆上一次的觸控X位置和Y位置,然后獲取當前觸控點的X坐標和Y坐標,并且響應屏幕的觸控事件,對ACTION_DOWN事件進行監聽,當觸發時將喂食標志位設為true。
第10~28行對ACTION_MOVE事件進行監聽,獲取手指在屏幕上的移動距離,按照一定比例移動攝像機X坐標,同時,如果攝像機X坐標達到閾值,則攝像機不會向滑動方向移動;然后將攝像機的位置信息存入到矩陣中,設置攝像機的位置,觀測點的坐標和up向量。
第30~37行是判斷喂食的標志位,因為滑動屏幕不能喂食,當前喂的食物在沒有消失之前也不能喂食,所以需要兩個標志位對其進行控制。一個在點擊喂食的時候為true,另一個在沒有喂食之前為true,然后通過矩陣變換獲取觸控點在世界坐標系中的坐標。
第38~47行通過拾取計算獲取觸控點在世界坐標系中的起點(近平面點)坐標、終點(遠平面點)坐標,判斷feedfish不為空,則調用startFeed方法開始喂食;然后記錄觸控筆的X位置、Y位置,回調父類的方法。
2.4.2 自定義渲染器類——MySurfaceView
下面將詳細介紹自定義的場景渲染器代碼,在自定義的場景渲染器類里,可以進行魚、魚群、烏龜、珍珠貝、氣泡、背景圖、魚食的初始化。初始化魚類和烏龜的初始速度、初始位置以及加載紋理等,并且設置光源位置,初始化矩陣等。
(1)由于MySurfaceView類中繪制代碼以及初始化代碼比較多,在此首先介紹該類的繪制代碼以及整體框架,使讀者對此類有一個大致的了解。具體代碼如下所示。
1 package wyf.lxg.mywallpaper;
2 .....//此處省略部分類和包的導入代碼,請讀者自行查閱隨書附帶光盤的源代碼
3 public class MySurfaceView extends GLSurfaceView
4 implements GLSurfaceView.Renderer,OpenGLES2WallpaperService.Renderer {
5 public MySurfaceView(Context context) {
6 super(context); //獲取上下文對象
7 this.setEGLContextClientVersion(2); //設置使用OPENGL ES2.0
8 }
9 .....//此處省略相關成員變量的聲明代碼,請讀者自行查閱隨書附帶光盤的源代碼
10 public void onDrawFrame(GL10 gl) {
11 GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT //清除深度緩沖與顏色緩沖
12 | GLES20.GL_COLOR_BUFFER_BIT);
13 MatrixState.pushMatrix(); //保護矩陣
14 if(bg!=null){ bg.drawSelf(back); } //繪制背景圖
15 if(singlefood!=null) { singlefood.drawSelf();} //繪制魚食
16 if (fishControl != null) { fishControl.drawSelf();} //繪制單條魚和烏龜
17 if (fishSchool != null) { fishSchool.drawSelf();} //繪制魚群
18 .....//此處繪制其他魚群的代碼與上述相似,故省略,請讀者自行查閱隨書附帶光盤的源代碼
19 MatrixState.pushMatrix(); //保護矩陣
20 MatrixState.translate(,-16,); //平移到指定位置
21 this.haibei.animate(time,dpm); //繪制珍珠貝
22 MatrixState.popMatrix(); //恢復矩陣
23 time += ;
24 if(time > this.ms3d.getTotalTime()) { //若當前播放時間大于總的動畫時間
25 time = time - this.ms3d.getTotalTime();//則播放時間等于當前播放時間減去總的動畫時間
26 }
27 MatrixState.popMatrix(); //恢復矩陣
28 GLES20.glEnable(GLES20.GL_BLEND); //開啟混合
29 GLES20.glBlendFunc(GLES20.GL_SRC_COLOR, //設置混合因子c
30 GLES20.GL_ONE_MINUS_SRC_COLOR);
31 MatrixState.pushMatrix(); //保護矩陣
32 if(bubble!=null) { bubble.drawSelf();} //繪制氣泡
33 MatrixState.popMatrix(); //恢復矩陣
34 GLES20.glDisable(GLES20.GL_BLEND); //關閉混合
35 }
36 public void onSurfaceChanged(GL10 gl, int width, int height) {
37 .....//此處省略設置攝像機的代碼,將在后面詳細介紹
38 }
39 public void onSurfaceCreated(GL10 gl, EGLConfig config) {
40 .....//此處省略初始化的代碼,將在后面詳細介紹
41 }
42 public int initTexture(Resources res,String pname)//textureId{
43 .....//此處省略加載紋理的代碼,將在后面詳細介紹
44 }}
第1~9行為聲明包名,其中部分類和包的導入代碼、相關成員變量的聲明代碼在此處省略,請讀者自行查閱隨書附帶的光盤代碼;然后創建構造方法,獲取父類上下文對象,設置使用OPENGL ES2.0渲染技術進行繪制。
第11~18行首先清除深度緩沖與顏色緩沖,進行現場保護,判斷背景、魚食、單條魚、烏龜以及魚群的引用若不為空,依次繪制背景圖、魚食、單條魚、烏龜以及魚群。這里只給出了黃色小丑魚群的繪制代碼,其他魚群繪制代碼與之相似,請讀者自行查閱隨書附帶光盤的源代碼。
第19~27行為繪制珍珠貝的代碼。首先保護現場,然后平移到指定位置,繪制珍珠貝,恢復現場。并且不斷更新動畫播放時間,若當前播放時間大于總的動畫時間,則實際播放時間等于當前播放時間減去總的動畫時間。
第28~34行為繪制氣泡的代碼。首先開啟混合,設置混合因子,保護現場,判斷氣泡引用不為空,則進行氣泡的繪制,然后恢復現場,關閉混合。
(2)下面介紹上面省略的onSurfaceChanged方法。重寫該方法,主要作用是設置視口的大小及位置、計算GLSurfaceView的寬高比、通過計算產生投影矩陣以及攝像機9參數位置矩陣。該方法是場景渲染器類不可或缺的。具體代碼如下所示。
1 public void onSurfaceChanged(GL10 gl, int width, int height) {
2 GLES20.glViewport(0, 0, width, height); //設置視窗大小及位置
3 float ratio = (float) width / height; //計算GLSurfaceView的寬高比
4 Constant.SCREEN_HEGHT=height; //獲取屏幕高度
5 Constant.SCREEN_WIDTH=width; //獲取屏幕寬度
6 Constant.leftABS=ratio*Constant.View_SCALE; //設置left值
7 Constant.topABS=1 * Constant.View_SCALE; //設置top值
8 Constant.SCREEN_SCALEX=Constant.View_SCALE*((ratio>1)?ratio:(1/ratio)); //設置縮放比
9 MatrixState.setProjectFrustum(-Constant.leftABS, Constant.leftABS, //產生透視投影矩陣
10 -Constant.topABS, Constant.topABS, Constant.nearABS,Constant.farABS);
11 MatrixState.setCamera( //產生攝像機9參數位置矩陣
12 Constant.CameraX //攝像機的X位置
13 Constant.CameraY, //攝像機的Y位置
14 Constant.CameraZ, //攝像機的Z位置
15 Constant.TargetX, //觀測點的X位置
16 Constant.TargetY, //觀測點的Y位置
17 Constant.TargetZ, //觀測點的Z位置
18 Constant.UpX, //up向量的X分量
19 Constant.UpY, //up向量的Y分量
20 Constant.UpZ); //up向量的Z分量
21 }
第1~10行用于設置視窗大小及位置,獲取屏幕高度以及寬度,設置視角的left值以及top值,計算橫屏豎屏縮放比,產生透視投影矩陣。這里使用透視投影矩陣是為了更真實地模擬現實世界,產生近大遠小的效果。
第11~20行用于產生攝像機的9參數位置矩陣,分別設置攝像機的XYZ位置、觀測點的XYZ位置以及up向量的XYZ分量,這里將攝像機位置矩陣的9參數都存放在Constant類中,是為了便于壁紙左右移動時修改攝像機的位置。
(3)下面介紹上面省略的onSurfaceCreated方法。重寫該方法,主要作用是初始化光源位置,創建紋理管理器,加載紋理,獲取ms3d文件的輸入流,加載ms3d模型,創建魚類、烏龜、珍珠貝等對象,開啟深度檢測等。具體代碼如下所示。
1 public void onSurfaceCreated(GL10 gl, EGLConfig config){
2 GLES20.glClearColor(,,, ); //設置屏幕背景色RGBA
3 MatrixState.setInitStack(); //初始化矩陣
4 MatrixState.setLightLocation(0,9,13); //初始化光源位置
5 manager = new TextureManager(getResources()); //創建紋理管理器對象
6 dpm=initTexture(MySurfaceView.this.getResources(),"dpm.png");//加載明暗紋理
7 String name="ms3d/"; //獲取ms3d文件的輸入流
8 InputStream in=null;
9 ......//此處其他輸入流的聲明與上述相同,故省略,請讀者自行查閱隨書附帶光盤的源代碼
10 try{in=getResources().getAssets().open(name+"fish0.ms3d");//鰩魚
12 ......//此處其他獲取輸入流與上述相同,故省略,請讀者自行查閱隨書附帶光盤的源代碼
13 } catch(Exception e){
14 e.printStackTrace(); //打印異常棧信息
15 }
16 ms3d = MS3DModel.load(in,manager,MySurfaceView.this); //從輸入流加載模型
17 ......//此處加載模型代碼與上述相同,故省略,請讀者自行查閱隨書附帶光盤的源代碼
18 if(fishAl.size() ){
19 fishAl.add(new SingleFish(ms3d,dpm, //位置、速度、力、吸引力、重力
20 new Vector(-7, 5, -7), new Vector(, , ),
21 new Vector(0, 0, 0), new Vector(0, 0, 0), 150));
22 .....//此處添加魚的代碼與上述相同,故省略,請讀者自行查閱隨書附帶光盤的源代碼
23 }
24 back=initTexture(MySurfaceView.this.getResources(),"background.png"); //背景紋理
25 fishfood=initTexture(MySurfaceView.this.getResources(),"fishfood.png"); //魚食紋理
26 bubbles=initTexture(MySurfaceView.this.getResources(),"bubble.png");//氣泡紋理
27 GLES20.glEnable(GLES20.GL_DEPTH_TEST); //打開深度檢測
28 bg=new Background(MySurfaceView.this); //創建背景對象
29 bubble = new BubbleControl(MySurfaceView.this,bubbles); //創建氣泡對象
30 fishfoods=LoadUtil.loadFromFile("fishfood.obj", //魚食對象
31 MySurfaceView.this.getResources(),MySurfaceView.this);
32 singlefood=new SingleFood(fishfood,fishfoods, MySurfaceView.this);//單個魚食對象
33 feedfish=new FeedFish(MySurfaceView.this); //喂食
34 if (fishControl == null) { //創建對象魚類的Control對象
35 fishControl = new FishControl(fishAl, MySurfaceView.this);
36 }
37 if (fishSchool == null) { //創建魚群對象的Control
38 fishSchool = new FishSchoolControl(ms3d3,dpm,MySurfaceView.this,
39 new Vector(5, -2, 4),new Vector(, , ),50);//位置、速度、質量
40 }
41 ......//此處添加魚群的代碼與上述相同,故省略,請讀者自行查閱隨書附帶光盤的源代碼
42 GLES20.glDisable(GLES20.GL_CULL_FACE); //關閉背面剪裁
43 }
第1~6行為設置背景色的RGBA通道,初始化矩陣,只有初始化矩陣之后,保護矩陣、恢復矩陣等才能起作用。初始化光源位置,將光源置于場景的正上方。創建紋理管理器對象,用于加載紋理圖,并且加載呈現明暗效果的紋理圖。
第7~23行為獲取ms3d文件的輸入流,從輸入流中加載ms3d模型,向魚類列表中添加單條魚、烏龜等。這里僅給出了fish0的加載代碼,其他種類魚、烏龜以及珍珠貝的加載代碼與此相似,故省略,請讀者自行查閱隨書附帶光盤的源代碼。
第24~42行為加載背景、魚食以及氣泡的紋理,打開深度檢測,創建背景、氣泡、魚食、喂食、魚類以及魚群的對象。其中在創建魚群對象時,只給出了創建黃色小丑魚魚群的代碼,其他魚群的創建代碼與此相似,故省略,請讀者自行查閱隨書附帶光盤的源代碼。
(4)下面詳細介紹上面省略的initTexture方法。該方法的主要作用是通過輸入流從assets中加載圖片,生成紋理ID,設置紋理的拉伸方式,設置紋理采樣方式等。具體代碼如下所示。
1 public int initTexture(Resources res,String pname){ //初始化紋理
2 int[] textures = new int[1]; //生成紋理ID
3 GLES20.glGenTextures(
4 1, //產生的紋理id的數量
5 textures, //紋理id的數組
6 0 //偏移量
7 );
8 int textureId=textures[0];
9 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);//綁定紋理
10 GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, //最近點采樣
11 GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_NEAREST);
12 GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, //線性紋理過濾
13 GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR);
14 GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, //縱向拉伸方式
15 GLES20.GL_TEXTURE_WRAP_S,GLES20.GL_REPEAT);
16 GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, //橫向拉伸方式
17 GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_REPEAT);
18 InputStream is = null; //創建輸入流
19 String name="pic/"+pname;
20 try {
21 is = res.getAssets().open(name); //加載紋理圖片
22 } catch (IOException e1) {
23 e1.printStackTrace(); //異常處理
24 }
25 Bitmap bitmapTmp; //創建Bitmap對象
26 try {
27 bitmapTmp = BitmapFactory.decodeStream(is); //對獲取的圖片解碼
28 } finally {
29 try {
30 is.close(); //關閉輸入流
31 }catch(IOException e) {
32 e.printStackTrace(); //異常處理
33 }}
34 GLUtils.texImage2D(
35 GLES20.GL_TEXTURE_2D, //紋理類型
36 0, //紋理的層次
37 bitmapTmp, //紋理圖像
38 0 //紋理邊框尺寸
39 );
40 bitmapTmp.recycle(); //釋放Bitmap
41 return textureId;
42 }
第2~17行為定義紋理ID、生成紋理ID數組、以及綁定紋理。同時設置紋理的過濾方式分別為最近點采樣過濾和線性紋理過濾,設置紋理的拉伸方式為縱向拉伸方式和橫向拉伸方式并且都為重復拉伸方式。
第18~33行為創建輸入流,從assets中加載紋理圖片,創建Bitmap對象,對獲取的圖片進行解碼,然后關閉輸入流。其中關閉輸入流是非常重要的,加載完圖片,一定要記得關閉輸入流,否則會造成資源浪費。
第34~41行為指定紋理。首先是紋理類型,在OpenGL ES中必須為GLES20.GL_ TEXTURE_2D;其次是紋理的層次,0表示基本圖像層,可以理解為直接貼圖;然后是紋理的圖像以及邊框尺寸;最后釋放Bitmap,返回紋理ID。
總結
以上是生活随笔為你收集整理的《Android 应用案例开发大全(第3版)》——第2.4节壁纸的实现的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。