LiveWallpaperをつくる

LiveWallpaperを作るメモ。
Handlerを利用してメッセージを処理する(描画を行う)サンプルです。

概要

必要条件
実装メモ
  • WallpaperServiceを実装する
  • WallpaperService.Engine、を実装する
  • Handlerを通じて描画を行う
  • layoutのxmlは使用できない
  • AndroidManifest.xmlファイルにlive_wallpaperを設定
  • SurfaceView、Canvasで描画

実装

プロジェクトの作成

API lvevel 7(Android 2.1)以上でAndroidプリプロジェクトを生成します。

WallpaperService継承クラス作成

WallpaperServiceを継承するクラスを作成します。
このクラスがLiveWallpaperサービスの主な実装となります。

  • ExampleWallpaer.java
package com.ttshrk.livewallpaper_example;

import android.os.Handler;
import android.service.wallpaper.WallpaperService;
import android.service.wallpaper.WallpaperService.Engine;

public class ExampleWallpaer extends WallpaperService {

    private final Handler mHandler = new Handler();
    
    @Override
    public void onCreate() {
        super.onCreate();
    }

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

    @Override
    public Engine onCreateEngine() {
    	// TODO
    	return null;
    }
}

このクラスの中のWallpaperService::onCreateEngineが描画を行うインスタンス(後述)を返します。
このEngineクラスのインスタンスは複数作成されることがあるため、静的プロパティの扱いに注意が必要です。(壁紙に設定してあるLiveWallpaperを設定画面のプレビューで表示したときなど。)

WallpaperService.Engineを実装する

壁紙の描画を行うクラスExampleWallpaer.ExampleEngineを作成します。
このクラスは、WallpaperService.Engineを継承します。

  • WallpaperService.java
public class ExampleWallpaer extends WallpaperService {
	private final Handler mHandler = new Handler();

	@Override
	public void onCreate() {
		super.onCreate();
	}

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

	@Override
	public Engine onCreateEngine() {
		return new ExampleEngine();
	}

	class ExampleEngine extends Engine {
		Context context;
		int width;
		int height;
		boolean mVisible;
		PointF timePoint = new PointF();
		Paint timePaint;
		float mMillisPerFrame;
		SimpleDateFormat simpleDateFormat;
		String timeString;

		private final Runnable mDrawer = new Runnable() {
			public void run() {
				drawFrame();
			}
		};

		ExampleEngine() {
			context = ExampleWallpaer.this.getApplicationContext();
			width = getWallpaperDesiredMinimumWidth() / 2;
			height = getWallpaperDesiredMinimumHeight();
			mMillisPerFrame = 1000f / 30f;
			simpleDateFormat = new SimpleDateFormat("HH:mm:ss");
			timePaint = new Paint();
			timePaint.setAntiAlias(true);
			timePaint.setColor(Color.WHITE);
			timePaint.setTextSize(80);
			timePaint.setTextAlign(Paint.Align.RIGHT);
			timePaint.setAlpha(0xcf);
			timePoint.set(width, height * 3 / 5);
			timeString = "";
		}

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

		@Override
		public void onDestroy() {
			super.onDestroy();
			mHandler.removeCallbacks(mDrawer);
		}

		@Override
		public void onVisibilityChanged(boolean visible) {
			mVisible = visible;
			if (visible) {
				drawFrame();
			} else {
				mHandler.removeCallbacks(mDrawer);
			}
		}

		@Override
		public void onSurfaceChanged(SurfaceHolder holder, int format,
				int width, int height) {
			super.onSurfaceChanged(holder, format, width, height);
			drawFrame();
		}

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

		@Override
		public void onSurfaceDestroyed(SurfaceHolder holder) {
			super.onSurfaceDestroyed(holder);
			mVisible = false;
			mHandler.removeCallbacks(mDrawer);
		}

		@Override
		public void onOffsetsChanged(float xOffset, float yOffset, float xStep,
				float yStep, int xPixels, int yPixels) {
			drawFrame();
		}

		/*
		 * Store the position of the touch event so we can use it for drawing
		 * later
		 */
		@Override
		public void onTouchEvent(MotionEvent event) {
			timePoint.set(event.getX(), event.getY());
			super.onTouchEvent(event);
		}

		void drawFrame() {
			final SurfaceHolder holder = getSurfaceHolder();

			Canvas c = null;
			try {
				c = holder.lockCanvas();
				if (c != null) {
					update();
					c.drawColor(Color.BLACK);
					draw(c);
				}
			} finally {
				if (c != null)
					holder.unlockCanvasAndPost(c);
			}

			// Reschedule the next redraw
			mHandler.removeCallbacks(mDrawer);
			if (mVisible) {
				mHandler.postDelayed(mDrawer, (int) mMillisPerFrame);
			}
		}

		void update() {
			timeString = simpleDateFormat.format(new Date());
		}

		void draw(Canvas canvas) {
			canvas.drawText(timeString, timePoint.x, timePoint.y, timePaint);
		}

	}
}


ポイントは以下の通りです。

  • Handler

ExampleWallpaer#mHandlerにメッセージ(mDrawer)をPostして描画を行います。Handlerクラスは受け取ったメッセージを独立したスレッドで順次処理していくため、サービスの実行を阻害せずに描画を行ってくれます。

  • ExampleEngine#mDrawer

描画を行うRnnbable(メッセージ)です。

  • ExampleEngine#onVisibilityChanged

壁紙が表示、非表示されたタイミングで呼ばれます。
このイベントで、描画の開始や停止を設定します。

  • ExampleEngine#drawFrame

1frameあたりの処理を行います。また、次に行う描画処理の登録も行います。

AndroidManifest.xml

AndroidManifest.xmlにLiveWallpaperの設定を作成します。

  • AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.ttshrk.livewallpaper_example"
      android:versionCode="1"
      android:versionName="1.0">
    <uses-sdk android:minSdkVersion="7" />
    <uses-feature android:name="android.software.live_wallpaper" />

    <application
        android:label="@string/app_name"
        android:icon="@drawable/icon" android:debuggable="true">

        <service
            android:label="@string/app_name"
            android:name=".ExampleWallpaer"
            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/wallpaper" />
        </service>
        
    </application>
</manifest>


ポイントは以下の通りです。

  • live_wallpaperの設定

uses-featureにandroid.software.live_wallpaperを設定します。

<uses-feature android:name="android.software.live_wallpaper" />
  • serviceの登録

ExampleWallpaerをサービスとして登録します。

  • wallpaperのxmlファイルを設定

壁紙の設定を記述したXMLファイルを、meta-data指定します。

<meta-data android:name="android.service.wallpaper" android:resource="@xml/wallpaper" />
リソースファイル作成

壁紙の設定を記述したXMLファイルを作成します。
これはライブ壁紙の設定画面の一覧に表示されます。

<?xml version="1.0" encoding="utf-8"?>
<wallpaper xmlns:android="http://schemas.android.com/apk/res/android"  
	android:thumbnail="@drawable/icon" 
	android:description="@string/service_description" />


stringリソース設定。

  • res/values/strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">Livewallpaper Example</string>
    <string name="service_description">壁紙の説明</string>
</resources>


以上です。
実行すると壁紙に時刻が表示されます。また、タッチしたポイントに表示が移動します。


サンプルコード
livewallpaper_example.zip 直