Tuesday, October 27, 2015

android - How to make a Live Wallpaper

GradientLiveWallPaperService.java

package com.cfsuman.me.gradientlivewallpaper;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;
import android.os.Handler;
import android.service.wallpaper.WallpaperService;
import android.view.SurfaceHolder;
import java.util.Random;

/*
    WallpaperService
        A wallpaper service is responsible for showing a live wallpaper behind applications that
        would like to sit on top of it. This service object itself does very little -- its only
        purpose is to generate instances of WallpaperService.Engine as needed. Implementing a
        wallpaper thus involves subclassing from this, subclassing an Engine implementation, and
        implementing onCreateEngine() to return a new instance of your engine.
*/
public class GradientLiveWallPaperService extends WallpaperService {
    private Context mContext;
    /*
        Handler
            A Handler allows you to send and process Message and Runnable objects associated with a
            thread's MessageQueue. Each Handler instance is associated with a single thread and that
            thread's message queue. When you create a new Handler, it is bound to the thread / message
            queue of the thread that is creating it -- from that point on, it will deliver messages
            and runnables to that message queue and execute them as they come out of the message queue.
    */
    private Handler mHandler = new Handler();
    private Random mRandom = new Random();
    private Point mSize = new Point();

    private int mInterval = 50;

    private int mIndicator;
    private boolean mRegenerateTopLayerPaint = true;
    private boolean mRegenerateBottomLayerPaint = true;
    private int mPaintTopLayerAlpha = 0;
    private Paint mPaintTopLayer = new Paint();
    private Paint mPaintBottomLayer = new Paint();

    private GradientManager mGradientManager;


    /*
        WallpaperService.Engine
            The actual implementation of a wallpaper. A wallpaper service may have multiple instances
            running (for example as a real wallpaper and as a preview), each of which is represented
            by its own Engine instance. You must implement onCreateEngine() to return your concrete
            Engine implementation.

        public abstract WallpaperService.Engine onCreateEngine ()
            Must be implemented to return a new instance of the wallpaper's engine. Note that
            multiple instances may be active at the same time, such as when the wallpaper is
            currently set as the active wallpaper and the user is in the wallpaper picker viewing
            a preview of it as well.
    */
    public Engine onCreateEngine(){
        return new GradientLiveWallpaperEngine();
    }

    // New private WallpaperService.Engine class started
    private class GradientLiveWallpaperEngine extends Engine{
        // To determine whether live wallpaper is visible state
        boolean mVisible = true;

        /*
            Runnable
                Represents a command that can be executed. Often used to run code in a different Thread.
        */
        Runnable mRunnable = new Runnable() {
            /*
                public abstract void run ()
                    Starts executing the active part of the class' code. This method is called when
                    a thread is started that has been created with a class which implements Runnable.
            */
            @Override
            public void run() {
                drawGradient();
            }
        };

        public GradientLiveWallpaperEngine(){
        }

        /*
            public void onDestroy ()
                Called right before the engine is going away. After this the surface will be
                destroyed and this Engine object is no longer valid.
        */
        @Override
        public void onDestroy() {
            super.onDestroy();
            mHandler.removeCallbacks(mRunnable);


        }

        /*
            public void onVisibilityChanged (boolean visible)
                Called to inform you of the wallpaper becoming visible or hidden. It is very
                important that a wallpaper only use CPU while it is visible..
        */
        @Override
        public void onVisibilityChanged(boolean visible){
            mVisible = visible;
            if (visible) {
                drawGradient();
            } else {
                mHandler.removeCallbacks(mRunnable);
            }
        }

        /*
            public void onSurfaceDestroyed (SurfaceHolder holder)
                Convenience for SurfaceHolder.Callback.surfaceDestroyed().

            public abstract void surfaceDestroyed (SurfaceHolder holder)
                This is called immediately before a surface is being destroyed. After returning from
                this call, you should no longer try to access this surface. If you have a rendering
                thread that directly accesses the surface, you must ensure that thread is no longer
                touching the Surface before returning from this function.
        */
        @Override
        public void onSurfaceDestroyed(SurfaceHolder holder){
            super.onSurfaceDestroyed(holder);
            mVisible = false;
            mHandler.removeCallbacks(mRunnable);
        }

