Android Live Wallpaper Tutorial


Hi, at last I was able to create my own live wallpaper " MATRIX RAIN ". 

Download : click here

The matrix rain effect which I am using is the same which I created in canvas.
ref :- http://www.androidlearner.com/2016/09/create-custom-view-in-android-matrix.html
I this post I will describe how I put together the live wallpaper. 






To create live wallpaper you need to set the following things,
1. A class which extends WallpaperService and further implements a nested class which  extends Engine class.This Class is responsible for calling the draw code.
2. A activity which displays list of settings for the wallpaper.(optional). Here I discovered android Preference API which easily enable to create setting for applications
3. XML definition for wallpaper in xml folder.
4. AndroidManifest.xml entry for the live-wallpaper and the preference activity.

Getting Started :-
1. For creating the Live wallpaper you must class which extends class WallpaperService find more info about class at https://developer.android.com/reference/android/service/wallpaper/WallpaperService.html
This class must override method onCreateEngine() method which return Engine class object 
Further this class must have a nested class which extends Engine class.
(I don’t know why)
 
To create the falling animation I need to draw the surface continuously.Earlier in the when 
using canvas view invalidate() method was called to update the view.However in case of 
Engine class  there is no such method.

To draw the effect first you need to get the canvas from the getSurfaceHolder();
After that a thread is runned at specific interval to update the draw surface.


Below is the skeleton class from the live wallpaper.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package matrixlw.app.skd.wa;

import android.content.Context;
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.util.Log;
import android.view.SurfaceHolder;

import com.enrico.colorpicker.colorDialog;

import java.util.Random;

/**
 * Created by sapan on 1/10/2017.
 */

public class matrixWall extends WallpaperService {

    private boolean mVisible;  // visible flag
    Canvas canvas;      // canvas reference
    int Drawspeed=10;   // thread call delay time
    Context mcontext;   //reference to the current context

   

    @Override
    public Engine onCreateEngine() {
        //set the Preference default value
        mcontext = this;  //set the current context
        
        //return the Engine Class
        return new LiveWall(); // this calls contain the wallpaper code
    }


    /*
    * this class extends the engine for the live wallpaper
    * THis class implements all the draw calls required to draw the wallpaper
    * This call is to neseted inside the wallpaper service class to function properly
    * don't know why though :(
     */
    public class LiveWall extends Engine
    {

        final Handler mHandler = new Handler(); // this is to handle the thread

        //the tread responsibe for drawing this thread get calls every time
        // drawspeed vars set the execution speed
        private final Runnable mDrawFrame = new Runnable() {
            public void run() {

              
                // This method get called each time to drwaw thw frame
                // Engine class does not provide any invlidate methods
                // as used in canvas
                // set your draw call here
                drawFrame();
            }
        };


        //Called when the surface is created
        @Override
        public void onSurfaceCreated(SurfaceHolder holder) {
            super.onSurfaceCreated(holder);

            //call the draw method
            // this is where you must call your draw code
            drawFrame();

        }

        // remove thread
        @Override
        public void onDestroy() {
            super.onDestroy();
            mHandler.removeCallbacks(mDrawFrame);
        }


        //called when varaible changed
        @Override
        public void onVisibilityChanged(boolean visible) {
            mVisible = visible;
            if (visible) {
              
    //call the drawFunction
                drawFrame();   
    
            } else {
                
                //this is necessay to remove the call back
                mHandler.removeCallbacks(mDrawFrame);
            }
        }

        //called when surface destroyed
        @Override
        public void onSurfaceDestroyed(SurfaceHolder holder) {
            super.onSurfaceDestroyed(holder);
            mVisible = false;
            //this is necessay to remove the call back
            mHandler.removeCallbacks(mDrawFrame);
        }



