OPhone动态壁纸探究

OPhone平台开发, 2010-08-06 14:50:47

标签 : 动态壁纸 Live Wallpapers 时间壁纸 TimeWall

随着三星Oscar的上市,流畅的操作,华丽的界面,OPhone 2.0的不俗表现不禁让人眼前一亮。作为OPhone 2.0一个新特性,动态壁纸(Live Wallpapers)为用户带来了更炫体验。本文主要通过一个完整的时间壁纸(TimeWall)为大家介绍如何开发 Live Wallpapers。还没开发环境?赶紧去下载OPhone SDK 2.0吧!

1、 Live Wallpapers是什么?
在oscar上有一个动态壁纸叫“天空草地”,用过一段时间,可以发现,随着时间的变化,壁纸的天空就会由蓝蓝青天变成繁星满天。看看效果:

为什么壁纸还有这么神奇的变化,这中间到底是什么在起作用?其实,一个Live Wallpaper就是一个apk!也就是说,动态壁纸的实质是一个apk在后台不断地重绘壁纸,所以我们可以让小草长高,小鸟飞翔。

来看一下我们TimeWall的AndoridManifest.xml:

view plaincopy to clipboardprint?
<?xml version="1.0" encoding="utf-8"?> 
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
 package="com.oms.LiveWall" android:versionCode="1" 
 android:versionName="1.0"> 
 <application android:icon="@drawable/icon" android:label="@string/app_name"> 
 <service android:label="@string/app_name" android:name=".TimeWall" 
 android:permission="android.permission.BIND_WALLPAPER"> 
 <intent-filter> 
 <action android:name="android.service.wallpaper.WallpaperService" /> 
 </intent-filter> 
 <meta-data android:name="android.service.wallpaper" 
 android:resource="@xml/alive_wall" /> 
 </service> 
 </application> 
 <uses-sdk android:minSdkVersion="7" /> 
</manifest>



原来如此简单,动态壁纸仅仅有一个service就够了。其中

android:permission="android.permission.BIND_WALLPAPER"



是让该service有能设置为壁纸的权限,没有的话该壁纸只能被预览。

<uses-sdk android:minSdkVersion="7" />



告诉我们,如果你想开发一个live wallpaper,必须是OPhone 2.0或者更高的版本。当然这也需要手机硬件的支持。

2、怎样实现WallpaperService?
WallpaperService与其他的service唯一的不同就是,你必须要增加一个方法onCreateEngine(),它会返回一个WallpaperService.Engine,这个engine才是负责绘制壁纸以及响应与用户交互事件的核心部件。这个service代码结构如下:

public class TimeWall extends WallpaperService { 

 public Engine onCreateEngine() { 
 return new TimeEngine(); 
 } 

 public class TimeEngine extends Engine { 
 // ...more code 
 } 
}



类TimeEngine才是处理壁纸的核心类,我们会在类TimeEngine中加上自己的逻辑以完成壁纸的绘制、变化以及销毁。Engine的生命周期与大多数OPhone应用程序组件,比如activity类似,都是从onCreate()开始,在销毁时调用onDestory()方法。不同的是WallpaperService会提供一个surface用来绘制壁纸,所以在生命周期中多一个onSurfaceCreated与onSurfaceDestroyed的过程。下面是一个最简生命周期:

也就是说只要我们实现上面四个方法,一个基本的LiveWallpaper就可以完成了。让我们逐个看一下这几个方法的实现。

@Override 
public void onCreate(SurfaceHolder surfaceHolder) { 
 super.onCreate(surfaceHolder); 
 setTouchEventsEnabled(true); 
} 

@Override 
public void onDestroy() { 
 super.onDestroy(); 
 mHandler.removeMessages(DRAW); 
} 

@Override 
public void onSurfaceCreated(SurfaceHolder holder) { 
 super.onSurfaceCreated(holder); 
 mHandler.sendEmptyMessage(DRAW); 
} 

@Override 
public void onSurfaceDestroyed(SurfaceHolder holder) { 
 super.onSurfaceDestroyed(holder); 
 mHandler.removeMessages(DRAW); 
}



在onCreate方法里,我们

setTouchEventsEnabled(true);



作用是使壁纸能响应touch event,默认是false。TimeWall会在用户点击屏幕的时候画一个十字架,所以我们需要设置其为true。

可以看到我们在这四个方法里面做的事情非常简单,就是在create时候发一个message,执行画面的绘制,在destory时remove这个消息。看一下mHandler的代码:

view plaincopy to clipboardprint?
private Handler mHandler = new Handler() { 
 public void handleMessage(Message msg) { 
 switch (msg.what) { 
 case DRAW: 
 drawWall(); 
 break; 
 } 
 } 
 };


方法drawWall():

private void drawWall() { 
 SurfaceHolder holder = getSurfaceHolder(); 
 Canvas canvas = holder.lockCanvas(); 
 drawTime(canvas); 
 drawCross(canvas); 
 holder.unlockCanvasAndPost(canvas); 

 mHandler.removeMessages(DRAW); 
 mHandler.sendEmptyMessageDelayed(DRAW, 50); 
 }



从上面可以看出,动态壁纸实际上就是不断刷新的静态壁纸,越华丽越流畅,CPU就消耗越大,对于现在的本来电量就不怎么地的智能机来说,耗电也是很可观的。但是偶尔向朋友们炫一下还是绝对可行的。drawTime()与drawCross()的内容可以由家自己实现,在TimeWall里,它们比较简单。drawTime()是计算下一处Time String应该移动到的坐标,以及画出这个String。drawCross()的作用是在用户触发onTouchEvent时画一个十字架。因为TimeWall比较简单,如果大家自己实现的画图比较复杂,可以另外开启一个线程来刷新UI,否则有可能主线程被阻塞掉。(代码见附件)