        // Custom method to draw a color pallet
        void drawGradient(){
            /*
                SurfaceHolder
                    Abstract interface to someone holding a display surface. Allows you to control
                    the surface size and format, edit the pixels in the surface, and monitor changes
                    to the surface. This interface is typically available through the SurfaceView class.
            */
            // Get the SurfaceHolder object
            SurfaceHolder holder = getSurfaceHolder();

            /*
                Canvas
                    The Canvas class holds the "draw" calls. To draw something, you need 4 basic
                    components: A Bitmap to hold the pixels, a Canvas to host the draw calls
                    (writing into the bitmap), a drawing primitive (e.g. Rect, Path, text, Bitmap),
                    and a paint (to describe the colors and styles for the drawing).
            */
            /*
                public abstract Canvas lockCanvas ()
                    Start editing the pixels in the surface. The returned Canvas can be used to draw
                    into the surface's bitmap. A null is returned if the surface has not been
                    created or otherwise cannot be edited. You will usually need to implement
                    Callback.surfaceCreated to find out when the Surface is available for use.
            */
            // Initialize the Canvas to draw something
            Canvas canvas = holder.lockCanvas();

            // Get the application context
            mContext = getApplicationContext();

            // Set the point x y values
            mSize.x = canvas.getWidth();
            mSize.y = canvas.getHeight();

            // Initialize a new instance of GradientManager class
            mGradientManager = new GradientManager(mContext,mSize);

            try{
                /*
                    We will draw canvas with two layers, top and bottom
                    Top layer continuously change its transparency
                    Top layer transparency cycle is 0 > 255 > 0

                    When top layer transparency reach 255
                        then we will convert it again zero
                        Replace the bottom layer shader with top layer shader
                        Regenerate the top layer shader
                */

                // If required regenerate the top layer shader
                if(mRegenerateTopLayerPaint){
                    /*
                        setDither(boolean dither)
                            Helper for setFlags(), setting or clearing the DITHER_FLAG bit Dithering
                            affects how colors that are higher precision than the device are down-sampled.
                    */
                    mPaintTopLayer.setDither(true);
                    mIndicator = mRandom.nextInt(3);
                    // Assign the random gradient type gradient
                    if(mIndicator == 0){
                        mPaintTopLayer.setShader(mGradientManager.getRandomLinearGradient());
                    }else if(mIndicator == 1){
                        mPaintTopLayer.setShader(mGradientManager.getRandomRadialGradient());
                    }else {
                        mPaintTopLayer.setShader(mGradientManager.getRandomSweepGradient());
                    }
                }

                // If required regenerate the bottom layer shader
                if(mRegenerateBottomLayerPaint){
                    mPaintBottomLayer.setDither(true);
                    mIndicator = mRandom.nextInt(3);
                    // Assign the random gradient type gradient
                    if(mIndicator == 0){
                        /*
                            Paint
                                The Paint class holds the style and color information about how to
                                draw geometries, text and bitmaps.

                            setShader(Shader shader)
                                Set or clear the shader object.

                            Shader
                                Shader is the based class for objects that return horizontal spans
                                of colors during drawing. A subclass of Shader is installed in a
                                Paint calling paint.setShader(shader). After that any object
                                (other than a bitmap) that is drawn with that paint will get its
                                color(s) from the shader.
                        */
                        mPaintBottomLayer.setShader(mGradientManager.getRandomLinearGradient());
                    }else if(mIndicator == 1){
                        mPaintBottomLayer.setShader(mGradientManager.getRandomRadialGradient());
                    }else {
                        mPaintBottomLayer.setShader(mGradientManager.getRandomSweepGradient());
                    }
                }


                /*
                    setAlpha(int a)
                        Helper to setColor(), that only assigns the color's alpha value, leaving
                        its r,g,b values unchanged.
                */
                // Set the top layer alpha
                mPaintTopLayer.setAlpha(mPaintTopLayerAlpha);

                if(mPaintTopLayerAlpha <256){
                    mPaintTopLayerAlpha ++;
                    mRegenerateTopLayerPaint = false;
                    mRegenerateBottomLayerPaint = false;
                }

                /*
                    drawRect(float left, float top, float right, float bottom, Paint paint)
                        Draw the specified Rect using the specified paint.
                */
                // Finally, draw the canvas with two layer
                canvas.drawRect(0,0,mSize.x,mSize.y,mPaintBottomLayer);
                canvas.drawRect(0,0,mSize.x,mSize.y,mPaintTopLayer);

                if (mPaintTopLayerAlpha == 256)
                {
                    /*
                        Rule - To generate continuous animation

                            top layer alpha = 0 (full transparent)
                            top layer = null (regenerate)
                            bottom layer shader = top layer shader
                    */
                    mPaintTopLayerAlpha = 0;
                    mPaintBottomLayer.setShader(mPaintTopLayer.getShader());
                    mRegenerateTopLayerPaint = true;
                }

            }finally{
                if (canvas != null){
                    /*
                        public abstract void unlockCanvasAndPost (Canvas canvas)
                            Finish editing pixels in the surface. After this call, the surface's
                            current pixels will be shown on the screen, but its content is lost, in
                            particular there is no guarantee that the content of the Surface will
                            remain unchanged when lockCanvas() is called again.

                            Parameters
                                canvas : The Canvas previously returned by lockCanvas().
                    */
                    holder.unlockCanvasAndPost(canvas);
                }
            }

            /*
                removeCallbacks(Runnable r)
                    Remove any pending posts of Runnable r that are in the message queue.
            */
            // Now remove the call backs from handler
            mHandler.removeCallbacks(mRunnable);

            // If visible continue the animation
            if(mVisible){
                /*
                    postDelayed(Runnable r, long delayMillis)
                        Causes the Runnable r to be added to the message queue, to be run after the
                        specified amount of time elapses.
                */
                mHandler.postDelayed(mRunnable, mInterval);
            }
        }
    } // WallpaperService.Engine class end
}// WallpaperService class end
GradientManager.java

