android插件式换肤核心实现

文章讲述了如何使用AppCompat和TintContextWrapper来处理Android中的主题应用,包括解析自定义属性如`android:theme`和`app:theme`,以及通过DeclaredOnClickListener实现点击事件的处理。还介绍了皮肤管理类,如SkinAttrSupport、SkinManager和SkinResource用于动态替换资源。
摘要由CSDN通过智能技术生成

}

if (readAndroidTheme || readAppTheme) {

// We then apply the theme on the context, if specified

context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);

}

if (wrapContext) {

context = TintContextWrapper.wrap(context);

}

View view = null;

// We need to ‘inject’ our tint aware Views in place of the standard framework versions

switch (name) {

case “TextView”:

view = new AppCompatTextView(context, attrs);

break;

case “ImageView”:

view = new AppCompatImageView(context, attrs);

break;

case “Button”:

view = new AppCompatButton(context, attrs);

break;

case “EditText”:

view = new AppCompatEditText(context, attrs);

break;

case “Spinner”:

view = new AppCompatSpinner(context, attrs);

break;

case “ImageButton”:

view = new AppCompatImageButton(context, attrs);

break;

case “CheckBox”:

view = new AppCompatCheckBox(context, attrs);

break;

case “RadioButton”:

view = new AppCompatRadioButton(context, attrs);

break;

case “CheckedTextView”:

view = new AppCompatCheckedTextView(context, attrs);

break;

case “AutoCompleteTextView”:

view = new AppCompatAutoCompleteTextView(context, attrs);

break;

case “MultiAutoCompleteTextView”:

view = new AppCompatMultiAutoCompleteTextView(context, attrs);

break;

case “RatingBar”:

view = new AppCompatRatingBar(context, attrs);

break;

case “SeekBar”:

view = new AppCompatSeekBar(context, attrs);

break;

}

if (view == null && originalContext != context) {

// If the original context does not equal our themed context, then we need to manually

// inflate it using the name so that android:theme takes effect.

view = createViewFromTag(context, name, attrs);

}

if (view != null) {

// If we have created a view, check its android:onClick

checkOnClickListener(view, attrs);

}

return view;

}

private View createViewFromTag(Context context, String name, AttributeSet attrs) {

if (name.equals(“view”)) {

name = attrs.getAttributeValue(null, “class”);

}

try {

mConstructorArgs[0] = context;

mConstructorArgs[1] = attrs;

if (-1 == name.indexOf(‘.’)) {

for (int i = 0; i < sClassPrefixList.length; i++) {

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 = 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 {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后我还整理了很多Android中高级的PDF技术文档。以及一些大厂面试真题解析文档。

image

Android高级架构师之路很漫长,一起共勉吧!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

}

public void skin(View view) {

mType.skin(view, mResName);

}

}

  • 获取想要的资源

/**

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

  • 获取想要的资源

*/

public enum SkinType {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-hR8MFabd-1713265043996)]

[外链图片转存中…(img-8xvdzbOQ-1713265043998)]

[外链图片转存中…(img-wixeJj76-1713265043999)]

[外链图片转存中…(img-O22dJLLT-1713265044000)]

[外链图片转存中…(img-sftkDDCb-1713265044001)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后我还整理了很多Android中高级的PDF技术文档。以及一些大厂面试真题解析文档。

[外链图片转存中…(img-iFVnbHuK-1713265044003)]

Android高级架构师之路很漫长,一起共勉吧!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值