1. 程式人生 > >Android動態桌布詳解

Android動態桌布詳解

動態桌布

Livewallpaper(動態桌布): 首先動態桌布並不是GIF圖片,而是一個獨立的應用程式,本質是一個Service,甚至可以沒有桌面圖示。

直接看AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.qwq.clocklivewallpaper">

    <application
        android:allowBackup="true"
android:icon="@drawable/clock" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <!-- 主介面Activity.可以去掉。動態桌布應用是可以允許沒有Activity的 --> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"
/> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <!-- 動態桌布設定介面,可有可無,一般桌布應用會有,作用是讓使用者自定義動態桌布 --> <activity android:name=".SettingsActivity" android:exported="true"
android:label="@string/app_name"> </activity> <!-- 動態桌布本質就是Service,所以此類為動態桌布核心--> <service android:name=".ClockWallpaperService" android:enabled="true" android:label="@string/wallpaper_name" //下面第一張圖片中label android:permission="android.permission.BIND_WALLPAPER">//動態桌布必須加此許可權 <intent-filter> //系統就是通過APK的這個action把其當做一個動態牆紙。 <action android:name="android.service.wallpaper.WallpaperService"></action> </intent-filter> <!-- android:resource 指定的xml很重要!!!後面會單獨介紹 --> <meta-data android:name="android.service.wallpaper" android:resource="@xml/clock_wallpaper"> </meta-data> </service> </application> </manifest>

clock_wallpaper.xml

<?xml version="1.0" encoding="UTF-8"?>
<wallpaper xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/wallpaper_description"
    android:settingsActivity="com.qwq.clocklivewallpaper.SettingsActivity"
    android:thumbnail="@drawable/ic_preview" />

android:thumbnail 動態桌布列表中的圖示,
android:description 動態桌布的簡單介紹文字,有的手機可能不顯示
動態桌布列表圖片

android:settingsActivity 指定一個Activity。系統會檢測動態桌布應用有沒有此屬性。如果有則和下方的圖片顯示效果一樣(兩個Button),點選下圖中設定Button可跳轉至指定的Activity(設定介面)。如果沒有此屬性,就只有一個設定桌布Button。可以去掉此屬性看一下效果。
動態桌布設定介面圖片

WallpaperService(核心)

實現動態桌布必須繼承WallpaperService,且過載onCreateEngine方法。onCreateEngine()方法只需返回一個Engine的子類物件就可以了,所以動態桌布應用的主要工作就是實現Engine的子類。其原理是使用surfaceview不斷更新,實現動態桌布效果。不多說了,直接貼程式碼,重要的方法都會有所說明。

package com.qwq.clocklivewallpaper;

import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.service.wallpaper.WallpaperService;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;

import java.util.Date;

public class ClockWallpaperService extends WallpaperService {

    static final String TAG = ClockWallpaperService.class.getSimpleName();
    static final int POINTER_WIDTH = 9;

  //  onCreateEngine()方法需返回一個Engine的子類物件就可以了,
  //  所以動態桌布應用的主要工作就是實現Engine的子類
  @Override
  public Engine onCreateEngine() {
        Log.d(TAG, "onCreateEngine");
        return new ClockWallpaperEngine();
    }


