随着
三星
Oscar的上市,流畅的操作,华丽的界面,
OPhone
2.0的不俗表现不禁让人眼前一亮。作为OPhone 2.0一个新特性,动态壁纸(Live Wallpapers)为用户带来了更炫体验。本文主要通过一个完整的时间壁纸(TimeWall)为大家介绍如何开发 Live Wallpapers。还没开发环境?赶紧去下载OPhone
SDK
2.0吧!
1、 Live Wallpapers是什么?
在oscar上有一个动态壁纸叫“天空草地”,用过一段时间,可以发现,随着时间的变化,壁纸的天空就会由蓝蓝青天变成繁星满天。看看效果:
为什么壁纸还有这么神奇的变化,这中间到底是什么在起作用?其实,一个Live Wallpaper就是一个apk!也就是说,动态壁纸的实质是一个apk在后台不断地重绘壁纸,所以我们可以让小草长高,小鸟飞翔。
来看一下AndoridManifest.xml:
-
<?
xml
version
=
“1.0”
encoding
=
“utf-8”
?>
- <!–
- /*
- **
- ** Copyright 2009, The Android Open Source Project
- **
- ** Licensed under the Apache License, Version 2.0 (the “License”);
- ** you may not use this file except in compliance with the License.
- ** You may obtain a copy of the License at
- **
- ** http://www.apache.org/licenses/LICENSE-2.0
- **
- ** Unless required by applicable law or agreed to in writing, software
- ** distributed under the License is distributed on an “AS IS” BASIS,
- ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ** See the License for the specific language governing permissions and
- ** limitations under the License.
- */
-
—
>
-
<
manifest
-
xmlns:android
=
“http://schemas.android.com/apk/res/android”
-
package
=
“com.example.android.livecubes”
>
-
<
uses-sdk
android:minSdkVersion
=
“7”
/>
-
<
uses-feature
android:name
=
“android.software.live_wallpaper”
/>
-
<
application
-
android:label
=
“@string/wallpapers”
-
android:icon
=
“@drawable/ic_launcher_wallpaper”
>
-
<
service
-
android:label
=
“@string/wallpaper_cube1”
-
android:name
=
“.cube1.CubeWallpaper1”
-
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/cube1”
/>
-
</
service
>
-
<
service
-
android:label
=
“@string/wallpaper_cube2”
-
android:name
=
“.cube2.CubeWallpaper2”
-
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/cube2”
/>
-
</
service
>
-
<
activity
-
android:label
=
“@string/cube2_settings”
-
android:name
=
“.cube2.CubeWallpaper2Settings”
-
android:theme
=
“@android:style/Theme.Light.WallpaperSettings”
-
android:exported
=
“true”
>
-
</
activity
>
-
<!–android:exported 是否可被其他程序调用 android:permission=”android.permission.BIND_WALLPAPER” 桌面服务绑定设置–>
-
</
application
>
-
</
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代码结构如下:
-
/*
-
* Copyright (C) 2009 The Android Open Source Project
-
*
-
* Licensed under the Apache License, Version 2.0 (the “License”);
-
* you may not use this file except in compliance with the License.
-
* You may obtain a copy of the License at
-
*
-
* http://www.apache.org/licenses/LICENSE-2.0
-
*
-
* Unless required by applicable law or agreed to in writing, software
-
* distributed under the License is distributed on an “AS IS” BASIS,
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-
* See the License for the specific language governing permissions and
-
* limitations under the License.
-
*/
-
package
com.example.android.livecubes.cube2; -
import
android.content.SharedPreferences; -
import
android.graphics.Canvas; -
import
android.graphics.Paint; -
import
android.graphics.Rect; -
import
android.os.Handler; -
import
android.os.SystemClock; -
import
android.service.wallpaper.WallpaperService; -
import
android.view.MotionEvent; -
import
android.view.SurfaceHolder; -
/*
-
* This animated wallpaper draws a rotating wireframe shape. It is similar to
-
* example #1, but has a choice of 2 shapes, which are user selectable and
-
* defined in resources instead of in code.
-
*/
-
public
class
CubeWallpaper2
extends
WallpaperService { -
public
static
final
String SHARED_PREFS_NAME=
“cube2settings”
; -
static
class
ThreeDPoint { -
float
x; -
float
y; -
float
z; - }
-
static
class
ThreeDLine { -
int
startPoint; -
int
endPoint; - }
-
@Override
-
public
void
onCreate() { -
super
.onCreate(); - }
-
@Override
-
public
void
onDestroy() { -
super
.onDestroy(); - }
-
@Override
-
public
Engine onCreateEngine() { -
return
new
CubeEngine(); - }
-
class
CubeEngine
extends
Engine -
implements
SharedPreferences.OnSharedPreferenceChangeListener { -
private
final
Handler mHandler =
new
Handler(); - ThreeDPoint [] mOriginalPoints;
- ThreeDPoint [] mRotatedPoints;
- ThreeDLine [] mLines;
-
private
final
Paint mPaint =
new
Paint(); -
private
float
mOffset; -
private
float
mTouchX = –
1
; -
private
float
mTouchY = –
1
; -
private
long
mStartTime; -
private
float
mCenterX; -
private
float
mCenterY; -
private
final
Runnable mDrawCube =
new
Runnable() { -
public
void
run() { - drawFrame();
- }
- };
-
private
boolean
mVisible; -
private
SharedPreferences mPrefs; - CubeEngine() {
-
// Create a Paint to draw the lines for our cube
-
final
Paint paint = mPaint; -
paint.setColor(
0xffffffff
); -
paint.setAntiAlias(
true
); -
paint.setStrokeWidth(
2
); - paint.setStrokeCap(Paint.Cap.ROUND);
- paint.setStyle(Paint.Style.STROKE);
- mStartTime = SystemClock.elapsedRealtime();
-
mPrefs = CubeWallpaper2.
this
.getSharedPreferences(SHARED_PREFS_NAME,
0
); -
mPrefs.registerOnSharedPreferenceChangeListener(
this
); -
onSharedPreferenceChanged(mPrefs,
null
); - }
-
public
void
onSharedPreferenceChanged(SharedPreferences prefs, String key) { -
String shape = prefs.getString(
“cube2_shape”
,
“cube”
); -
// read the 3D model from the resource
- readModel(shape);
- }
-
private
void
readModel(String prefix) { -
// Read the model definition in from a resource.
-
// get the resource identifiers for the arrays for the selected shape
-
int
pid = getResources().getIdentifier(prefix +
“points”
,
“array”
, getPackageName()); -
int
lid = getResources().getIdentifier(prefix +
“lines”
,
“array”
, getPackageName()); - String [] p = getResources().getStringArray(pid);
-
int
numpoints = p.length; -
mOriginalPoints =
new
ThreeDPoint[numpoints]; -
mRotatedPoints =
new
ThreeDPoint[numpoints]; -
for
(
int
i =
0
; i < numpoints; i++) { -
mOriginalPoints[i] =
new
ThreeDPoint(); -
mRotatedPoints[i] =
new
ThreeDPoint(); -
String [] coord = p[i].split(
” ”
); -
mOriginalPoints[i].x = Float.valueOf(coord[
0
]); -
mOriginalPoints[i].y = Float.valueOf(coord[
1
]); -
mOriginalPoints[i].z = Float.valueOf(coord[
2
]); - }
- String [] l = getResources().getStringArray(lid);
-
int
numlines = l.length; -
mLines =
new
ThreeDLine[numlines]; -
for
(
int
i =
0
; i < numlines; i++) { -
mLines[i] =
new
ThreeDLine(); -
String [] idx = l[i].split(
” ”
); -
mLines[i].startPoint = Integer.valueOf(idx[
0
]); -
mLines[i].endPoint = Integer.valueOf(idx[
1
]); - }
- }
-
@Override
-
public
void
onCreate(SurfaceHolder surfaceHolder) { -
super
.onCreate(surfaceHolder); -
setTouchEventsEnabled(
true
); - }
-
@Override
-
public
void
onDestroy() { -
super
.onDestroy(); - mHandler.removeCallbacks(mDrawCube);
- }
-
@Override
-
public
void
onVisibilityChanged(
boolean
visible) { - mVisible = visible;
-
if
(visible) { - drawFrame();
-
}
else
{ - mHandler.removeCallbacks(mDrawCube);
- }
- }
-
@Override
-
public
void
onSurfaceChanged(SurfaceHolder holder,
int
format,
int
width,
int
height) { -
super
.onSurfaceChanged(holder, format, width, height); -
// store the center of the surface, so we can draw the cube in the right spot
-
mCenterX = width/
2
.0f; -
mCenterY = height/
2
.0f; - drawFrame();
- }
-
@Override
-
public
void
onSurfaceCreated(SurfaceHolder holder) { -
super
.onSurfaceCreated(holder); - }
-
@Override
-
public
void
onSurfaceDestroyed(SurfaceHolder holder) { -
super
.onSurfaceDestroyed(holder); -
mVisible =
false
; - mHandler.removeCallbacks(mDrawCube);
- }
-
@Override
-
public
void
onOffsetsChanged(
float
xOffset,
float
yOffset, -
float
xStep,
float
yStep,
int
xPixels,
int
yPixels) { - mOffset = xOffset;
- drawFrame();
- }
-
/*
-
* Store the position of the touch event so we can use it for drawing later
-
*/
-
@Override
-
public
void
onTouchEvent(MotionEvent event) { -
if
(event.getAction() == MotionEvent.ACTION_MOVE) { - mTouchX = event.getX();
- mTouchY = event.getY();
-
}
else
{ -
mTouchX = –
1
; -
mTouchY = –
1
; - }
-
super
.onTouchEvent(event); - }
-
/*
-
* Draw one frame of the animation. This method gets called repeatedly
-
* by posting a delayed Runnable. You can do any drawing you want in
-
* here. This example draws a wireframe cube.
-
*/
-
void
drawFrame() { -
final
SurfaceHolder holder = getSurfaceHolder(); -
final
Rect frame = holder.getSurfaceFrame(); -
final
int
width = frame.width(); -
final
int
height = frame.height(); -
Canvas c =
null
; -
try
{ - c = holder.lockCanvas();
-
if
(c !=
null
) { -
// draw something
- drawCube(c);
- drawTouchPoint(c);
- }
-
}
finally
{ -
if
(c !=
null
) holder.unlockCanvasAndPost(c); - }
- mHandler.removeCallbacks(mDrawCube);
-
if
(mVisible) { -
mHandler.postDelayed(mDrawCube,
1000
/
25
); - }
- }
-
void
drawCube(Canvas c) { - c.save();
- c.translate(mCenterX, mCenterY);
-
c.drawColor(
0xff000000
); -
long
now = SystemClock.elapsedRealtime(); -
float
xrot = ((
float
)(now – mStartTime)) /
1000
; -
float
yrot = (
0
.5f – mOffset) *
2
.0f; - rotateAndProjectPoints(xrot, yrot);
- drawLines(c);
- c.restore();
- }
-
void
rotateAndProjectPoints(
float
xrot,
float
yrot) { -
int
n = mOriginalPoints.length; -
for
(
int
i =
0
; i < n; i++) { -
// rotation around X-axis
- ThreeDPoint p = mOriginalPoints[i];
-
float
x = p.x; -
float
y = p.y; -
float
z = p.z; -
float
newy = (
float
)(Math.sin(xrot) * z + Math.cos(xrot) * y); -
float
newz = (
float
)(Math.cos(xrot) * z – Math.sin(xrot) * y); -
// rotation around Y-axis
-
float
newx = (
float
)(Math.sin(yrot) * newz + Math.cos(yrot) * x); -
newz = (
float
)(Math.cos(yrot) * newz – Math.sin(yrot) * x); -
// 3D-to-2D projection
-
float
screenX = newx / (
4
– newz /
400
); -
float
screenY = newy / (
4
– newz /
400
); - mRotatedPoints[i].x = screenX;
- mRotatedPoints[i].y = screenY;
-
mRotatedPoints[i].z =
0
; - }
- }
-
void
drawLines(Canvas c) { -
int
n = mLines.length; -
for
(
int
i =
0
; i < n; i++) { - ThreeDLine l = mLines[i];
- ThreeDPoint start = mRotatedPoints[l.startPoint];
- ThreeDPoint end = mRotatedPoints[l.endPoint];
- c.drawLine(start.x, start.y, end.x, end.y, mPaint);
- }
- }
-
void
drawTouchPoint(Canvas c) { -
if
(mTouchX >=
0
&& mTouchY >=
0
) { -
c.drawCircle(mTouchX, mTouchY,
80
, mPaint); - }
- }
- }
- }
-
/*
-
* Copyright (C) 2009 Google Inc.
-
*
-
* Licensed under the Apache License, Version 2.0 (the “License”); you may not
-
* use this file except in compliance with the License. You may obtain a copy of
-
* the License at
-
*
-
* http://www.apache.org/licenses/LICENSE-2.0
-
*
-
* Unless required by applicable law or agreed to in writing, software
-
* distributed under the License is distributed on an “AS IS” BASIS, WITHOUT
-
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-
* License for the specific language governing permissions and limitations under
-
* the License.
-
*/
-
package
com.example.android.livecubes.cube2; -
import
com.example.android.livecubes.R; -
import
android.content.SharedPreferences; -
import
android.os.Bundle; -
import
android.preference.PreferenceActivity; -
public
class
CubeWallpaper2Settings
extends
PreferenceActivity -
implements
SharedPreferences.OnSharedPreferenceChangeListener { -
@Override
-
protected
void
onCreate(Bundle icicle) { -
super
.onCreate(icicle); - getPreferenceManager().setSharedPreferencesName(
- CubeWallpaper2.SHARED_PREFS_NAME);
- addPreferencesFromResource(R.xml.cube2_settings);
- getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(
-
this
); - }
-
@Override
-
protected
void
onResume() { -
super
.onResume(); - }
-
@Override
-
protected
void
onDestroy() { - getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(
-
this
); -
super
.onDestroy(); - }
-
public
void
onSharedPreferenceChanged(SharedPreferences sharedPreferences, - String key) {
- }
- }
类TimeEngine才是处理壁纸的核心类,我们会在类TimeEngine中加上自己的逻辑以完成壁纸的绘制、变化以及销毁。Engine的生命周期与大多数OPhone
应用程序
组件,比如activity类似,都是从onCreate()开始,在销毁时调用onDestory()方法。不同的是WallpaperService会提供一个surface用来绘制壁纸,所以在生命周期中多一个onSurfaceCreated与onSurfaceDestroyed的过程。下面是一个最简生命周期:
也就是说只要我们实现上面四个方法,一个基本的LiveWallpaper就可以完成了。让我们逐个看一下这几个方法的实现。
nCreate方法里,我们
setTouchEventsEnabled(true);
作用是使壁纸能响应touch event,默认是false。TimeWall会在用户点击屏幕的时候画一个十字架,所以我们需要设置其为true。
可以看到我们在这四个方法里面做的事情非常简单,就是在create时候发一个message,执行画面的绘制,在destory时remove这个消息。
从上面可以看出,动态壁纸实际上就是不断刷新的静态壁纸,越华丽越流畅,CPU就消耗越大,对于现在的本来电量就不怎么地的智能机来说,耗电也是很可观的。但是偶尔向朋友们炫一下还是绝对可行的。drawTime()与 drawCross()的内容可以由家自己实现,在TimeWall里,它们比较简单。drawTime()是计算下一处Time String应该移动到的坐标,以及画出这个String。drawCross()的作用是在用户触发onTouchEvent时画一个十字架。因为 TimeWall比较简单,如果大家自己实现的画图比较复杂,可以另外开启一个线程来刷新
UI
,否则有可能主线程被阻塞掉。
cube2.xml
-
<wallpaper xmlns:android=
“http://schemas.android.com/apk/res/android”
-
android:settingsActivity=
“com.example.android.livecubes.cube2.CubeWallpaper2Settings”
- />
cube2_setting
-
<
PreferenceScreen
xmlns:android
=
“http://schemas.android.com/apk/res/android”
-
android:title
=
“@string/cube2_settings”
-
android:key
=
“cube2wallpaper_settings”
>
-
<
ListPreference
-
android:key
=
“cube2_shape”
-
android:title
=
“@string/cube2_settings_title”
-
android:summary
=
“@string/cube2_settings_summary”
-
android:entries
=
“@array/cube2_shapenames”
-
android:entryValues
=
“@array/cube2_shapeprefix”
/>
-
</
PreferenceScreen
>
