12/08/2018, 14:41

Tạo LiveWallpaper

Hôm nay mình xin trình bày cách tạo LiveWallpaper với hiệu ứng Matrix Rain. Để tạo được LiveWallpaper ta cần set up như sau: Tạo class extends WallpaperService (service thực hiện vẽ wallpaper) khi ở màn hình preview wallpaper để vào màn hình settings ta đã custom ta cần file wallpaper.xml ...

Hôm nay mình xin trình bày cách tạo LiveWallpaper với hiệu ứng Matrix Rain. Để tạo được LiveWallpaper ta cần set up như sau:

  • Tạo class extends WallpaperService (service thực hiện vẽ wallpaper)
  • khi ở màn hình preview wallpaper để vào màn hình settings ta đã custom ta cần file wallpaper.xml (không bắt buộc)
  1. Tạo hiệu ứng Matrix Rain

đầu tiên ta tạo custom view sẽ hiển thị effect Matrix Rain

public class MatrixEffect extends View {

    public MatrixEffect(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

}
  • Khai báo biến dùng trong MatrixEffect
    int awidth = 1000000; // default initial awidth
    int height = 100;    // default initial height
    Canvas canvas = null;// default canvas
    Bitmap canvasBitmap; // bitmap used to create the canvas
    int fontSize = 15;   // font size of the text which will fall
    int columnSize = awidth / fontSize; // column size, no digit required to fill 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;
    Random rand = new Random();

    int[] textPosition; // contain the position which will help to draw the text
  • Tiếp theo ta khai báo hàm thực hiện vẽ text
   // draw the text on the bitmap
    void drawText() {
        // set up the paint
        Paint paint = new Paint();
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.GREEN);
        paint.setTextSize(fontSize);

        // loop and paint
        for (int index = 0; index < textPosition.length; index++) {
            // draw the text at the random position
            canvas.drawText(""+textChar[rand.nextInt(textLength)], index * fontSize, textPosition[index] * fontSize, paint);
            // check if text has reached bottom or not
            if (textPosition[index] * fontSize > height && Math.random() > 0.975) {
                textPosition[index] = 0;
            }
            textPosition[index]++;
        }
    }
  • Dưới đây là hàm thực hiện gọi hàm vẽ text với alpha cho trước
public void canvasDraw() {
        // set the paint for the canvas
        Paint paint = new Paint();
        paint.setColor(Color.BLACK);
        paint.setAlpha(5);
        paint.setStyle(Paint.Style.FILL);
        // draw rect to clear the canvas
        canvas.drawRect(0, 0, awidth, height, paint);

        drawText(); // draw the canvas
    }
  • Để có view với hiệu ứng động Matrix Rain ta sẽ thực hiện vẽ từ hàm draw, sau khi vẽ ta sẽ update lại gía trị position vẽ text và gọi hàm invalidate() để yêu cầu view vẽ lại
@Override
    public void draw(Canvas canvas) {
        super.draw(canvas);

        Paint paint = new Paint();
        paint.setColor(Color.BLACK);

        // draw the bitmap to canvas
        canvas.drawBitmap(canvasBitmap, 0, 0, paint);

        // call the draw command
        canvasDraw();

        // Redraw the canvas
        invalidate();
    }
  • Trước đó ta sẽ implement phương thức onSizeChanged() để khởi tạo các gía trị cho việc vẽ, ta initial gía trị ở đây vì khi đó ta sẽ xác định được height và awidth của view
@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        awidth = w;
        height = h;
        super.onSizeChanged(w, h, oldw, oldh);
        // create a Bitmap
        canvasBitmap = Bitmap.createBitmap(awidth, height, Bitmap.Config.ARGB_8888);
        canvas = new Canvas(canvasBitmap);
        // init paint with black rectangle
        Paint paint = new Paint();
        paint.setColor(Color.BLACK);
        paint.setAlpha(255);
        paint.setStyle(Paint.Style.FILL);
        canvas.drawRect(0, 0, awidth, height, paint);

        columnSize = awidth / fontSize;
        // initalise the textposiotn to zero
        textPosition = new int[columnSize+1]; // add one more drop
        for(int x = 0; x < columnSize; x++) {
            textPosition[x] = 0;
        }
    }
  • Để kiểm tra customview có hoạt động không ta khai báo trong activity_main.xml như sau
