android插件式换肤核心实现,独家发布

final View view = createView(context, name, sClassPrefixList[i]);

if (view != null) {

return view;

}

}

return null;

} else {

return createView(context, name, null);

}

} catch (Exception e) {

// We do not want to catch these, lets return null and let the actual LayoutInflater

// try

return null;

} finally {

// Don’t retain references on context.

mConstructorArgs[0] = null;

mConstructorArgs[1] = null;

}

}

/**

  • android:onClick doesn’t handle views with a ContextWrapper context. This method

  • backports new framework functionality to traverse the Context wrappers to find a

  • suitable target.

*/

private void checkOnClickListener(View view, AttributeSet attrs) {

final Context context = view.getContext();

if (!(context instanceof ContextWrapper) ||

(Build.VERSION.SDK_INT >= 15 && !ViewCompat.hasOnClickListeners(view))) {

// Skip our compat functionality if: the Context isn’t a ContextWrapper, or

// the view doesn’t have an OnClickListener (we can only rely on this on API 15+ so

// always use our compat code on older devices)

return;

}

final TypedArray a = context.obtainStyledAttributes(attrs, sOnClickAttrs);

final String handlerName = a.getString(0);

if (handlerName != null) {

view.setOnClickListener(new SkinAppCompatViewInflater.DeclaredOnClickListener(view, handlerName));

}

a.recycle();

}

private View createView(Context context, String name, String prefix)

throws ClassNotFoundException, InflateException {

Constructor<? extends View> constructor = sConstructorMap.get(name);

try {

if (constructor == null) {

// Class not found in the cache, see if it’s real, and try to add it

Class<? extends View> clazz = context.getClassLoader().loadClass(

prefix != null ? (prefix + name) : name).asSubclass(View.class);

constructor = clazz.getConstructor(sConstructorSignature);

sConstructorMap.put(name, constructor);

}

constructor.setAccessible(true);

return constructor.newInstance(mConstructorArgs);

} catch (Exception e) {

// We do not want to catch these, lets return null and let the actual LayoutInflater

// try

return null;

}

}

/**

  • Allows us to emulate the {@code android:theme} attribute for devices before L.

*/

private static Context themifyContext(Context context, AttributeSet attrs,

boolean useAndroidTheme, boolean useAppTheme) {

final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.View, 0, 0);

int themeId = 0;

if (useAndroidTheme) {

// First try reading android:theme if enabled

themeId = a.getResourceId(R.styleable.View_android_theme, 0);

}

if (useAppTheme && themeId == 0) {

// …if that didn’t work, try reading app:theme (for legacy reasons) if enabled

themeId = a.getResourceId(R.styleable.View_theme, 0);

if (themeId != 0) {

Log.i(LOG_TAG, "app:theme is now deprecated. "

  • “Please move to using android:theme instead.”);

}

}

a.recycle();

if (themeId != 0 && (!(context instanceof ContextThemeWrapper)

|| ((ContextThemeWrapper) context).getThemeResId() != themeId)) {

// If the context isn’t a ContextThemeWrapper, or it is but does not have

// the same theme as we need, wrap it in a new wrapper

context = new ContextThemeWrapper(context, themeId);

}

return context;

}

/**

  • An implementation of OnClickListener that attempts to lazily load a

  • named click handling method from a parent or ancestor context.

*/

private static class DeclaredOnClickListener implements View.OnClickListener {

private final View mHostView;

private final String mMethodName;

private Method mResolvedMethod;

private Context mResolvedContext;

public DeclaredOnClickListener(@NonNull View hostView, @NonNull String methodName) {

mHostView = hostView;

mMethodName = methodName;

}

@Override

public void onClick(@NonNull View v) {

if (mResolvedMethod == null) {

resolveMethod(mHostView.getContext(), mMethodName);

}

try {

mResolvedMethod.invoke(mResolvedContext, v);

} catch (IllegalAccessException e) {

throw new IllegalStateException(

“Could not execute non-public method for android:onClick”, e);

} catch (InvocationTargetException e) {

throw new IllegalStateException(

“Could not execute method for android:onClick”, e);

}

}

@NonNull

private void resolveMethod(@Nullable Context context, @NonNull String name) {

while (context != null) {

try {

if (!context.isRestricted()) {

final Method method = context.getClass().getMethod(mMethodName, View.class);

if (method != null) {

mResolvedMethod = method;

mResolvedContext = context;

return;

}

}

} catch (NoSuchMethodException e) {

// Failed to find method, keep searching up the hierarchy.

}

if (context instanceof ContextWrapper) {

context = ((ContextWrapper) context).getBaseContext();

} else {

// Can’t search up the hierarchy, null out and fail.

context = null;

}

}

final int id = mHostView.getId();

final String idText = id == View.NO_ID ? “” : " with id '"

  • mHostView.getContext().getResources().getResourceEntryName(id) + “’”;

throw new IllegalStateException("Could not find method " + mMethodName

  • "(View) in a parent or ancestor Context for android:onClick "

  • "attribute defined on view " + mHostView.getClass() + idText);

}

}

}

  • 解析属性