看看TimeWall的效果:

附件代码:

package com.OPhonesdn.timewall; 

import java.text.SimpleDateFormat; 
import java.util.Date; 

import android.graphics.Canvas; 
import android.graphics.Color; 
import android.graphics.Paint; 
import android.os.Handler; 
import android.os.Message; 
import android.service.wallpaper.WallpaperService; 
import android.view.MotionEvent; 
import android.view.SurfaceHolder; 

public class TimeWall extends WallpaperService { 

 public Engine onCreateEngine() { 
 return new TimeEngine(); 
 }


public class TimeEngine extends Engine { 

 private final float STEP_X = 2f; 
 private final float STEP_Y = 7f; 
 private final float SCOPE_LEFT = 10f; 
 private final float SCOPE_RIGHT = 110f; 
 private final float SCOPE_TOP = 250f; 
 private final float SCOPE_BOTTOM = 600f; 
 private final float RADIUS = 20f; 
 private final int DIRECTION_1 = 1; // move to right top side 
 private final int DIRECTION_2 = 2; // move to right bottom side 
 private final int DIRECTION_3 = 3; // move to left bottom side 
 private final int DIRECTION_4 = 4; // move to left top side 
 private final int DRAW = 1; 
 private float mTouchX = -1f; 
 private float mTouchY = -1f; 
 private float mLocationX = 0f; 
 private float mLocationY = 400f; 
 private int mDirection = 1; 

 private Paint mPaint = new Paint(); 
 private Handler mHandler = new Handler() { 
 public void handleMessage(Message msg) { 
 switch (msg.what) { 
 case DRAW: 
 drawWall(); 
 break; 
 } 
 } 
 };



public TimeEngine() { 
 mPaint.setColor(Color.RED); 
 mPaint.setAntiAlias(true); 
 mPaint.setStrokeWidth(4); 
 mPaint.setStrokeCap(Paint.Cap.BUTT); 
 mPaint.setStyle(Paint.Style.STROKE); 
 mPaint.setTextSize(40); 
 }



@Override 
 public void onCreate(SurfaceHolder surfaceHolder) { 
 super.onCreate(surfaceHolder); 
 setTouchEventsEnabled(true); 
 } 

 @Override 
 public void onDestroy() { 
 super.onDestroy(); 
 mHandler.removeMessages(DRAW); 
 } 

 @Override 
 public void onSurfaceCreated(SurfaceHolder holder) { 
 super.onSurfaceCreated(holder); 
 mHandler.sendEmptyMessage(DRAW); 
 } 

 @Override 
 public void onSurfaceDestroyed(SurfaceHolder holder) { 
 super.onSurfaceDestroyed(holder); 
 mHandler.removeMessages(DRAW); 
 } 

 @Override 
 public void onTouchEvent(MotionEvent event) { 
 if (event.getAction() == MotionEvent.ACTION_MOVE 
 || event.getAction() == MotionEvent.ACTION_DOWN) { 
 mTouchX = event.getX(); 
 mTouchY = event.getY(); 
 } else { 
 mTouchX = -1; 
 mTouchY = -1; 
 } 
 super.onTouchEvent(event); 
 } 

 private void drawWall() { 
 SurfaceHolder holder = getSurfaceHolder(); 
 Canvas canvas = holder.lockCanvas(); 
 drawTime(canvas); 
 drawCross(canvas); 
 holder.unlockCanvasAndPost(canvas); 

 mHandler.removeMessages(DRAW); 
 mHandler.sendEmptyMessageDelayed(DRAW, 50); 
 } 

 private void drawTime(Canvas c) { 
 Date date = new Date(System.currentTimeMillis()); 
 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 
 String strDate = sdf.format(date); 
 c.save(); 
 c.drawColor(0xff000000); 
 c.drawText(strDate, mLocationX, mLocationY, mPaint); 
 switch (mDirection) { 
 case DIRECTION_1: 
 mLocationX = mLocationX + STEP_X; 
 mLocationY = mLocationY - STEP_Y; 
 if (mLocationY <= SCOPE_TOP) { 
 mDirection = DIRECTION_2; 
 } 
 break; 
 case DIRECTION_2: 
 mLocationX = mLocationX + STEP_X; 
 mLocationY = mLocationY + STEP_Y; 
 if (mLocationX >= SCOPE_RIGHT) { 
 mDirection = DIRECTION_3; 
 } 
 break; 
 case DIRECTION_3: 
 mLocationX = mLocationX - STEP_X; 
 mLocationY = mLocationY + STEP_Y; 
 if (mLocationY >= SCOPE_BOTTOM) { 
 mDirection = DIRECTION_4; 
 } 
 break; 
 case DIRECTION_4: 
 mLocationX = mLocationX - STEP_X; 
 mLocationY = mLocationY - STEP_Y; 
 if (mLocationX <= SCOPE_LEFT) { 
 mDirection = DIRECTION_1; 
 } 
 break; 
 } 
 c.restore(); 
 } 

 private void drawCross(Canvas c) { 
 if (mTouchX >= 0 && mTouchY >= 0) { 
 c.drawLine(mTouchX - RADIUS, mTouchY, mTouchX + RADIUS, 
 mTouchY, mPaint); 
 c.drawLine(mTouchX, mTouchY - RADIUS, mTouchX, 
 mTouchY + RADIUS, mPaint); 
 } 
 } 
 } 
}