Support free tutorials









vogella training Training Books



Android Live Wallpaper - Tutorial

Lars Vogel

Version 2.1

15.10.2012

Revision History
Revision 0.1 08.01.2011 Lars
Vogel
Created
Revision 0.2 - 2.1 09.01.2011 - 15.10.2012 Lars
Vogel
updated and bugfixes

Android Live Wallpaper

This tutorial describes the creation of live wallpapers for Android. It is based on Eclipse 4.2, Java 1.6 and Android 4.1 (Ice Cream Sandwich).


Table of Contents

1. Pre-requisitions
2. Overview
2.1. Live Wallpapers
2.2. How to create a live wallpaper
2.3. Intent to set the wallpaper
3. Android Wallpaper Example
4. Support this website
4.1. Thank you
4.2. Questions and Discussion
5. Links and Literature
5.1. Source Code
5.2. Android Resources
5.3. vogella Resources

1. Pre-requisitions

The following tutorial assumes that you have already basic knowledge in Android development. Please check the Android development tutorial to learn the basics.

2. Overview

2.1. Live Wallpapers

Live Wallpapers are animated, interactive backgrounds for the Android home screen. A live wallpaper is similar to other Android applications and can use most of the same functionality.

2.2. How to create a live wallpaper

To create a live wallpaper, you need to create an XML file which describes your wallpaper. This file should contain a description of the application and can contain a preview and a link to a preference activity Activity which allow to customize the live wallpaper.

You also create a service which must extend the WallpaperService class. This class is the base class for all live wallpapers in the system. You must implement the onCreateEngine() method and return an object of type android.service.wallpaper.WallpaperService.Engine. This objects handles the lifecycle events, animations and drawings of the wallpaper. The Engine class defines the life cycle methods, as for example onCreate(), onSurfaceCreated(), onVisibilityChanged(), onOffsetsChanged(), onTouchEvent() and onCommand().

The service requires the permission android.permission.BIND_WALLPAPER and must be registered via an intent-filter for the android.service.wallpaper.WallpaperService action.

You should also enter in the AndroidManifest.xml file of the application that your application uses the android.software.live_wallpaper feature. This will prevent that your wallpaper can be installed on devices which do not support live wallpapers.

2.3. Intent to set the wallpaper

You can use an Intent to set the Wallpaper.

// Button to set the Wallpaper
public void onClick(View view) {
  Intent intent = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
  intent.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT,
    new ComponentName(this, MyWallpaperService.class));
  startActivity(intent);
} 

3. Android Wallpaper Example

Create a new project called de.vogella.android.wallpaper. Do not create an activity.

Create the /res/xml folder and create the mywallpaper.xml file.

<?xml version="1.0" encoding="UTF-8"?>
<wallpaper 
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:thumbnail="@drawable/icon"
  android:description="@string/wallpaper_description"  
  android:settingsActivity="de.vogella.android.wallpaper.MyPreferencesActivity"/> 

This file contains a description of your wallpaper and a preview graphic. You can also enter a link to an activity which allow to configure the wallpaper. This resource file will be linked to from the AndroidManifest.xml. You could also include the "android:thumbnail attribute" which would point to a drawable which gives a smaller image of the running wallpaper.

Change your AndroidManifest.xml to the following to define your MyWallpaperService service. Also define the uses-feature.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="de.vogella.android.wallpaper"
    android:versionCode="1"
    android:versionName="1.0" >

    <application
        android:icon="@drawable/icon"
        android:label="@string/app_name" >
        <service
            android:name="MyWallpaperService"
            android:enabled="true"
            android:label="Wallpaper Example "
            android:permission="android.permission.BIND_WALLPAPER" >
            <intent-filter>
                <action android:name="android.service.wallpaper.WallpaperService" >
                </action>
            </intent-filter>

            <meta-data
                android:name="android.service.wallpaper"
                android:resource="@xml/mywallpaper" >
            </meta-data>
        </service>

        <activity
            android:name=".MyPreferencesActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:theme="@android:style/Theme.Light.WallpaperSettings" >
        </activity>
        <activity
            android:name=".SetWallpaperActivity"
            android:label="@string/app_name"
            android:theme="@android:style/Theme.Light.WallpaperSettings" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

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

    <uses-feature
        android:name="android.software.live_wallpaper"
        android:required="true" >
    </uses-feature>

</manifest> 

We create the MyPoint class to save the elements we have drawn.

package de.vogella.android.wallpaper;

public class MyPoint {
  String text;
  private int x;
  private int y;

  public MyPoint(String text, int x, int y) {
    this.text = text;
    this.x = x;
    this.y = y;
  }
} 

Create a new activity. Create the prefs.xml preference file in the res/xml folder.

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
  <CheckBoxPreference android:key="touch"
    android:title="Enable Touch"></CheckBoxPreference>
  <EditTextPreference android:key="numberOfCircles"
    android:title="Number of Circles"></EditTextPreference>
</PreferenceScreen> 

Create a new activity called MyPreferencesActivity and the following class.

package de.vogella.android.wallpaper;

import android.os.Bundle;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.PreferenceActivity;
import android.widget.Toast;

public class MyPreferencesActivity extends PreferenceActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    addPreferencesFromResource(R.xml.prefs);

    // add a validator to the "numberofCircles" preference so that it only
    // accepts numbers
    Preference circlePreference = getPreferenceScreen().findPreference("numberOfCircles");

    // add the validator
    circlePreference.setOnPreferenceChangeListener(numberCheckListener);
  }

  