  // my function which contain the code to draw
        //this function contain the the main draw call
        /// this function need to call every time the code is executed
        // the thread call this functioin with some delay "drawspeed"
        public void drawFrame()
        {
            //getting the surface holder
            final SurfaceHolder holder = getSurfaceHolder();

            canvas = null;  // canvas
            try {
                canvas = holder.lockCanvas();  //get the canvas
                if (canvas != null) {
                    // draw something
     // my draw code
                  
                }
            } finally {
                if (canvas != null)
                    holder.unlockCanvasAndPost(canvas);
            }

            // Reschedule the next redraw
            // this is the replacement for the invilidate funtion
            // every time call the drawFrame to draw the matrix
            mHandler.removeCallbacks(mDrawFrame);
            if (mVisible) {
                // set the execution delay
                mHandler.postDelayed(mDrawFrame, Drawspeed);
            }
        }

        @Override
        public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            super.onSurfaceChanged(holder, format, width, height);
           // update when surface changed

        }
    }


}
2. Creating xml definition.
As the for the wallpaper service a xml file must be created in xml folder like mywallpaper.xml

1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8"?>
<wallpaper
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:thumbnail="@mipmap/ic_launcher"

    android:settingsActivity="matrixlw.app.skd.wa.SettingsActivity"/>
here android:settingsActivity="matrixlw.app.skd.wa.SettingsActivity"/>
points to the settings activity which get called when setting button is clicked
at the live wallpaper preview screen
 
3. Setting up the android AndroidManifest.xml to get the live wallpaper running this file must be set properly. 
a.  Set the features 
1
 <uses-feature android:name="android.software.live_wallpaper" />
b.  Add the service for the wallpaper and the resource xml file.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<service
            android:name=".matrixWall"
            android:enabled="true"
            android:label="MATRIX RAIN"
            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/mywallpaper"></meta-data>
        </service>
c.  Add the settings activity 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
   <activity
            android:name=".SettingsActivity"
            android:label="@string/title_activity_settings"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />

            </intent-filter>

        </activity>
Here  android:exported="true" must be set else this activity will not open when clicking setting button
3. Creating the Setting activity
The Settings activity Contains the UI for customize the property of the live wallpaper.
The preference activity is created using android preference API this provides a easy way to created the preference API
For more info ref : https://developer.android.com/guide/topics/ui/settings.html
Color Picker is created using library by enricocid
Ref :- https://github.com/enricocid/Color-picker-library
I am skipping about the setting activity. The files and layout for the setting activity is SettingsActivity.java and xml/preferences.xml4. Putting together the Live wallpaper :
The matrix rain effect is going to be my live wallpaper which I have explained in my previous article.
ref-
below is the draw code from that project


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// ======== MATRIX LIVE WALLPAPER VARS
    int background_color= Color.parseColor("#FF000000");
    int text_color=Color.parseColor("#FF8BFF4A");

    int width = 1000000; //default initial width
    int height = 100; //default initial height
    int fontSize = 15; //font size of the text which will fall
    int columnSize = width/fontSize; //column size ; no of digit required to fill the screen
    int parentWidth;
    String text = "MATRIXRAIN";  // Text which need to be drawn
    char[] textChar = text.toCharArray(); // split the character of the text
    int textLength = textChar.length;   //length of the length text
    Random rand = new Random(); //random generater

    int[]  textPosition; // contain the position which will help to draw the text
    //======================

  //old matrix effect code
    void drawText()
    {
        //Set up the paint
        Paint paint = new Paint();
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(text_color);
        paint.setTextSize(15);


        //loop and paint
        for(int i =0 ;i<textPosition.length;i++)
        {
            // draw the text at the random position
            canvas.drawText(""+textChar[rand.nextInt(textLength)+0],i*fontSize,textPosition[i]*fontSize,paint);
            // check if text has reached bottom or not
            if(textPosition[i]*fontSize > height && Math.random() > 0.975)
                textPosition[i] = 0;   // change text position to zero when 0 when text is at the bottom

            textPosition[i]++; //increment the position array
        }
    }

    //old martix effect code
    public void canvasDraw()
    {
        Log.d("canvas ","drawing");
        //set the paint for the canvas
        Paint paint = new Paint();
        paint.setColor(background_color);

        paint.setAlpha(5);
        paint.setStyle(Paint.Style.FILL);
        canvas.drawRect(0, 0, width, height, paint);//draw rect to clear the canvas

        drawText(); // draw the canvas

    }

