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