/** * Checks that a preference is a valid numerical value */
Preference.OnPreferenceChangeListener numberCheckListener = new OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { // check that the string is an integer if (newValue != null && newValue.toString().length() > 0 && newValue.toString().matches("\\d*")) { return true; } // If now create a message to the user Toast.makeText(MyPreferencesActivity.this, "Invalid Input", Toast.LENGTH_SHORT).show(); return false; } }; }

Create the following coding for the Wallpaper service.

package de.vogella.android.wallpaper;

import java.util.ArrayList;
import java.util.List;

import android.content.SharedPreferences;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.service.wallpaper.WallpaperService;
import android.view.MotionEvent;
import android.view.SurfaceHolder;

public class MyWallpaperService extends WallpaperService {

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

  private class MyWallpaperEngine extends Engine {
    private final Handler handler = new Handler();
    private final Runnable drawRunner = new Runnable() {
      @Override
      public void run() {
        draw();
      }

    };
    private List<MyPoint> circles;
    private Paint paint = new Paint();
    private int width;
    int height;
    private boolean visible = true;
    private int maxNumber;
    private boolean touchEnabled;

    public MyWallpaperEngine() {
      SharedPreferences prefs = PreferenceManager
          .getDefaultSharedPreferences(MyWallpaperService.this);
      maxNumber = Integer
          .valueOf(prefs.getString("numberOfCircles", "4"));
      touchEnabled = prefs.getBoolean("touch", false);
      circles = new ArrayList<MyPoint>();
      paint.setAntiAlias(true);
      paint.setColor(Color.WHITE);
      paint.setStyle(Paint.Style.STROKE);
      paint.setStrokeJoin(Paint.Join.ROUND);
      paint.setStrokeWidth(10f);
      handler.post(drawRunner);
    }

    @Override
    public void onVisibilityChanged(boolean visible) {
      this.visible = visible;
      if (visible) {
        handler.post(drawRunner);
      } else {
        handler.removeCallbacks(drawRunner);
      }
    }

    

} 

@Override
    public void onSurfaceDestroyed(SurfaceHolder holder) {
      super.onSurfaceDestroyed(holder);
      this.visible = false;
      handler.removeCallbacks(drawRunner);
    }

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

    @Override
    public void onTouchEvent(MotionEvent event) {
      if (touchEnabled) {

        float x = event.getX();
        float y = event.getY();
        SurfaceHolder holder = getSurfaceHolder();
        Canvas canvas = null;
        try {
          canvas = holder.lockCanvas();
          if (canvas != null) {
            canvas.drawColor(Color.BLACK);
            circles.clear();
            circles.add(new MyPoint(String.valueOf(circles.size() + 1), x, y));
            drawCircles(canvas, circles);

          }
        } finally {
          if (canvas != null)
            holder.unlockCanvasAndPost(canvas);
        }
        super.onTouchEvent(event);
      }
    }

    private void draw() {
      SurfaceHolder holder = getSurfaceHolder();
      Canvas canvas = null;
      try {
        canvas = holder.lockCanvas();
        if (canvas != null) {
          if (circles.size() >= maxNumber) {
            circles.clear();
          }
          int x = (int) (width * Math.random());
          int y = (int) (height * Math.random());
          circles.add(new MyPoint(String.valueOf(circles.size() + 1),
              x, y));
          drawCircles(canvas, circles);
        }
      } finally {
        if (canvas != null)
          holder.unlockCanvasAndPost(canvas);
      }
      handler.removeCallbacks(drawRunner);
      if (visible) {
        handler.postDelayed(drawRunner, 5000);
      }
    }

    // Surface view requires that all elements are drawn completely
    private void drawCircles(Canvas canvas, List<MyPoint> circles) {
      canvas.drawColor(Color.BLACK);
      for (MyPoint point : circles) {
        canvas.drawCircle(point.x, point.y, 20.0f, paint);
      }
    }
  } 

Create the following Activity which should use a layout with one Button included. This Button should use the onClick property to point to the onClick method.

package de.vogella.android.wallpaper;

import android.app.Activity;
import android.app.WallpaperManager;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;

public class SetWallpaperActivity extends Activity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
  }

  public void onClick(View view) {
    Intent intent = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
    intent.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT,
        new ComponentName(this, MyWallpaperService.class));
    startActivity(intent);
  }
} 

If you start your application your application should allow to set the wallpaper. Your background should look similar to the following screenshot. If you have Touch enabled via the preferences you can click on the screen to remove the existing circles, also via the settings you can define the number of circles which should be displayed.

Live Wallpaper displayed

4. Support this website

This tutorial is Open Content under the CC BY-NC-SA 3.0 DE license. Source code in this tutorial is distributed under the Eclipse Public License. See the vogella License page for details on the terms of reuse.

Writing and updating these tutorials is a lot of work. If this free community service was helpful, you can support the cause by giving a tip as well as reporting typos and factual errors.

4.1. Thank you

Please consider a contribution if this article helped you. It will help to maintain our content and our Open Source activities.

4.2. Questions and Discussion

If you find errors in this tutorial, please notify me (see the top of the page). Please note that due to the high volume of feedback I receive, I cannot answer questions to your implementation. Ensure you have read the vogella FAQ as I don't respond to questions already answered there.

5. Links and Literature

5.1. Source Code

Source Code of Examples

5.3. vogella Resources

vogella Training Android and Eclipse Training from the vogella team

Android Tutorial Introduction to Android Programming

GWT Tutorial Program in Java, compile to JavaScript and HTML

Eclipse RCP Tutorial Create native applications in Java

JUnit Tutorial Test your application

Git Tutorial Put all your files in a distributed version control system