2015年4月1日 星期三

Android的動態桌布 - Wallpaper

在Android中可以製作動態桌布,動態桌布是一個類似動畫的桌布,上面可以顯示動畫,甚或可以與使用者產生互動。

下面以一個範例來實作一個簡單的動態桌布,桌布背景為黑色, 當使用者點擊或劃過桌布時,會產生藍色的圓,並且隨時間變小消失:

  1. 建立WallpaperService。動態桌布其實也是一種Service,使用的class為WallpaperService,所以我們要先建立WallpaperService類別,程式碼如下,需要注意的是,在WallpaperService裡的Engine內部類別不行在WallpaperService之外建立然後使用,必須是也內部類別的方式存在在WallpaperService中:

  2. MyWallpaperService.java:
    package com.example.administrator.wallpaperservice_example;
    
    import android.service.wallpaper.WallpaperService;
    import android.util.Log;
    import android.view.MotionEvent;
    import android.view.SurfaceHolder;
    
    public class MyWallpaperService extends WallpaperService{
        @Override
        public Engine onCreateEngine() {
            return new MyEngine();
        }
    
        //設定自訂的Engine
        class MyEngine extends Engine{
            WallpaperThread wallpaperThread;
            public MyEngine() {
                SurfaceHolder surfaceHolder = getSurfaceHolder();
                wallpaperThread = new WallpaperThread(surfaceHolder);
            }
    
            @Override
            public void onCreate(SurfaceHolder surfaceHolder) {
                super.onCreate(surfaceHolder);
                //設定動態桌布可以互動觸控事件
                setTouchEventsEnabled(true);
            }
    
            @Override
            public void onSurfaceCreated(SurfaceHolder holder) {
                super.onSurfaceCreated(holder);
                //開始執行動態桌布
                wallpaperThread.start();
            }
    
            @Override
            public void onSurfaceDestroyed(SurfaceHolder holder) {
                super.onSurfaceDestroyed(holder);
                //結束動態桌布
                wallpaperThread.stopRunning();
            }
    
            @Override
            public void onDestroy() {
                super.onDestroy();
                //讓GC回收wallpaperThread
                wallpaperThread = null;
            }
    
            @Override
            public void onTouchEvent(MotionEvent event) {
                super.onTouchEvent(event);
                //執行wallpaperThread裡的觸控事件
                wallpaperThread.doTouchEvent(event);
            }
        }
    }
    
  3. 建立給WallpaperService的資訊xml檔。其中可以指定縮圖(thumbnail)、簡介(description)等,配置內容如下:

  4. wallpaperinfo.xml:
    <?xml version="1.0" encoding="utf-8"?>
    
    <wallpaper xmlns:android="http://schemas.android.com/apk/res/android">
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:description="動態桌布"
    </wallpaper>
    
  5. 註冊WallpaperService。因為WallpaperService是一種Service,所以要在AndroidManifest.xml中的<Application>節點中添加Service的注冊,如下程式碼,有幾點要注意,在Service中要加入"android.permission.BIND_WALLPAPER"並設定<intent-filter>的<action>為"android.service.wallpaper.WallpaperService"、還要加入相關的<meta-data>並在其中指定動態桌步的資訊xml檔,即上步驟的wallpaperinfo.xml:

  6. AndroidManifest.xml的片段:
    <service
                android:name=".MyWallpaperService"
                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/wallpaperinfo" />
    </service>
  7. 建立Engine要用的Thread類別。
  8. WallpaperThread.java:
    package com.example.administrator.wallpaperservice_example;
    
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.util.Log;
    import android.view.MotionEvent;
    import android.view.SurfaceHolder;
    import java.util.ArrayList;
    
    public class WallpaperThread extends Thread {
        //run為ture表示不斷檢查並執行消圓動作
        boolean run = true;
        //wait為true表示無圓可消,所以暫停消圓動作
        boolean wait = true;
    
        //SurfaceHolder用來取得Canvas
        SurfaceHolder surfaceHolder;
        Canvas canvas;
        Paint paint;
        //用來紀錄已產生的圓資料,待處理
        ArrayList<Circle> pastCircles = new ArrayList<>();
        //用來紀錄要從pastCircles中刪去的圓資料,因為不能在ArrayList遍歷元素時刪除元素,否則會有ConcurrentModificationException
        ArrayList<Circle> circlesToBeRemoved = new ArrayList<>();
        public WallpaperThread(SurfaceHolder surfaceHolder) {
            this.surfaceHolder = surfaceHolder;
            //設定畫圓用的畫筆顏色
            paint = new Paint();
            paint.setColor(Color.BLUE);
        }
    
        @Override
        public void run() {
            canvas = this.surfaceHolder.lockCanvas(null);
            //繪製初始背景
            canvas.drawColor(Color.BLACK);
            surfaceHolder.unlockCanvasAndPost(canvas);
            while (run) {
                if (wait) {
                    try {
                        synchronized (this) {
                            //沒有圓可消時,讓Thread等待
                            wait();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    synchronized (pastCircles) {
                        //將圓刪掉(背景重新塗色)
                        canvas = surfaceHolder.lockCanvas(null);
                        canvas.drawColor(Color.BLACK);
                        //檢查pastCircles中有無待處理的圓
                        for (Circle circle : pastCircles) {
                            //如果圓的半徑已被減0以下,就將其從pastCircles中刪除
                            if (circle.getRadius() <= 0) {
                                circlesToBeRemoved.add(circle);
                            } else {
                                //畫圓
                                canvas.drawCircle(circle.getCenterX(), circle.getCenterY(), circle.getRadius(), paint);
                                //將圓半徑減1
                                circle.setRadius(circle.getRadius() - 1);
                            }
                        }
                        surfaceHolder.unlockCanvasAndPost(canvas);
                        //從pastCircles中刪去已消完的圓資料
                        pastCircles.removeAll(circlesToBeRemoved);
                        //如果pastCircles中沒有圓了,就進行等待
                        wait = (pastCircles.size() == 0);
                    }
                }
            }
        }
    
        public void stopRunning() {
            run = false;
        }
    
        public void doTouchEvent(MotionEvent motionEvent) {
            //判斷Touch動作
            switch (motionEvent.getAction()) {
                case MotionEvent.ACTION_DOWN:
                case MotionEvent.ACTION_MOVE:
                    //用來紀錄觸控動作發生時相應的圓圈資訊
                    Circle currentCircle = new Circle(motionEvent.getX(), motionEvent.getY(), 100);
                    //保護pastCircles,避免跟run()中讀取pastCircles的動作起衡突
                    synchronized (pastCircles) {
                        //將要處理的的圓紀錄到pastCircles中
                        pastCircles.add(currentCircle);
                        wait = false;
                        synchronized (this) {
                            //主動喚醒Thread
                            notify();
                        }
                    }
                    break;
            }
        }
    
        //用來紀錄圓的資訊
        class Circle {
            float centerX;
            float centerY;
            int radius = 100;
    
            public Circle(float centerX, float centerY, int radius) {
                this.centerX = centerX;
                this.centerY = centerY;
                this.radius = radius;
            }
    
            public void setRadius(int radius) {
                this.radius = radius;
            }
    
            public float getCenterX() {
                return centerX;
            }
    
            public float getCenterY() {
                return centerY;
            }
    
            public int getRadius() {
                return radius;
            }
        }
    }

  9. 最後的成品如下面影片展示:

  10. 附上源始碼下載: Android動態桌布.7z

3 則留言 :

  1. 您好,想請問大大如何把動態桌布的黑底改成自定圖片?

    回覆刪除
    回覆
    1. 您可以使用canvas.drawBitmap()代替canvas.drawColor()來繪製您要的圖片。

      刪除