<com.example.maidaidien.matrixeffectcanvas.ui.MatrixEffect
        android:layout_awidth="match_parent"
        android:layout_height="match_parent"/>
  1. LiveWallpaper
  • Tạo Class MatrixWall extends WallpaperService. Lớp này sẽ override phương thức onCreateEngine() trả về object thuộc lớp Engine
public class MatrixWall extends WallpaperService {
    private boolean mVisible; // Visible flag
    Canvas canvas;
    int drawSpeed = 10;
    Context context;

    @Override
    public Engine onCreateEngine() {
        context = this;

        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 {
        // this is to handle the thread
        final Handler mHandler = new Handler();
        //the tread responsibe for drawing this thread get calls every time
        // drawspeed vars set the execution speed
        private final Runnable mDrawFrame = new Runnable() {
            @Override
            public void run() {
                // 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 draw function
                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);
        }

        // this 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;
            try {
                canvas = holder.lockCanvas(); // get the canvas
                if (canvas != null) {
                    // draw something
                }
            } 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 awidth, int height) {
            super.onSurfaceChanged(holder, format, awidth, height);
            // update when surface changed
        }
    }
}
  • Tiếp theo ta tạo file wallpaper.xml trong directory xml(res/xml/wallpaper.xml) dùng để chạy màn hình SettingsActivity khi người dùng chọn setting trong màn hình preview live wallpaper
<wallpaper xmlns:android="http://schemas.android.com/apk/res/android"
    android:thumbnail="@mipmap/ic_launcher"
    android:settingsActivity="com.example.maidaidien.matrixeffectcanvas.SettingsActivity"/>

android:settingsActivity="com.example.maidaidien.matrixeffectcanvas.SettingsActivity"/> sẽ trỏ đến màn hình SettingsActivity ở đây ta dùng Preferences API để tạo màn hình setting.

  • trong file AndroidManifest.xml ta khai báo
<uses-feature android:name="android.software.live_wallpaper"/>
<service
            android:name=".ui.MatrixWall"
            android:enabled="true"
            android:label="cube"
            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"></meta-data>
        </service>
<activity
            android:name=".SettingsActivity"
            android:label="@string/title_activity_settings"
            android:exported="true"/>

Chú ý: android:exported="true" cần khai báo nếu không sẽ không mở màn hình SettingsActivity khi click vào setting trong màn hình preview livewallpaper Tiếp theo ta sẽ cho đoạn code vẽ hiệu ứng Matrix Rain đã thực hiện bên trên vào class MatrixWall ta được code hoàn chỉnh như sau:

public class MatrixWall extends WallpaperService {
    private boolean mVisible; // Visible flag
    Canvas canvas;
    int drawSpeed = 10;
    Context context;

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

    int awidth = 1000000; //default initial awidth
    int height = 100; //default initial height
    int fontSize = 15; //font size of the text which will fall
    int columnSize = awidth / 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() {
        context = this;

        //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);

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

        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 {
        // this is to handle the thread
        final Handler mHandler = new Handler();
        //the tread responsibe for drawing this thread get calls every time
        // drawspeed vars set the execution speed
        private final Runnable mDrawFrame = new Runnable() {
            @Override
            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
            awidth = getDesiredMinimumWidth();
            height = getDesiredMinimumHeight();
            columnSize = awidth/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(context);
                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 = awidth/fontSize;
                // call the draw function
                drawFrame();
            } else {
                SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
                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 = awidth/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 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;
            try {
                canvas = holder.lockCanvas(); // get the canvas
                if (canvas != null) {
                    // draw something

                    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 awidth, int height) {
            super.onSurfaceChanged(holder, format, awidth, height);
            // update when surface changed
            // 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, awidth, height, paint);
        }
    }

    // draw the text on the bitmap
    void drawText() {
        // set up the paint
        Paint paint = new Paint();
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.GREEN);
        paint.setTextSize(fontSize);

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

    public void canvasDraw() {
        // set the paint for the canvas
        Paint paint = new Paint();
        paint.setColor(Color.BLACK);
        paint.setAlpha(5);
        paint.setStyle(Paint.Style.FILL);
        // draw rect to clear the canvas
        canvas.drawRect(0, 0, awidth, height, paint);

        drawText(); // draw the canvas
    }
}
  • Cuối cùng, để set wallpaper ta sẽ chạy đoạn code sau
public void onClick(View view) {
        Intent intent = new Intent(
                WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
        intent.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT,
                new ComponentName(this, MatrixWall.class));
        startActivity(intent);
    }

src: hexgear code

0