有点太晚了,但这是我几年前在 Solid Launcher 中实现 shuch 功能的方式。它可能并不完美,但它会给你一个关于主题如何工作的基本解释。
主题引擎.java:
package com.majeur.launcher.data;
import android.app.Application;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.Log;
import com.majeur.launcher.R;
import com.majeur.launcher.preference.PreferenceHelper;
import com.majeur.launcher.util.BitmapUtils;
import com.majeur.launcher.util.Constants;
import com.majeur.launcher.util.Matrix;
import com.majeur.util.ArrayUtils;
import org.xmlpull.v1.XmlPullParser;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
/**
* Created by MajeurAndroid on 10/10/14.
*/
public class ThemeEngine {
private static final String TAG = ThemeEngine.class.getSimpleName();
private static final String CATEGORY_APEX_THEME = "com.anddoes.launcher.THEME";
private static final String COMPONENT = "component";
private static final String RES_DRAWABLE = "drawable";
private static final String RES_XML = "xml";
private static final String RES_BOOL = "bool";
private static final String RES_ARRAY = "array";
private static final String APPFILTER = "appfilter";
private static final String ICONS = "icon_pack";
private static final String ATTR_SUPPORT_ICON_PACK = "config_iconpack";
private static final String ATTR_ITEM = "item";
private static final String ATTR_SCALE = "scale";
private static final String ATTR_FACTOR = "factor";
private static final String ATTR_ICON_BACK = "iconback";
private static final String ATTR_ICON_MASK = "iconmask";
private static final String ATTR_ICON_UPON = "iconupon";
private static final String[] ATTR_IMGS = {"img1", "img2", "img3", "img4", "img5"};
private static final Matrix sMatrix = new Matrix();
private Application mApplication;
private boolean mIsOperational;
private int mIconSize;
private List<ComponentName> mAppFilterComponentNames = new ArrayList<ComponentName>();
private List<String> mAppFilterDrawableStrings = new ArrayList<String>();
private List<String> mAppFilterIconsBack = new ArrayList<String>(5);
private List<String> mAppFilterIconsMask = new ArrayList<String>(5);
private List<String> mAppFilterIconsUpon = new ArrayList<String>(5);
private float mAppFilterScaleFactor = 1f;
private boolean mSupportIconBack;
private boolean mSupportIconMask;
private boolean mSupportIconUpon;
private boolean mMultipleIconBack;
private boolean mMultipleIconMask;
private boolean mMultipleIconUpon;
private Resources mIconPackResources;
private String mIconPackPackageName;
private Random mRandom = new Random();
private Paint mPaint;
public ThemeEngine(Application application) {
mApplication = application;
mIconSize = application.getResources().getDimensionPixelSize(R.dimen.workspace_item_icon_size);
initializeIconPack();
}
public boolean assertPackageIsCompatible(String iconPackPackageName) {
List<IconPackRetriever.IconPackInfo> iconPackInfoList = new IconPackRetriever(mApplication).loadIconPacksInfo();
for (IconPackRetriever.IconPackInfo iconPackInfo : iconPackInfoList)
if (TextUtils.equals(iconPackInfo.packageName, iconPackPackageName))
return true;
return false;
}
public Bitmap getIconInPack(Resources iconPackResources, String pkgName, String resName) {
int id = iconPackResources.getIdentifier(resName, RES_DRAWABLE, pkgName);
return id != 0 ? BitmapFactory.decodeResource(iconPackResources, id,
BitmapUtils.getOptimalBitmapOptions(iconPackResources, id, mIconSize)) : null;
}
public Bitmap getSpecialIcon(String iconPackPackage, String iconResName) {
Resources localResources;
try {
localResources = mApplication
.createPackageContext(iconPackPackage, Context.CONTEXT_IGNORE_SECURITY)
.getResources();
} catch (PackageManager.NameNotFoundException e) {
return null;
}
int id = localResources.getIdentifier(iconResName, RES_DRAWABLE, iconPackPackage);
return id != 0 ?
BitmapFactory.decodeResource(localResources, id, BitmapUtils.getOptimalBitmapOptions(localResources, id, mIconSize))
: null;
}
public String getIconPackPackageName() {
return PreferenceHelper.preferences().getString(Constants.PREF_ICON_PACK_PKG_NAME, null);
}
public void setIconPack(String packageName) {
if (packageName == null)
PreferenceHelper.preferences()
.edit()
.remove(Constants.PREF_ICON_PACK_PKG_NAME)
.apply();
else {
PreferenceHelper.preferences()
.edit()
.putString(Constants.PREF_ICON_PACK_PKG_NAME, packageName)
.apply();
}
initializeIconPack();
}
public boolean isOperational() {
return mIsOperational;
}
private void initializeIconPack() {
try {
prepareIconPackOrThrow();
mIsOperational = true;
} catch (Exception e) {
//e.printStackTrace();
PreferenceHelper.preferences()
.edit()
.remove(Constants.PREF_ICON_PACK_PKG_NAME)
.apply();
mIsOperational = false;
}
}
private void prepareIconPackOrThrow() throws Exception {
mAppFilterComponentNames.clear();
mAppFilterDrawableStrings.clear();
mAppFilterIconsBack.clear();
mAppFilterIconsMask.clear();
mAppFilterIconsUpon.clear();
Context localContext;
mIconPackPackageName = getIconPackPackageName();
if (mIconPackPackageName == null)
throw new NullPointerException("Icon pack packageName is null");
// throws NameNotFoundException
localContext = mApplication.createPackageContext(mIconPackPackageName, Context.CONTEXT_IGNORE_SECURITY);
mIconPackResources = localContext.getResources();
int identifier = mIconPackResources.getIdentifier(APPFILTER, RES_XML, mIconPackPackageName);
// can throw InvalidResIdException if id is 0 (eg. xml doesn't exist)
XmlPullParser appFilterPullParser = mIconPackResources.getXml(identifier);
// throws XmlPullParserException and IOException
int eventType = appFilterPullParser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG) {
String name = appFilterPullParser.getName();
switch (name) {
case ATTR_ITEM:
String component = appFilterPullParser.getAttributeValue(null, COMPONENT);
String drawableName = appFilterPullParser.getAttributeValue(null, RES_DRAWABLE);
if (component != null && drawableName != null) {
try {
ComponentName cn = getComponentNameFromXmlAttribute(component);
mAppFilterComponentNames.add(cn);
mAppFilterDrawableStrings.add(drawableName);
} catch (StringIndexOutOfBoundsException e) {
Log.w(TAG, "Invalid flatten ComponentName: " + component);
}
}
break;
case ATTR_ICON_BACK:
for (String attribute : ATTR_IMGS) {
String s = appFilterPullParser.getAttributeValue(null, attribute);
if (s != null) {
mAppFilterIconsBack.add(s);
}
}
break;
case ATTR_ICON_MASK:
for (String attribute : ATTR_IMGS) {
String s = appFilterPullParser.getAttributeValue(null, attribute);
if (s != null)
mAppFilterIconsMask.add(s);
}
break;
case ATTR_ICON_UPON:
for (String attribute : ATTR_IMGS) {
String s = appFilterPullParser.getAttributeValue(null, attribute);
if (s != null)
mAppFilterIconsUpon.add(s);
}
break;
case ATTR_SCALE:
String s = appFilterPullParser.getAttributeValue(null, ATTR_FACTOR);
if (s != null)
mAppFilterScaleFactor = Float.parseFloat(s);
break;
}
}
eventType = appFilterPullParser.next();
}
mSupportIconBack = mAppFilterIconsBack.size() > 0;
mSupportIconMask = mAppFilterIconsMask.size() > 0;
mSupportIconUpon = mAppFilterIconsUpon.size() > 0;
mMultipleIconBack = mAppFilterIconsBack.size() > 1;
mMultipleIconMask = mAppFilterIconsMask.size() > 1;
mMultipleIconUpon = mAppFilterIconsUpon.size() > 1;
setPaints();
}
private ComponentName getComponentNameFromXmlAttribute(String xmlAttribute) {
String s = xmlAttribute.substring(14, xmlAttribute.length() - 1);
return ComponentName.unflattenFromString(s);
}
private void setPaints() {
mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
mPaint.setAntiAlias(true);
}
private boolean isIconAvailable(ComponentName cn) {
return mAppFilterComponentNames.contains(cn);
}
/**
* Return themed icon if any, else return null
*
* @param componentName Name of the activity represented by the item
* @return Themed icon if any available
*/
public Bitmap peekIconBitmap(ComponentName componentName, int iconSize) {
if (isIconAvailable(componentName)) {
int index = mAppFilterComponentNames.indexOf(componentName);
int resId = mIconPackResources.getIdentifier(mAppFilterDrawableStrings.get(index), RES_DRAWABLE, mIconPackPackageName);
// Return prebuilt icon
if (resId != 0)
return BitmapFactory.decodeResource(mIconPackResources, resId, BitmapUtils.getOptimalBitmapOptions(mIconPackResources, resId, iconSize));
}
return null;
}
/**
* Entry point for requesting app/shortcut icon
*
* @param defaultBitmap Default bitmap
* @return Themed icon
*/
public Bitmap loadIconBitmap(Bitmap defaultBitmap) {
return getThemedBitmap(defaultBitmap);
}
/**
* Entry point for requesting app/shortcut icon
*
* @param defaultBitmap Default bitmap
* @return Themed icon
*/
public Bitmap loadShortcutBitmap(Bitmap defaultBitmap) {
return getThemedBitmap(defaultBitmap);
}
// Themes an icon, used if applicationIcon is not supported by icon pack or for shortcut icons
private Bitmap getThemedBitmap(Bitmap appIcon) {
Bitmap iconBack = null;
if (mSupportIconBack) {
String iconBackName;
if (mMultipleIconBack) {
iconBackName = randItem(mAppFilterIconsBack);
} else iconBackName = mAppFilterIconsBack.get(0);
int iconBackId = mIconPackResources.getIdentifier(iconBackName, RES_DRAWABLE, mIconPackPackageName);
if (iconBackId != 0)
iconBack = BitmapFactory.decodeResource(mIconPackResources, iconBackId, BitmapUtils.getOptimalBitmapOptions(mIconPackResources, iconBackId, mIconSize));
}
Bitmap iconUpon = null;
if (mSupportIconUpon) {
String iconUponName;
if (mMultipleIconUpon) {
iconUponName = randItem(mAppFilterIconsUpon);
} else iconUponName = mAppFilterIconsUpon.get(0);
int iconUponId = mIconPackResources.getIdentifier(iconUponName, RES_DRAWABLE, mIconPackPackageName);
if (iconUponId != 0)
iconUpon = BitmapFactory.decodeResource(mIconPackResources, iconUponId, BitmapUtils.getOptimalBitmapOptions(mIconPackResources, iconUponId, mIconSize));
}
Bitmap iconMask = null;
if (mSupportIconMask) {
String iconMaskName;
if (mMultipleIconMask) {
iconMaskName = randItem(mAppFilterIconsMask);
} else iconMaskName = mAppFilterIconsMask.get(0);
int iconMaskId = mIconPackResources.getIdentifier(iconMaskName, RES_DRAWABLE, mIconPackPackageName);
if (iconMaskId != 0)
iconMask = BitmapFactory.decodeResource(mIconPackResources, iconMaskId, BitmapUtils.getOptimalBitmapOptions(mIconPackResources, iconMaskId, mIconSize));
}
Bitmap resultBitmap = Bitmap.createBitmap(mIconSize, mIconSize, Bitmap.Config.ARGB_8888);
Canvas resultCanvas = new Canvas(resultBitmap);
if (iconBack != null) {
resultCanvas.drawBitmap(iconBack, getResizeMatrix(iconBack, mIconSize, mIconSize), mPaint);
iconBack.recycle();
}
int targetSize = ((int) (mIconSize * mAppFilterScaleFactor));
int offset = (mIconSize / 2) - (targetSize / 2);
if (iconMask != null) {
Bitmap tempBitmap = Bitmap.createBitmap(mIconSize, mIconSize, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(tempBitmap);
canvas.drawBitmap(appIcon, getResizeTranslateMatrix(appIcon, targetSize, targetSize, offset, offset), mPaint);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
canvas.drawBitmap(iconMask, getResizeMatrix(iconMask, mIconSize, mIconSize), mPaint);
mPaint.setXfermode(null);
iconMask.recycle();
resultCanvas.drawBitmap(tempBitmap, 0, 0, mPaint);
tempBitmap.recycle();
} else {
resultCanvas.drawBitmap(appIcon, getResizeTranslateMatrix(appIcon, targetSize, targetSize, offset, offset), mPaint);
}
if (iconUpon != null) {
resultCanvas.drawBitmap(iconUpon, getResizeMatrix(iconUpon, mIconSize, mIconSize), mPaint);
iconUpon.recycle();
}
return resultBitmap;
}
private <T> T randItem(List<T> list) {
return list.get(mRandom.nextInt(list.size()));
}
private Matrix getResizeMatrix(Bitmap bm, int newWidth, int newHeight) {
return getResizeTranslateMatrix(bm, newWidth, newHeight, 0, 0);
}
private Matrix getResizeTranslateMatrix(Bitmap bm, int newWidth, int newHeight, float dx, float dy) {
int width = bm.getWidth();
int height = bm.getHeight();
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
sMatrix.reset();
sMatrix.postScale(scaleWidth, scaleHeight);
sMatrix.postTranslate(dx, dy);
return sMatrix;
}
public static class IconPackRetriever {
private Context mContext;
private PackageManager mPackageManager;
public static IconPackRetriever newInstance(Context context) {
return new IconPackRetriever(context);
}
private IconPackRetriever(Context context) {
mContext = context;
mPackageManager = context.getPackageManager();
}
public List<IconPackInfo> loadIconPacksInfo() {
final Intent intent = new Intent(Intent.ACTION_MAIN, null);
intent.addCategory(CATEGORY_APEX_THEME);
List<ResolveInfo> resolveInfoList = mPackageManager.queryIntentActivities(intent, 0);
List<IconPackInfo> iconPackInfoList = new LinkedList<>();
for (ResolveInfo resolveInfo : resolveInfoList) {
if (supportsIconPack(resolveInfo)) {
IconPackInfo iconPackInfo = new IconPackInfo();
iconPackInfo.packageName = resolveInfo.activityInfo.packageName;
iconPackInfo.icon = resolveInfo.activityInfo.loadIcon(mPackageManager);
iconPackInfo.label = resolveInfo.activityInfo.loadLabel(mPackageManager).toString();
iconPackInfoList.add(iconPackInfo);
}
}
return iconPackInfoList;
}
private boolean supportsIconPack(ResolveInfo resolveInfo) {
Resources localResources;
try {
localResources = mContext
.createPackageContext(resolveInfo.activityInfo.packageName, Context.CONTEXT_IGNORE_SECURITY)
.getResources();
} catch (PackageManager.NameNotFoundException e) {
return false;
}
int id = localResources.getIdentifier(ATTR_SUPPORT_ICON_PACK, RES_BOOL, resolveInfo.activityInfo.packageName);
return id != 0 && localResources.getBoolean(id);
}
public String[] getIconNamesForPack(String packageName) {
Resources localResources;
try {
localResources = mContext
.createPackageContext(packageName, Context.CONTEXT_IGNORE_SECURITY)
.getResources();
} catch (PackageManager.NameNotFoundException e) {
return null;
}
int id = localResources.getIdentifier(ICONS, RES_ARRAY, packageName);
return id != 0 ? assertedArray(packageName, localResources.getStringArray(id)) : null;
}
private String[] assertedArray(String iconPackPackageName, String[] drawableNames) {
Resources localResources;
try {
localResources = mContext.createPackageContext(iconPackPackageName, Context.CONTEXT_IGNORE_SECURITY)
.getResources();
} catch (PackageManager.NameNotFoundException e) {
return null;
}
List<String> list = ArrayUtils.asList(drawableNames);
Iterator<String> iterator = list.iterator();
while (iterator.hasNext())
if (localResources.getIdentifier(iterator.next(), RES_DRAWABLE, iconPackPackageName) == 0)
iterator.remove();
return list.toArray(new String[list.size()]);
}
public static class IconPackInfo {
public String packageName;
public Drawable icon;
public String label;
}
}
}
或在这里:
https ://gist.github.com/MajeurAndroid/a51869e826b9a283a173b65e923857f8