android-自定義換膚(1) https://blog.csdn.net/tiangaopan/article/details/104895134
前面說到本質上是通過實現LayoutInflater.Factory2,在onCreateView()方法中進行處理
這就需要我們找到對應的控件,控件又有系統控件和自定義控件,這裏我們先說系統控件,自定義後面會說處理方法
通過看源碼,我們可以找到系統控件在這三個包下,
private static final String[] mClassPrefixList = {"android.widget.", "android.view.", "android.webkit.",}
private static final Class[] mConstructorSignature = new Class[]{Context.class, AttributeSet.class};
private static final HashMap<String, Constructor<? extends View>> mConstructorMap = new HashMap<>();
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
//反射獲取
View view = createViewFromTag(name, context, attrs);
//自定義view
if (view == null) {
view = createView(name, context, attrs);
}
//篩選符合屬性的attrs
mSkinAttribute.load(view, attrs);
return view;
}
private View createViewFromTag(String name, Context context, AttributeSet attrs) {
//包含自定義控件
if (-1 != name.indexOf(".")) {
return null;
}
View view = null;
for (String s : mClassPrefixList) {
//mClassPrefixList[i] + name 組成全類名
view = createView(s + name, context, attrs);
if (view != null) {
break;
}
}
return view;
}
private View createView(String name, Context context, AttributeSet attrs) {
Constructor<? extends View> constructor = mConstructorMap.get(name);
if (constructor == null) {
try {
//通過全類名拿到class
Class<? extends View> aClass = context.getClassLoader().loadClass(name).asSubclass(View.class);
//獲取構造方法
constructor = aClass.getConstructor(mConstructorSignature);
mConstructorMap.put(name, constructor);
} catch (Exception e) {
e.printStackTrace();
}
}
if (constructor != null) {
try {
return constructor.newInstance(context, attrs);
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
對於已經通過反射獲取到構造方法的,可以使用map進行存儲,沒必要每次都進行一次獲取
public class SkinAttribute {
private static final List<String> sAttribute = new ArrayList<>();
static {
sAttribute.add("background");
sAttribute.add("src");
sAttribute.add("textColor");
sAttribute.add("drawableLeft");
sAttribute.add("drawableTop");
sAttribute.add("drawableRight");
sAttribute.add("drawableBottom");
}
private List<SkinView> skinViews = new ArrayList<>();
private Typeface mSkinTypeface;
public SkinAttribute(Typeface skinTypeface) {
mSkinTypeface = skinTypeface;
}
public void load(View view, AttributeSet attrs) {
List<SkinPain> skinPains = new ArrayList<>();
int attributeCount = attrs.getAttributeCount();
for (int i = 0; i < attributeCount; i++) {
//獲取屬性名字
String attributeName = attrs.getAttributeName(i);
if (sAttribute.contains(attributeName)) {
//獲取屬性值
String attributeValue = attrs.getAttributeValue(i);
//寫死的情況
if (attributeValue.startsWith("#")) {
continue;
}
int resId;
//系統自帶的 attributeValue = "?1313123"
if (attributeValue.startsWith("?")) {
int attrId = Integer.parseInt(attributeValue.substring(1));
//存在數組的可能
resId = SkinThemeUtils.getResId(view.getContext(), new int[]{attrId})[0];
} else {
//attributeValue = "@21321213"
resId = Integer.parseInt(attributeValue.substring(1));
}
if (resId != 0) {
SkinPain skinPain = new SkinPain(attributeName, resId);
skinPains.add(skinPain);
}
}
}
if (!skinPains.isEmpty() || view instanceof TextView || view instanceof SkinViewSupport) {
SkinView skinView = new SkinView(view, skinPains);
skinView.applySkin(mSkinTypeface);
skinViews.add(skinView);
}
}
public void applySkin() {
for (SkinView skinView : skinViews) {
skinView.applySkin(mSkinTypeface);
}
}
public void setTypeface(Typeface skinTypeface) {
this.mSkinTypeface = skinTypeface;
}
static class SkinView {
View view;
List<SkinPain> skinPains;
public SkinView(View view, List<SkinPain> skinPains) {
this.view = view;
this.skinPains = skinPains;
}
public void applySkin(Typeface typeface) {
applyTypeface(typeface);
applySkinSupport();
for (SkinPain skinPair : skinPains) {
Drawable left = null, top = null, right = null, bottom = null;
switch (skinPair.attributeName) {
case "background":
Object background = SkinResources.getInstance().getBackground(skinPair.resId);
//Color
if (background instanceof Integer) {
view.setBackgroundColor((Integer) background);
} else {
ViewCompat.setBackground(view, (Drawable) background);
}
break;
case "src":
background = SkinResources.getInstance().getBackground(skinPair.resId);
if (background instanceof Integer) {
((ImageView) view).setImageDrawable(new ColorDrawable((Integer)
background));
} else {
((ImageView) view).setImageDrawable((Drawable) background);
}
break;
case "textColor":
((TextView) view).setTextColor(SkinResources.getInstance().getColorStateList
(skinPair.resId));
break;
case "drawableLeft":
left = SkinResources.getInstance().getDrawable(skinPair.resId);
break;
case "drawableTop":
top = SkinResources.getInstance().getDrawable(skinPair.resId);
break;
case "drawableRight":
right = SkinResources.getInstance().getDrawable(skinPair.resId);
break;
case "drawableBottom":
bottom = SkinResources.getInstance().getDrawable(skinPair.resId);
break;
default:
break;
}
if (null != left || null != right || null != top || null != bottom) {
((TextView) view).setCompoundDrawablesWithIntrinsicBounds(left, top, right,
bottom);
}
}
}
//自定義view替換
private void applySkinSupport() {
if (view instanceof SkinViewSupport) {
((SkinViewSupport) view).applySkin();
}
}
//字體換膚
private void applyTypeface(Typeface typeface) {
if (view instanceof TextView) {
((TextView) view).setTypeface(typeface);
}
}
}
static class SkinPain {
String attributeName;
int resId;
public SkinPain(String attributeName, int resId) {
this.attributeName = attributeName;
this.resId = resId;
}
}
}
通過attributeName來知道是background,textcolor等屬性,通過resid可以知道是資源名,這時候去獲取資源包中的對應resid,即可實現相應的換膚
還有個需要注意的是,每個界面都應該進行相應的換膚,所以我們需要去實現Application.ActivityLifecycleCallbacks,這裏去進行加載和移除
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
//更新狀態欄
SkinThemeUtils.updateStatusBarColor(activity);
//更新字體
Typeface skinTypeface = SkinThemeUtils.getSkinTypeface(activity);
try {
LayoutInflater layoutInflater = LayoutInflater.from(activity);
Field mFactorySet = LayoutInflater.class.getDeclaredField("mFactorySet");
mFactorySet.setAccessible(true);
mFactorySet.setBoolean(layoutInflater, false);
SkinLayoutFactory skinLayoutFactory = new SkinLayoutFactory(activity, skinTypeface);
//添加自定義創建view 工廠
layoutInflater.setFactory2(skinLayoutFactory);
//註冊觀察者
SkinManager.getInstance().addObserver(skinLayoutFactory);
mFactoryMap.put(activity, skinLayoutFactory);
} catch (Exception e) {
e.printStackTrace();
}
}
在這裏我們對對應的setFactory2進行替換,有個需要注意的是mFactorySet這個屬性,如果是true的話會拋出異常,同時該變量是私有的,所以我們需要通過反射找到該變量將其更改爲false
public void setFactory2(Factory2 factory) {
if (mFactorySet) {
throw new IllegalStateException("A factory has already been set on this LayoutInflater");
}
if (factory == null) {
throw new NullPointerException("Given factory can not be null");
}
mFactorySet = true;
if (mFactory == null) {
mFactory = mFactory2 = factory;
} else {
mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
}
}