@Override

public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {

//拦截到View的创建 获取View之后要去解析

//1.创建View

View view = createView(parent, name, context, attrs);

//2.解析属性 src textColor background 自定义属性

Log.d(“huangxiaoguo”, view + “----------”);

if (view != null) {

List skinAttrs = SkinAttrSupport.getSkinAttrs(context, attrs);

}

return view;

}

  • 皮肤属性解析的支持类

/**

  • Created by Administrator on 2018/8/3 0003.

  • 皮肤属性解析的支持类

*/

public class SkinAttrSupport {

/**

  • 获取SkinAttr的属性

  • @param context

  • @param attrs

  • @return

*/

public static List getSkinAttrs(Context context, AttributeSet attrs) {

// background src textColor

ArrayList skinAttrs = new ArrayList<>();

int attributeCount = attrs.getAttributeCount();

for (int i = 0; i < attributeCount; i++) {

//获取名称和值

String attrName = attrs.getAttributeName(i);

String attrValue = attrs.getAttributeValue(i);

Log.d(“huangxiaoguo”, “attrName—>” + attrName);

Log.d(“huangxiaoguo”, “attriValue—>” + attrValue);

//获取需要的资源

SkinType skinType = getSkinType(attrName);

if (skinType != null) {

//资源名称

String resName = geteResName(context, attrValue);

if (TextUtils.isEmpty(resName)) {

continue;

}

SkinAttr skinAttr = new SkinAttr(resName, skinType);

skinAttrs.add(skinAttr);

}

}

return skinAttrs;

}

/**

  • 获取资源名称

  • @param context

  • @param attrValue

  • @return

*/

private static String geteResName(Context context, String attrValue) {

if (attrValue.startsWith("@")) {

attrValue = attrValue.substring(1);

int resId = Integer.parseInt(attrValue);

return context.getResources().getResourceEntryName(resId);

}

return null;

}

/**

  • 通过名称获取SkinType

  • @param attrName

  • @return

*/

private static SkinType getSkinType(String attrName) {

SkinType[] skinTypes = SkinType.values();

for (SkinType skinType : skinTypes) {

if (skinType.getResName().equals(attrName)) {

return skinType;

}

}

return null;

}

}

  • SkinManager管理

@Override

public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {

//拦截到View的创建 获取View之后要去解析

//1.创建View

View view = createView(parent, name, context, attrs);

//2.解析属性 src textColor background 自定义属性

Log.d(“huangxiaoguo”, view + “----------”);

if (view != null) {

List skinAttrs = SkinAttrSupport.getSkinAttrs(context, attrs);

SkinView skinView = new SkinView(view, skinAttrs);

//3.统一交给SkinManager管理

managerSkinView(skinView);

}

return view;

}

/**

  • 统一管理SkinView

  • @param skinView

*/

protected void managerSkinView(SkinView skinView) {

List skinViews = SkinManager.getInstance().getSkinViews(this);

if (skinViews == null) {

skinViews = new ArrayList<>();

SkinManager.getInstance().register(this, skinViews);

}

skinViews.add(skinView);

}

  • 皮肤管理类

/**

  • Created by Administrator on 2018/8/3 0003.

  • 皮肤管理类

*/

public class SkinManager {

private static SkinManager mInstance;

private Context mContext;

private Map<Activity, List> mSkinViews = new HashMap<>();

private SkinResource mSkinResource;

static {

mInstance = new SkinManager();

}

public static SkinManager getInstance() {

return mInstance;

}

public void init(Context context) {

this.mContext = context.getApplicationContext();

}

/**

  • 加载皮肤

  • @param skinPath

  • @return

*/

public int loadSkin(String skinPath) {

//初始化资源管理

mSkinResource = new SkinResource(mContext, skinPath);

//改变皮肤

Set keys = mSkinViews.keySet();

for (Activity key : keys) {

List skinViews =

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享

mSkinViews.get(key);

for (SkinView skinView : skinViews) {

skinView.skin();

}

}

return 0;

}

/**

  • 恢复默认

  • @return

*/

public int restoreDefault() {

return 0;

}

/**

  • 获取SkinView

  • @param activity

  • @return

*/

public List getSkinViews(Activity activity) {

return mSkinViews.get(activity);

}

/**

  • 注册

  • @param activity

  • @param skinViews

*/

public void register(Activity activity, List skinViews) {

mSkinViews.put(activity, skinViews);

}

/**

  • 获取当前皮肤资源

  • @return

*/

public SkinResource getSkinResource() {

return mSkinResource;

}

}

  • 皮肤资源管理