    //實現Engine的子類,本文的重中之重,動態桌布就是在此具體實現的
  private class ClockWallpaperEngine extends Engine implements 
  OnSharedPreferenceChangeListener {
        private final Handler handler = new Handler();
        // surfacewive使用執行緒更新UI,所以我們可使用Runnable介面建立一個執行緒,把具體繪製方法放進去,
        // 下文會使用handler呼叫
        private final Runnable drawRunner = new Runnable() {
            @Override
            public void run() {
                draw();
            }
        };

        private Paint paint;
        private int width;
        private int height;
        private boolean isVisible = true;
        private boolean isShowSecond;
        private ClockView clockView;
        private SharedPreferences sp;

 //建構函式,初始化動態桌布
 public ClockWallpaperEngine() {
            Log.d(TAG, "ClockWallpaperEngine");
            initSp();
            initPaint();
            startDrawClock();
        }

  public void initSp() {
         sp = PreferenceManager.getDefaultSharedPreferences(ClockWallpaperService.this);
         sp.registerOnSharedPreferenceChangeListener(this);
         isShowSecond = sp.getBoolean(SettingsActivity.IS_SHOW_SECOND, true);
        }

  public void initPaint() {
            paint = new Paint();
            paint.setAntiAlias(true);
            paint.setStyle(Paint.Style.STROKE);
            paint.setStrokeWidth(POINTER_WIDTH);
        }

   public void startDrawClock() {
            clockView = new ClockView(getApplicationContext());
            handler.post(drawRunner);
        }

   @Override
   public void onCreate(SurfaceHolder surfaceHolder) {
            super.onCreate(surfaceHolder);
            Log.d(TAG, "onCreate");
        }


   //監聽是否可見變化,可見時開始更新UI,不可見時停止重新整理
   @Override
   public void onVisibilityChanged(boolean visible) {
            this.isVisible = visible;
            if (visible) {
                handler.post(drawRunner);
            } else {
                handler.removeCallbacks(drawRunner);
            }
        }

   @Override
   public void onSurfaceDestroyed(SurfaceHolder holder) {
            super.onSurfaceDestroyed(holder);
            isVisible = false;
            handler.removeCallbacks(drawRunner);
            sp.unregisterOnSharedPreferenceChangeListener(this);
        }


   @Override
   public void onSurfaceChanged(SurfaceHolder holder,int format,int width,int height){    
            this.width = width;
            this.height = height;
            super.onSurfaceChanged(holder, format, width, height);
        }

   //繪製,每個200毫秒重新整理介面
   private void draw() {
            SurfaceHolder holder = getSurfaceHolder();
            Canvas canvas = null;
            try {
                canvas = holder.lockCanvas();
                if (canvas != null) {
                    drawClock(canvas);
                }
            } finally {
                if (canvas != null) {
                    holder.unlockCanvasAndPost(canvas);
                }
            }

            handler.removeCallbacks(drawRunner);
            if (isVisible) {
                handler.postDelayed(drawRunner, 200);
            }
        }

  //具體繪製鐘錶
  private void drawClock(Canvas canvas) {
       canvas.drawColor(Color.WHITE);   //繪製整個動態桌布的背景顏色
       clockView.config(width / 2, height / 2, (int) (width * 0.8f), new Date(), 
       paint, isShowSecond);
       clockView.draw(canvas);
        }

  @Override
  public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String 
  key){ 
     //監聽SharedPreference變化,改變動態桌布樣式
     if (SettingsActivity.IS_SHOW_SECOND.equals(key)) {
     isShowSecond = sharedPreferences.getBoolean(SettingsActivity.IS_SHOW_SECOND,  
     true);
            }
        }

   @Override
   public void onTouchEvent(MotionEvent event) {
            super.onTouchEvent(event);
            // 可以在這裡做一些與使用者互動的操作,例如動態星星桌布,使用者觸控式螢幕幕時增加星星數量。
            Log.d(TAG, "onTouchEvent");
        }
    }

}

ClockView(鐘錶的具體繪製)

package com.qwq.clocklivewallpaper;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.Log;
import android.view.View;

import java.util.Calendar;
import java.util.Date;

public class ClockView extends View {

    static final String TAG = ClockView.class.getSimpleName();
    private float x;
    private float y;
    private int radius;
    private Calendar cal;
    private Paint paint;
    private Bitmap clockDial = BitmapFactory.decodeResource(getResources(),R.drawable.clock_bg);
    private int sizeScaled = -1;
    private Bitmap clockDialScaled;
    private boolean isShowSecond;

    public ClockView(Context context) {
        super(context);
        cal = Calendar.getInstance();
    }

//設定鐘錶寬高等屬性
public void config(float x, float y, int size, Date date, Paint paint, boolean 
    isShowSecond) {
        this.x = x;
        this.y = y;
        this.paint = paint;
        this.isShowSecond = isShowSecond;

        cal.setTime(date);

        if (size != sizeScaled) {
            clockDialScaled = Bitmap.createScaledBitmap(clockDial, size, size, false);
            radius = size / 2;
        }
    }

//具體繪製鐘錶的指標
protected void onDraw(Canvas canvas) {
   Log.d(TAG,"onDraw:"+canvas);
   super.onDraw(canvas);
   if (paint != null) {
            canvas.drawBitmap(clockDialScaled, x - radius, y - radius, null);

            float sec = cal.get(Calendar.SECOND);
            float min = cal.get(Calendar.MINUTE);
            float hour = cal.get(Calendar.HOUR_OF_DAY);


   paint.setColor(Color.RED);
   canvas.drawLine(x, y,
   (float)(x + (radius * 0.5f)*Math.cos(Math.toRadians((hour / 12.0f * 360.0f) - 90f))),
   (float)(y + (radius * 0.5f)*Math.sin(Math.toRadians((hour / 12.0f * 360.0f) -90f))),   
   paint);
   canvas.save();

    paint.setColor(Color.BLUE);
    canvas.drawLine(x, y, 
    (float)(x + (radius * 0.6f)*Math.cos(Math.toRadians((min / 60.0f * 360.0f) - 90f))),
    (float)(y + (radius * 0.6f)*Math.sin(Math.toRadians((min / 60.0f * 360.0f) - 90f))),  
    paint);
    canvas.save();

    if (isShowSecond) {
       paint.setColor(Color.GREEN);
       canvas.drawLine(x, y,
       (float)(x + (radius * 0.7f)*Math.cos(Math.toRadians((sec/60.0f * 360.0f) - 90f))),
       (float)(y + (radius * 0.7f)*Math.sin(Math.toRadians((sec/60.0f * 360.0f) - 90f))),   
       paint);
          }
        }
    }

}