package com.cfsuman.me.gradientlivewallpaper;

import android.content.Context;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Point;
import android.graphics.RadialGradient;
import android.graphics.Shader;
import android.graphics.SweepGradient;
import java.util.Random;


public class GradientManager {
    private Random mRandom = new Random();
    private Context mContext;
    private Point mSize;

    public GradientManager(Context context, Point size){
        this.mContext = context;
        this.mSize = size;
    }

    // Custom method to generate a random LinearGradient
    protected LinearGradient getRandomLinearGradient(){
        /*
            public LinearGradient (float x0, float y0, float x1, float y1, int[] colors, float[]
                positions, Shader.TileMode tile)

                Create a shader that draws a linear gradient along a line.

                Parameters
                x0 : The x-coordinate for the start of the gradient line
                y0 : The y-coordinate for the start of the gradient line
                x1 : The x-coordinate for the end of the gradient line
                y1 : The y-coordinate for the end of the gradient line
                colors : The colors to be distributed along the gradient line
                positions : May be null. The relative positions [0..1] of each corresponding color
                    in the colors array. If this is null, the the colors are distributed evenly
                    along the gradient line.
                tile : The Shader tiling mode
        */

        LinearGradient gradient = new LinearGradient(
                0,
                0,
                mSize.x,
                mSize.y,
                getRandomColorArray(), // Colors to draw the gradient
                null, // No position defined
                getRandomShaderTileMode() // Shader tiling mode
        );
        // Return the LinearGradient
        return gradient;
    }

    // Custom method to generate a random RadialGradient
    protected RadialGradient getRandomRadialGradient(){
        /*
            public RadialGradient (float centerX, float centerY, float radius, int[] colors,
                float[] stops, Shader.TileMode tileMode)

                Create a shader that draws a radial gradient given the center and radius.

                Parameters
                    centerX : The x-coordinate of the center of the radius
                    centerY : The y-coordinate of the center of the radius
                    radius : Must be positive. The radius of the circle for this gradient.
                    colors : The colors to be distributed between the center and edge of the circle
                    stops : May be null. Valid values are between 0.0f and 1.0f. The relative
                        position of each corresponding color in the colors array. If null, colors
                        are distributed evenly between the center and edge of the circle.
                    tileMode : The Shader tiling mode
        */
        RadialGradient gradient = new RadialGradient(
            mRandom.nextInt(mSize.x),
                mRandom.nextInt(mSize.y),
                mRandom.nextInt(mSize.x),
                getRandomColorArray(),
                null, // Stops position is undefined
                getRandomShaderTileMode() // Shader tiling mode

        );
        // Return the RadialGradient
        return gradient;
    }

