
Живые обои для андроида представляют собой сервис (фоновый процесс, в терминах платформы), причем в SDK уже существует готовый класс для живых обоев, в котором нужно переопределить несколько методов, чтобы получить результат.
Для начала в манифесте описывается, что приложение будет предоставлять живые обои:
<service
android:name="LiveWallpaperService"
android:enabled="true"
android:icon="@drawable/icon"
android:label="@string/app_name"
android:permission="android.permission.BIND_WALLPAPER">
<intent-filter android:priority="1" >
<action android:name="android.service.wallpaper.WallpaperService" />
</intent-filter>
<meta-data
android:name="android.service.wallpaper"
android:resource="@xml/wallpaper" />
</service>
Самые важные строки здесь android:permission и intent-filter
Потом, собственно, кодирование. Мы создадим свой класс-наследник WallpaperService и переопределим его методы onCreate, onDestroy, onVisibilityChanged, onSurfaceChanged, onSurfaceCreated, onSurfaceDestroyed
Эти методы будут реагировать на системные события. Главное их назначение — включать и выключать отрисовку обоев, когда меняется их видимость, чтобы экономить батарею.
Фактически, нас интересуют только:
@Override
public void onVisibilityChanged(boolean visible) {
if (visible) {
painting.resumePainting();
} else {
// remove listeners and callbacks here
painting.pausePainting();
}
}
@Override
public void onDestroy() {
super.onDestroy();
// remove listeners and callbacks here
painting.stopPainting();
}
@Override
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
super.onSurfaceChanged(holder, format, width, height);
painting.setSurfaceSize(width, height);
Log.d("Clock", "Changed");
}
@Override
public void onSurfaceCreated(SurfaceHolder holder) {
super.onSurfaceCreated(holder);
painting.start();
}
@Override
public void onSurfaceDestroyed(SurfaceHolder holder) {
super.onSurfaceDestroyed(holder);
boolean retry = true;
painting.stopPainting();
while (retry) {
try {
painting.join();
retry = false;
} catch (InterruptedException e) {}
}
}
Саму отрисовку мы реализуем в отдельном потоке, чтобы реакции на пользовательские действия были своевременными и плавными. Для этого создадим еще одни класс-наследник Thread. Объект painting в примере выше и является этим потоком:
public ClockWallpaperPainting(SurfaceHolder surfaceHolder, Context context, int fontColor, int backgroundColor) {
this.surfaceHolder = surfaceHolder;
this.context_ = context;
clockFont_ = Typeface.createFromAsset(context.getAssets(), "digital_font.ttf");
textPainter_.setTypeface(clockFont_);
textPainter_.setTextSize(30);
setPreferences(fontColor, backgroundColor);
this.wait_ = true;
}
@Override
public void run() {
this.run_ = true;
Canvas c = null;
while (run_) {
try {
c = this.surfaceHolder.lockCanvas(null);
synchronized (this.surfaceHolder) {
doDraw( c );
}
} finally {
if (c != null) {
this.surfaceHolder.unlockCanvasAndPost( c );
}
}
// pause if no need to animate
synchronized (this) {
if (wait_) {
try {
wait();
} catch (Exception e) {}
}
}
}
}
public void pausePainting() {
this.wait_ = true;
synchronized(this) {
this.notify();
}
}
public void resumePainting() {
this.wait_ = false;
synchronized(this) {
this.notify();
}
}
public void stopPainting() {
this.run_ = false;
synchronized(this) {
this.notify();
}
}
Как видно, мы просто бесконечно выполняем метод doDraw(), периодически ставя его на паузу, когда рабочий стол не виден.
Сам метод выглядит примерно так:
private void doDraw(Canvas canvas) {
Date currentTime = new Date();
//Create text for clock
String clockText;
if (DateFormat.is24HourFormat(context_)) {
if (currentTime.getSeconds()%2 == 0)
clockText = DateFormat.format("kk:mm", currentTime).toString();
else
clockText = DateFormat.format("kk mm", currentTime).toString();
} else {
if (currentTime.getSeconds()%2 == 0)
clockText = DateFormat.format("h:mmaa", currentTime).toString();
else
clockText = DateFormat.format("h mmaa", currentTime).toString();
}
int textSize = (int)textPainter_.getTextSize();
//Erase Background
canvas.drawRect(0, 0, width_, height_, backgroundPainter_);
int stepX = (width_ - textSize) / (CLOCKS_COUNT + 1);
int stepY = (height_ - textSize*CLOCKS_COUNT) / (CLOCKS_COUNT + 1);
for (int i=1; i <= CLOCKS_COUNT; ++i)
canvas.drawText(clockText,
stepX/2 + stepX * (i-1) - absoluteOffset_,
height_/20 + stepY * i + textSize*(i-1),
textPainter_);
}
В сервисе мы переопределим еще один метод, чтобы обои реагировали на смену рабочих столов:
@Override
public void onOffsetsChanged(float xOffset, float yOffset, float xStep, float yStep, int xPixels, int yPixels) {
painting.setOffset(xOffset);
Log.d("Clock", String.format("off:%f-%f step:%f - %f px: %d - %d", xOffset, yOffset, xStep, yStep, xPixels, yPixels));
}
Как сделать отрисовку в отдельном потоке, отлично расписано в этом примере.
И, наконец, настройки. Делаются они несложно, так же как и настройки любых других андроид приложений. В xml файле нужно прописать настраиваемые параметры и диапазоны значений, которые они могут принимать. В нашем случае позволим пользователю выбирать цвет текста и фона.
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:title="@string/settings_title">
<ListPreference
android:key="preference_font_color"
android:title="@string/preference_font_color_title"
android:summary="@string/preference_font_color_summary"
android:entries="@array/color_names"
android:entryValues="@array/color_values" />
<ListPreference
android:key="preference_background_color"
android:title="@string/preference_background_color_title"
android:summary="@string/preference_background_color_summary"
android:entries="@array/color_names"
android:entryValues="@array/color_values" />
</PreferenceScreen>
Вот так это выглядит в итоге:

Скачать можно тут.
Чтобы написать свои обои, начать можно отсюда