1:配置Mainfest.xml
Android可查到该Live Wallpaper
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.wing.wallpaper.test"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="4" />
<!-- Android 会通过android.software.live_wallpaper feature Intent 获取动态壁纸列表 -->
<uses-feature android:name="android.software.live_wallpaper" />
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".TestWallpaper"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:label="@string/clock_settings"
android:name="com.android.wallpaper.polarclock.PolarClockSettings"
android:theme="@android:style/Theme"
android:exported="true">
</activity>
<!--android:permission="android.permission.BIND_WALLPAPER" 绑定壁纸权限 -->
<!--<action android:name="android.service.wallpaper.WallpaperService" 动态壁纸主View Layout -->
<!-- <meta-data android:name="android.service.wallpaper" android:resource="@xml/polar_clock" /> Wallpaper 相关配置 -->
<service
android:label="@string/wallpaper_clock"
android:name="com.android.wallpaper.polarclock.PolarClockWallpaper"
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/polar_clock" />
</service>
</application>
</manifest>
Wallpaper 相关配置xml/polar_clock.xml
<wallpaper xmlns:android="http://schemas.android.com/apk/res/android"
android:author="@string/wallpaper_clock_author"
android:description="@string/wallpaper_clock_desc"
android:thumbnail="@drawable/polarclock_thumb"
android:settingsActivity="com.android.wallpaper.polarclock.PolarClockSettings" />
Wallpaper 作者,缩略图,设置Activity
2: 创建Setting Activity
public class PolarClockSettings extends WallpaperSettingsActivity
implements SharedPreferences.OnSharedPreferenceChangeListener {
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
//定义SharedPreferencesName,可通过该Name对其修改时间注册监听,见下
getPreferenceManager().setSharedPreferencesName(
PolarClockWallpaper.SHARED_PREFS_NAME);
addPreferencesFromResource(R.xml.polar_clock_prefs);
getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(
this);
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onDestroy() {
getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(
this);
super.onDestroy();
}
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
(new BackupManager(this)).dataChanged();
}
}
Setting Activity 配置 xml/R.xml.polar_clock_prefs
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
android:title="@string/clock_settings"
android:key="polar_clock_settings">
<CheckBoxPreference
android:key="show_seconds"
android:title="@string/show_seconds"
android:defaultValue="true"
android:persistent="true"
/>
<CheckBoxPreference
android:key="variable_line_width"
android:title="@string/variable_line_width"
android:defaultValue="true"
android:persistent="true"
/>
<ListPreference
android:key="palette"
android:title="@string/palette"
android:entries="@array/polar_clock_palette_names"
android:entryValues="@array/polar_clock_palette_ids"
/>
</PreferenceScreen>
3: Wallpaper 主View
public class PolarClockWallpaper extends WallpaperService {
private final Handler mHandler = new Handler();
private IntentFilter mFilter;
@Override
public void onCreate() {
super.onCreate();
mFilter = new IntentFilter();
mFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
}
@Override
public void onDestroy() {
super.onDestroy();
}
public Engine onCreateEngine() {
return new ClockEngine();
}
class ClockEngine extends Engine implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final float SMALL_RING_THICKNESS = 8.0f;
private static final float MEDIUM_RING_THICKNESS = 16.0f;
private static final float LARGE_RING_THICKNESS = 32.0f;
private static final float DEFAULT_RING_THICKNESS = 24.0f;
private static final float SMALL_GAP = 14.0f;
private static final float LARGE_GAP = 38.0f;
private final HashMap<String, ClockPalette> mPalettes = new HashMap<String, ClockPalette>();
private ClockPalette mPalette;
private SharedPreferences mPrefs;
private boolean mShowSeconds;
private boolean mVariableLineWidth;
private boolean mWatcherRegistered;
private Time mCalendar;
private final Paint mPaint = new Paint();
private final RectF mRect = new RectF();
private float mOffsetX;
private final BroadcastReceiver mWatcher = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
final String timeZone = intent.getStringExtra("time-zone");
mCalendar = new Time(TimeZone.getTimeZone(timeZone).getID());
drawFrame();
}
};
private final Runnable mDrawClock = new Runnable() {
public void run() {
drawFrame();
}
};
private boolean mVisible;
ClockEngine() {
//android 自带xml解析工具
XmlResourceParser xrp = getResources().getXml(R.xml.polar_clock_palettes);
try {
int what = xrp.getEventType();
while (what != END_DOCUMENT) {
if (what == START_TAG) {
if ("palette".equals(xrp.getName())) {
ClockPalette pal = ClockPalette.parseXmlPaletteTag(xrp);
if (pal.getId() != null) {
mPalettes.put(pal.getId(), pal);
}
}
}
what = xrp.next();
}
} catch (IOException e) {
Log.e(LOG_TAG, "An error occured during wallpaper configuration:", e);
} catch (XmlPullParserException e) {
Log.e(LOG_TAG, "An error occured during wallpaper configuration:", e);
} finally {
xrp.close();
}
mPalette = CyclingClockPalette.getFallback();
}
@Override
public void onCreate(SurfaceHolder surfaceHolder) {
super.onCreate(surfaceHolder);
//对setting的SharedPreferences 注册修改监听
mPrefs = PolarClockWallpaper.this.getSharedPreferences(SHARED_PREFS_NAME, 0);
mPrefs.registerOnSharedPreferenceChangeListener(this);
// load up user's settings 加载SharedPreferences
onSharedPreferenceChanged(mPrefs, null);
mCalendar = new Time();
mCalendar.setToNow();
final Paint paint = mPaint;
paint.setAntiAlias(true);
paint.setStrokeWidth(DEFAULT_RING_THICKNESS);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStyle(Paint.Style.STROKE);
if (isPreview()) {
mOffsetX = 0.5f;
}
}
@Override
public void onDestroy() {
super.onDestroy();
//取消对时区改变的坚挺
if (mWatcherRegistered) {
mWatcherRegistered = false;
unregisterReceiver(mWatcher);
}
//取消图像重绘事件队列
mHandler.removeCallbacks(mDrawClock);
}
//特定SharedPreferences 配置改变回调方法
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
boolean changed = false;
if (key == null || PREF_SHOW_SECONDS.equals(key)) {
mShowSeconds = sharedPreferences.getBoolean(
PREF_SHOW_SECONDS, true);
changed = true;
}
if (key == null || PREF_VARIABLE_LINE_WIDTH.equals(key)) {
mVariableLineWidth = sharedPreferences.getBoolean(
PREF_VARIABLE_LINE_WIDTH, true);
changed = true;
}
if (key == null || PREF_PALETTE.equals(key)) {
String paletteId = sharedPreferences.getString(
PREF_PALETTE, "");
ClockPalette pal = mPalettes.get(paletteId);
if (pal != null) {
mPalette = pal;
changed = true;
}
}
if (mVisible && changed) {
drawFrame();
}
}
//View 是否可见变化响应时间
@Override
public void onVisibilityChanged(boolean visible) {
mVisible = visible;
if (visible) {
if (!mWatcherRegistered) {
mWatcherRegistered = true;
registerReceiver(mWatcher, mFilter, null, mHandler);
}
mCalendar = new Time();
mCalendar.setToNow();
} else {
if (mWatcherRegistered) {
mWatcherRegistered = false;
unregisterReceiver(mWatcher);
}
mHandler.removeCallbacks(mDrawClock);
}
drawFrame();
}
//View 大小发生改变
@Override
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
super.onSurfaceChanged(holder, format, width, height);
drawFrame();
}
@Override
public void onSurfaceCreated(SurfaceHolder holder) {
super.onSurfaceCreated(holder);
}
@Override
public void onSurfaceDestroyed(SurfaceHolder holder) {
super.onSurfaceDestroyed(holder);
mVisible = false;
mHandler.removeCallbacks(mDrawClock);
}
//位置改变,如Home滑动到别的页,3->1->3
@Override
public void onOffsetsChanged(float xOffset, float yOffset,
float xStep, float yStep, int xPixels, int yPixels) {
if (isPreview()) return;
mOffsetX = xOffset;
drawFrame();
}
//绘制view
void drawFrame() {
if (mPalette == null) {
Log.w("PolarClockWallpaper", "no palette?!");
return;
}
final SurfaceHolder holder = getSurfaceHolder();
final Rect frame = holder.getSurfaceFrame();
final int width = frame.width();
final int height = frame.height();
Canvas c = null;
try {
c = holder.lockCanvas();
if (c != null) {
final Time calendar = mCalendar;
final Paint paint = mPaint;
final long millis = System.currentTimeMillis();
calendar.set(millis);
calendar.normalize(false);
int s = width / 2;
int t = height / 2;
c.drawColor(mPalette.getBackgroundColor());
c.translate(s + MathUtils.lerp(s, -s, mOffsetX), t);
c.rotate(-90.0f);
if (height < width) {
c.scale(0.9f, 0.9f);
}
float size = Math.min(width, height) * 0.5f - DEFAULT_RING_THICKNESS;
final RectF rect = mRect;
rect.set(-size, -size, size, size);
float angle;
float lastRingThickness = DEFAULT_RING_THICKNESS;
if (mShowSeconds) {
// Draw seconds
angle = (float) (millis % 60000) / 60000.0f;
//Log.d("PolarClock", "millis=" + millis + ", angle=" + angle);
paint.setColor(mPalette.getSecondColor(angle));
if (mVariableLineWidth) {
lastRingThickness = SMALL_RING_THICKNESS;
paint.setStrokeWidth(lastRingThickness);
}
c.drawArc(rect, 0.0f, angle * 360.0f, false, paint);
}
// Draw minutes
size -= (SMALL_GAP + lastRingThickness);
rect.set(-size, -size, size, size);
angle = ((calendar.minute * 60.0f + calendar.second) % 3600) / 3600.0f;
paint.setColor(mPalette.getMinuteColor(angle));
if (mVariableLineWidth) {
lastRingThickness = MEDIUM_RING_THICKNESS;
paint.setStrokeWidth(lastRingThickness);
}
c.drawArc(rect, 0.0f, angle * 360.0f, false, paint);
// Draw hours
size -= (SMALL_GAP + lastRingThickness);
rect.set(-size, -size, size, size);
angle = ((calendar.hour * 60.0f + calendar.minute) % 1440) / 1440.0f;
paint.setColor(mPalette.getHourColor(angle));
if (mVariableLineWidth) {
lastRingThickness = LARGE_RING_THICKNESS;
paint.setStrokeWidth(lastRingThickness);
}
c.drawArc(rect, 0.0f, angle * 360.0f, false, paint);
// Draw day
size -= (LARGE_GAP + lastRingThickness);
rect.set(-size, -size, size, size);
angle = (calendar.monthDay - 1) /
(float) (calendar.getActualMaximum(Time.MONTH_DAY) - 1);
paint.setColor(mPalette.getDayColor(angle));
if (mVariableLineWidth) {
lastRingThickness = MEDIUM_RING_THICKNESS;
paint.setStrokeWidth(lastRingThickness);
}
c.drawArc(rect, 0.0f, angle * 360.0f, false, paint);
// Draw month
size -= (SMALL_GAP + lastRingThickness);
rect.set(-size, -size, size, size);
angle = (calendar.month) / 11.0f; // NB: month is already on [0..11]
paint.setColor(mPalette.getMonthColor(angle));
if (mVariableLineWidth) {
lastRingThickness = LARGE_RING_THICKNESS;
paint.setStrokeWidth(lastRingThickness);
}
c.drawArc(rect, 0.0f, angle * 360.0f, false, paint);
}
} finally {
if (c != null) holder.unlockCanvasAndPost(c);
}
mHandler.removeCallbacks(mDrawClock);
if (mVisible) {
if (mShowSeconds) {
mHandler.postDelayed(mDrawClock, 1000 / 25);
} else {
// If we aren't showing seconds, we don't need to update
// nearly as often.
mHandler.postDelayed(mDrawClock, 2000);
}
}
}
}
其他相关类
// Clock 父类
static abstract class ClockPalette {
public static ClockPalette parseXmlPaletteTag(XmlResourceParser xrp) {
String kind = xrp.getAttributeValue(null, "kind");
if ("cycling".equals(kind)) {
return CyclingClockPalette.parseXmlPaletteTag(xrp);
} else {
return FixedClockPalette.parseXmlPaletteTag(xrp);
}
}
public abstract int getBackgroundColor();
// forAngle should be on [0.0,1.0) but 1.0 must be tolerated
public abstract int getSecondColor(float forAngle);
public abstract int getMinuteColor(float forAngle);
public abstract int getHourColor(float forAngle);
public abstract int getDayColor(float forAngle);
public abstract int getMonthColor(float forAngle);
public abstract String getId();
}
static class FixedClockPalette extends ClockPalette {
protected String mId;
protected int mBackgroundColor;
protected int mSecondColor;
protected int mMinuteColor;
protected int mHourColor;
protected int mDayColor;
protected int mMonthColor;
private static FixedClockPalette sFallbackPalette = null;
public static FixedClockPalette getFallback() {
if (sFallbackPalette == null) {
sFallbackPalette = new FixedClockPalette();
sFallbackPalette.mId = "default";
sFallbackPalette.mBackgroundColor = Color.WHITE;
sFallbackPalette.mSecondColor =
sFallbackPalette.mMinuteColor =
sFallbackPalette.mHourColor =
sFallbackPalette.mDayColor =
sFallbackPalette.mMonthColor =
Color.BLACK;
}
return sFallbackPalette;
}
private FixedClockPalette() { }
public static ClockPalette parseXmlPaletteTag(XmlResourceParser xrp) {
final FixedClockPalette pal = new FixedClockPalette();
pal.mId = xrp.getAttributeValue(null, "id");
String val;
if ((val = xrp.getAttributeValue(null, "background")) != null)
pal.mBackgroundColor = Color.parseColor(val);
if ((val = xrp.getAttributeValue(null, "second")) != null)
pal.mSecondColor = Color.parseColor(val);
if ((val = xrp.getAttributeValue(null, "minute")) != null)
pal.mMinuteColor = Color.parseColor(val);
if ((val = xrp.getAttributeValue(null, "hour")) != null)
pal.mHourColor = Color.parseColor(val);
if ((val = xrp.getAttributeValue(null, "day")) != null)
pal.mDayColor = Color.parseColor(val);
if ((val = xrp.getAttributeValue(null, "month")) != null)
pal.mMonthColor = Color.parseColor(val);
return (pal.mId == null) ? null : pal;
}
@Override
public int getBackgroundColor() {
return mBackgroundColor;
}
@Override
public int getSecondColor(float forAngle) {
return mSecondColor;
}
@Override
public int getMinuteColor(float forAngle) {
return mMinuteColor;
}
@Override
public int getHourColor(float forAngle) {
return mHourColor;
}
@Override
public int getDayColor(float forAngle) {
return mDayColor;
}
@Override
public int getMonthColor(float forAngle) {
return mMonthColor;
}
@Override
public String getId() {
return mId;
}
}
static class CyclingClockPalette extends ClockPalette {
protected String mId;
protected int mBackgroundColor;
protected float mSaturation;
protected float mBrightness;
private static final int COLORS_CACHE_COUNT = 720;
private final int[] mColors = new int[COLORS_CACHE_COUNT];
private static CyclingClockPalette sFallbackPalette = null;
public static CyclingClockPalette getFallback() {
if (sFallbackPalette == null) {
sFallbackPalette = new CyclingClockPalette();
sFallbackPalette.mId = "default_c";
sFallbackPalette.mBackgroundColor = Color.WHITE;
sFallbackPalette.mSaturation = 0.8f;
sFallbackPalette.mBrightness = 0.9f;
sFallbackPalette.computeIntermediateColors();
}
return sFallbackPalette;
}
private CyclingClockPalette() { }
private void computeIntermediateColors() {
final int[] colors = mColors;
final int count = colors.length;
float invCount = 1.0f / (float) COLORS_CACHE_COUNT;
for (int i = 0; i < count; i++) {
colors[i] = Color.HSBtoColor(i * invCount, mSaturation, mBrightness);
}
}
public static ClockPalette parseXmlPaletteTag(XmlResourceParser xrp) {
final CyclingClockPalette pal = new CyclingClockPalette();
pal.mId = xrp.getAttributeValue(null, "id");
String val;
if ((val = xrp.getAttributeValue(null, "background")) != null)
pal.mBackgroundColor = Color.parseColor(val);
if ((val = xrp.getAttributeValue(null, "saturation")) != null)
pal.mSaturation = Float.parseFloat(val);
if ((val = xrp.getAttributeValue(null, "brightness")) != null)
pal.mBrightness = Float.parseFloat(val);
if (pal.mId == null) {
return null;
} else {
pal.computeIntermediateColors();
return pal;
}
}
@Override
public int getBackgroundColor() {
return mBackgroundColor;
}
@Override
public int getSecondColor(float forAngle) {
if (forAngle >= 1.0f || forAngle < 0.0f) forAngle = 0.0f;
return mColors[((int) (forAngle * COLORS_CACHE_COUNT))];
}
@Override
public int getMinuteColor(float forAngle) {
if (forAngle >= 1.0f || forAngle < 0.0f) forAngle = 0.0f;
return mColors[((int) (forAngle * COLORS_CACHE_COUNT))];
}
@Override
public int getHourColor(float forAngle) {
if (forAngle >= 1.0f || forAngle < 0.0f) forAngle = 0.0f;
return mColors[((int) (forAngle * COLORS_CACHE_COUNT))];
}
@Override
public int getDayColor(float forAngle) {
if (forAngle >= 1.0f || forAngle < 0.0f) forAngle = 0.0f;
return mColors[((int) (forAngle * COLORS_CACHE_COUNT))];
}
@Override
public int getMonthColor(float forAngle) {
if (forAngle >= 1.0f || forAngle < 0.0f) forAngle = 0.0f;
return mColors[((int) (forAngle * COLORS_CACHE_COUNT))];
}
@Override
public String getId() {
return mId;
}
}
配置ClockDelete 的xml
/xml/polar_clock_palettes
<?xml version="1.0" encoding="utf-8"?>
<palettes>
<!-- basic white polar clock with color-cycling rings -->
<palette kind="cycling"
id="palette_white_c"
background="#FFFFFFFF"
saturation="0.8" brightness="0.9" />
<!-- same as above, but on black -->
<palette kind="cycling"
id="palette_black_c"
background="#FF000000"
saturation="1.0" brightness="0" />
<!-- a nice gray color scheme with an orange seconds ring -->
<palette kind="fixed"
id="palette_gray"
background="#FF555555"
second="#FFCC8800" minute="#FF333333" hour="#FF000000"
day="#FF999999" month="#FF777777" />
<!-- smooth black with a purple cast -->
<palette kind="fixed"
id="palette_violet"
background="#FF181020"
second="#FF602070" minute="#603050" hour="#FF040008"
day="#FF706080" month="#FF403050" />
<!-- green on black -->
<palette kind="fixed"
id="palette_matrix"
background="#FF002200"
second="#FF00FF00" minute="#FF00BB00" hour="#FF007700"
day="#FF00AA00" month="#FF006600" />
<!-- orange -->
<palette kind="fixed"
id="palette_halloween"
background="#FF884400"
second="#FFFFFF00" minute="#FFFF8800" hour="#FFFF8800"
day="#FF000000" month="#FF000000" />
<!-- based on Zenburn by Jani Nurminen -->
<palette kind="fixed"
id="palette_zenburn"
background="#FF3f3f3f"
second="#FF8aCCCF" minute="#FFCB9292" hour="#FFCCDC90"
day="#FFDCA3A3" month="#FF7f9f7f" />
<!-- electric blue -->
<palette kind="fixed"
id="palette_oceanic"
background="#FF000066"
second="#FF6666FF" minute="#FF0000FF" hour="#FF0000AA"
day="#FF000033" month="#FF000011" />
</palettes>