Now the above code is need to be inserted into the skeleton wallpaper Service code.In the skeleton class DrawFrame() method is used to call the draw statements.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
package matrixlw.app.skd.wa;

import android.content.Context;
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.util.Log;
import android.view.SurfaceHolder;

import com.enrico.colorpicker.colorDialog;

import java.util.Random;

/**
 * Created by sapan on 1/10/2017.
 */

public class matrixWall extends WallpaperService {

    private boolean mVisible;  // visible flag
    Canvas canvas;      // canvas reference
    int Drawspeed=10;   // thread call delay time
    Context mcontext;   //reference to the current context

    // ======== MATRIX LIVE WALLPAPER VARS
    int background_color= Color.parseColor("#FF000000");
    int text_color=Color.parseColor("#FF8BFF4A");

    int width = 1000000; //default initial width
    int height = 100; //default initial height
    int fontSize = 15; //font size of the text which will fall
    int columnSize = width/fontSize; //column size ; no of digit required to fill the screen
    int parentWidth;
    String text = "MATRIXRAIN";  // Text which need to be drawn
    char[] textChar = text.toCharArray(); // split the character of the text
    int textLength = textChar.length;   //length of the length text
    Random rand = new Random(); //random generater

    int[]  textPosition; // contain the position which will help to draw the text
    //======================

    @Override
    public Engine onCreateEngine() {
        //set the Preference default value
        mcontext = this;  //set the current context

        //Initalise and read the preference
        PreferenceManager.setDefaultValues(this, R.xml.preferences, false);

        SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
        text = sharedPref.getString("matrix_scroll_text", "MATRIX");
        Drawspeed = Integer.parseInt(sharedPref.getString("matrix_falling_speed","10"));
        fontSize = Integer.parseInt(sharedPref.getString("matrix_font_size","15"));
        background_color = colorDialog.getPickerColor(getBaseContext(), 1);
        text_color =colorDialog.getPickerColor(getBaseContext(), 2);

        //Some loggers Commnet or remove if you want
        Log.d("back_color",""+background_color);
        Log.d("text_color",""+text_color);

        textChar = text.toCharArray(); // split the character of the text
        textLength = textChar.length;
        columnSize = width/fontSize;

        //return the Engine Class
        return new LiveWall(); // this calls contain the wallpaper code
    }


    /*
    * this class extends the engine for the live wallpaper
    * THis class implements all the draw calls required to draw the wallpaper
    * This call is to neseted inside the wallpaper service class to function properly
    * don't know why though :(
     */
    public class LiveWall extends Engine
    {

        final Handler mHandler = new Handler(); // this is to handle the thread

        //the tread responsibe for drawing this thread get calls every time
        // drawspeed vars set the execution speed
        private final Runnable mDrawFrame = new Runnable() {
            public void run() {

                //Matrix code to the color when changed
                // callback can also be used but I havent
                background_color = colorDialog.getPickerColor(getBaseContext(), 1);
                text_color =colorDialog.getPickerColor(getBaseContext(), 2);
                // ^^^^^^^^

                // This method get called each time to drwaw thw frame
                // Engine class does not provide any invlidate methods
                // as used in canvas
                // set your draw call here
                drawFrame();
            }
        };


        //Called when the surface is created
        @Override
        public void onSurfaceCreated(SurfaceHolder holder) {
            super.onSurfaceCreated(holder);

            //update  the matrix variables
            width = getDesiredMinimumWidth();
            height = getDesiredMinimumHeight();
            columnSize = width/fontSize;
            //initalise the textposiotn to zero
            textPosition = new int[columnSize+1]; //add one more drop
            for(int x = 0; x < columnSize; x++) {
                textPosition[x] = 1;
            }
            //^^^^^^^^^^^^^^^

            //call the draw method
            // this is where you must call your draw code
            drawFrame();

        }

        // remove thread
        @Override
        public void onDestroy() {
            super.onDestroy();
            mHandler.removeCallbacks(mDrawFrame);
        }