/**

  • Created by Administrator on 2018/8/3 0003.

  • 皮肤资源管理

*/

public class SkinResource {

//资源获取

private Resources mSkinResource;

private String mPackageName;

public SkinResource(Context context, String skinPath) {

try {

//读取本地资源

Resources superRes = context.getResources();

//创建AssetManager

AssetManager asset = AssetManager.class.newInstance();

//添加本地下载好的资源皮肤 Native层

Method method = AssetManager.class.getDeclaredMethod(“addAssetPath”, String.class);

//允许方法私有

method.setAccessible(true);

//反射方法

method.invoke(asset, Environment.getExternalStorageDirectory().getAbsolutePath() +

File.separator + “red.skin”);

mSkinResource = new Resources(asset, superRes.getDisplayMetrics(), superRes.getConfiguration());

//获取报名

mPackageName = context.getPackageManager().getPackageArchiveInfo(skinPath, PackageManager.GET_ACTIVITIES)

.packageName;

Log.d(“huangxiaoguo”, “mPackageName===>” + mPackageName);

} catch (Exception e) {

e.printStackTrace();

}

}

/**

  • 通过名字获取Drawable

  • @param resName

  • @return

*/

public Drawable getDrawableByName(String resName) {

try {

int resId = mSkinResource.getIdentifier(resName, “drawable”, mPackageName);

Log.d(“huangxiaoguo”, “drawable===>” + resId);

Drawable drawable = mSkinResource.getDrawable(resId);

return drawable;

} catch (Exception e) {

e.printStackTrace();

return null;

}

}

/**

  • 通过名字获取颜色

  • @param resName

  • @return

*/

public ColorStateList getColorByName(String resName) {

try {

int resId = mSkinResource.getIdentifier(resName, “color”, mPackageName);

Log.d(“huangxiaoguo”, “color===>” + resId);

ColorStateList color = mSkinResource.getColorStateList(resId);

return color;

} catch (Exception e) {

e.printStackTrace();

return null;

}

}

}

  • 封装View和属性bean

public class SkinView {

private View mView;

private List mAttrs;

public SkinView(View view, List attrs) {

this.mView = view;

this.mAttrs = attrs;

}

public void skin() {

for (SkinAttr attr : mAttrs) {

attr.skin(mView);

}

}

}

  • 封装包名和要替换的内容

public class SkinAttr {

private String mResName;

private SkinType mType;

public SkinAttr(String resName, SkinType skinType) {

this.mResName=resName;

this.mType=skinType;

}

public void skin(View view) {

mType.skin(view, mResName);

}

}

  • 获取想要的资源

/**

  • Created by Administrator on 2018/8/3 0003.

  • 获取想要的资源

*/

public enum SkinType {

TEXT_COLOR(“textColor”) {

@Override

public void skin(View view, String resName) {

SkinResource skinResource = getSkinResource();

ColorStateList color = skinResource.getColorByName(resName);

if (color == null) {

return;

}

TextView textView = (TextView) view;

textView.setTextColor(color);

}

},

BACKGROUND(“background”) {

@SuppressLint(“NewApi”)

@Override

public void skin(View view, String resName) {

//背景可能是图片也可能是颜色

SkinResource skinResource = getSkinResource();

Drawable drawable = skinResource.getDrawableByName(resName);

if (drawable != null) {

view.setBackground(drawable);

return;

}

//颜色

ColorStateList color = skinResource.getColorByName(resName);

if (color != null) {

view.setBackgroundTintList(color);

}

}

},

SRC(“src”) {

@Override

public void skin(View view, String resName) {

//获取资源

SkinResource skinResource = getSkinResource();

Log.d(“huangxiaoguo”, “srcName—>” + resName);

Drawable drawable = skinResource.getDrawableByName(resName);

if (drawable != null) {

ImageView imageView = (ImageView) view;

imageView.setImageDrawable(drawable);

return;

}

}

};

//会根据名字调对应的方法

private String mResName;

SkinType(String resName) {

this.mResName = resName;

}

public abstract void skin(View view, String resName);

public String getResName() {

return mResName;

}

public SkinResource getSkinResource() {

return SkinManager.getInstance().getSkinResource();

}

}

  • 点击换肤

/**

  • 点击换肤

*/

public void skin(View view) {

String skinPath = Environment.getExternalStorageDirectory().getAbsolutePath()

  • File.separator + “red.skin”;

int result = SkinManager.getInstance().loadSkin(skinPath);

}

重新创建项目,编写一套资源文件,替换color,drawable等等资源,打包更改apk名称为red.skin放进相应的目录下即可

补充

  • 如何拦截布局(以LinearLayout为例)

在SkinAppCompatViewInflater中 createView添加

case “LinearLayout”:

view = new LinearLayout(context, attrs);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值