    // Custom method to generate a random SweepGradient
    protected SweepGradient getRandomSweepGradient(){
        /*
            public SweepGradient (float cx, float cy, int[] colors, float[] positions)
                A subclass of Shader that draws a sweep gradient around a center point.

                Parameters
                cx : The x-coordinate of the center
                cy : The y-coordinate of the center
                colors : The colors to be distributed between around the center. There must be at
                    least 2 colors in the array.
                positions : May be NULL. The relative position of each corresponding color in the
                    colors array, beginning with 0 and ending with 1.0. If the values are not
                    monotonic, the drawing may produce unexpected results. If positions is NULL,
                    then the colors are automatically spaced evenly.
        */
        SweepGradient gradient = new SweepGradient(
                mRandom.nextInt(mSize.x),
                mRandom.nextInt(mSize.y),
                getRandomColorArray(), // Colors to draw gradient
                null // Position is undefined
        );
        // Return the SweepGradient
        return gradient;
    }

    // Custom method to generate random Shader TileMode
    protected Shader.TileMode getRandomShaderTileMode(){
        /*
            Shader
                Shader is the based class for objects that return horizontal spans of colors during
                drawing. A subclass of Shader is installed in a Paint calling paint.setShader(shader).
                After that any object (other than a bitmap) that is drawn with that paint will get
                its color(s) from the shader.
        */
        Shader.TileMode mode;
        int indicator = mRandom.nextInt(3);
        if(indicator==0){
            /*
                Shader.TileMode : CLAMP
                    replicate the edge color if the shader draws outside of its original bounds
            */
            mode = Shader.TileMode.CLAMP;
        }else if(indicator==1){
            /*
                Shader.TileMode : MIRROR
                    repeat the shader's image horizontally and vertically, alternating mirror images
                    so that adjacent images always seam
            */
            mode = Shader.TileMode.MIRROR;
        }else {
            /*
                Shader.TileMode : REPEAT
                    repeat the shader's image horizontally and vertically
            */
            mode = Shader.TileMode.REPEAT;
        }
        // Return the random Shader TileMode
        return mode;
    }

    // Custom method to generate random color array
    protected int[] getRandomColorArray(){
        int length = mRandom.nextInt(16-3)+3;
        int[] colors = new int[length];
        for (int i=0; i<length;i++){
            colors[i]=getRandomHSVColor();
        }
        // Return the color array
        return colors;
    }

    // Custom method to generate random HSV color
    protected int getRandomHSVColor(){
        /*
            Hue is the variation of color
            Hue range 0 to 360

            Saturation is the depth of color
            Range is 0.0 to 1.0 float value
            1.0 is 100% solid color

            Value/Black is the lightness of color
            Range is 0.0 to 1.0 float value
            1.0 is 100% bright less of a color that means black
        */

        // Generate a random hue value between 0 to 360
        int hue = mRandom.nextInt(361);

        // We make the color depth full
        float saturation = 1.0f;

        // We make a full bright color
        float value = 1.0f;

        // We avoid color transparency
        int alpha = 255;

        // Finally, generate the color
        int color = Color.HSVToColor(alpha,new float[]{hue,saturation,value});

        // Return the color
        return color;
    }

    /*
        Another way to generate a random color
        Custom method to generate a random color
    */
    protected int getRandomRGBColor(){
        // 256 is excluded, so random number is between 0 to 255
        int red = mRandom.nextInt(256);
        int green = mRandom.nextInt(256);
        int blue = mRandom.nextInt(256);
        int color = Color.argb(255, red, green, blue);

        // Return the random argb color
        return color;
    }
}
xml/colored_live_wallpaper.xml

<?xml version="1.0" encoding="utf-8"?>
<wallpaper
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:label="Gradient Live Wallpaper"
    android:thumbnail="@drawable/preview"
    />
AndroidManifest.xml

<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.cfsuman.me.gradientlivewallpaper"
    >

    <application
        android:allowBackup="true"
        android:label="@string/app_name"
        android:icon="@mipmap/ic_launcher"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <service
            android:name=".GradientLiveWallPaperService"
            android:label="Gradient Live Wallpaper"
            android:enabled="true"
            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/colored_live_wallpaper"
                >
            </meta-data>
        </service>

    </application>

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

</manifest>
More android examples