        //called when varaible changed
        @Override
        public void onVisibilityChanged(boolean visible) {
            mVisible = visible;
            if (visible) {
                SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(mcontext);
                text = sharedPref.getString("matrix_scroll_text", "MATRIX");
                Drawspeed = Integer.parseInt(sharedPref.getString("matrix_falling_speed","10"));
                fontSize = Integer.parseInt(sharedPref.getString("matrix_font_size","15"));
                background_color = colorDialog.getPickerColor(getBaseContext(), 1);
                text_color =colorDialog.getPickerColor(getBaseContext(), 2);



                textChar = text.toCharArray(); // split the character of the text
                textLength = textChar.length;
                columnSize = width/fontSize;
                drawFrame();
            } else {
                SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(mcontext);
                text = sharedPref.getString("matrix_scroll_text", "MATRIX");
                Drawspeed = Integer.parseInt(sharedPref.getString("matrix_falling_speed","10"));
                fontSize = Integer.parseInt(sharedPref.getString("matrix_font_size","15"));
                background_color = colorDialog.getPickerColor(getBaseContext(), 1);
                text_color =colorDialog.getPickerColor(getBaseContext(), 2);



                textChar = text.toCharArray(); // split the character of the text
                textLength = textChar.length;
                columnSize = width/fontSize;

                //this is necessay to remove the call back
                mHandler.removeCallbacks(mDrawFrame);
            }
        }

        //called when surface destroyed
        @Override
        public void onSurfaceDestroyed(SurfaceHolder holder) {
            super.onSurfaceDestroyed(holder);
            mVisible = false;
            //this is necessay to remove the call back
            mHandler.removeCallbacks(mDrawFrame);
        }



        //this function contain the the main draw call
        /// this function need to call every time the code is executed
        // the thread call this functioin with some delay "drawspeed"
        public void drawFrame()
        {
            //getting the surface holder
            final SurfaceHolder holder = getSurfaceHolder();

            canvas = null;  // canvas
            try {
                canvas = holder.lockCanvas();  //get the canvas
                if (canvas != null) {
                    // draw something

                    // canvas matrix draw code
                    canvasDraw();
                    //^^^^
                }
            } finally {
                if (canvas != null)
                    holder.unlockCanvasAndPost(canvas);
            }

            // Reschedule the next redraw
            // this is the replacement for the invilidate funtion
            // every time call the drawFrame to draw the matrix
            mHandler.removeCallbacks(mDrawFrame);
            if (mVisible) {
                // set the execution delay
                mHandler.postDelayed(mDrawFrame, Drawspeed);
            }
        }






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

            // some matrix variable
            // though not needed
            Paint paint = new Paint();
            paint.setColor(background_color);
            paint.setAlpha(255); //set the alpha
            paint.setStyle(Paint.Style.FILL);
            canvas.drawRect(0, 0, width, height, paint);

        }
    }


    //old matrix effect code
    void drawText()
    {
        //Set up the paint
        Paint paint = new Paint();
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(text_color);
        paint.setTextSize(15);


        //loop and paint
        for(int i =0 ;i<textPosition.length;i++)
        {
            // draw the text at the random position
            canvas.drawText(""+textChar[rand.nextInt(textLength)+0],i*fontSize,textPosition[i]*fontSize,paint);
            // check if text has reached bottom or not
            if(textPosition[i]*fontSize > height && Math.random() > 0.975)
                textPosition[i] = 0;   // change text position to zero when 0 when text is at the bottom

            textPosition[i]++; //increment the position array
        }
    }

    //old martix effect code
    public void canvasDraw()
    {
        Log.d("canvas ","drawing");
        //set the paint for the canvas
        Paint paint = new Paint();
        paint.setColor(background_color);

        paint.setAlpha(5);
        paint.setStyle(Paint.Style.FILL);
        canvas.drawRect(0, 0, width, height, paint);//draw rect to clear the canvas

        drawText(); // draw the canvas

    }


}


Here the below methods are used to set up the surface

1
2
3
4
public void onSurfaceCreated(SurfaceHolder holder)
 public void onVisibilityChanged(boolean visible)
 public void onSurfaceDestroyed(SurfaceHolder holder)
 public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height)

Comments

  1. Nice tutorial, thanks for it. Very helpful

    ReplyDelete

Post a Comment