Tạo LiveWallpaper
Bài đăng này đã không được cập nhật trong 3 năm
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 width = 1000000; // default initial width
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 = width / 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, width, 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à width của view
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
width = w;
height = h;
super.onSizeChanged(w, h, oldw, oldh);
// create a Bitmap
canvasBitmap = Bitmap.createBitmap(width, 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, width, height, paint);
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] = 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_width="match_parent"
android:layout_height="match_parent"/>
- LiveWallpaper
- Tạo Class
MatrixWall
extendsWallpaperService
. Lớp này sẽoverride
phương thứconCreateEngine()
trả vềobject
thuộc lớpEngine
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 width, int height) {
super.onSurfaceChanged(holder, format, width, 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 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() {
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 = width / 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
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(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 = width/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 = 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 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 width, int height) {
super.onSurfaceChanged(holder, format, width, 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, width, 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, width, 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
All rights reserved