Android换肤初探(一)

android换肤:主要是将资源文件,包括R.drawable.xx,R.mipamap.xx,R.color.xx更换为皮肤包中的资源。

  1. Android中对控件的资源设置,如下一个简单例子:
 btnSettings.setImageDrawable(SkinResources.getInstance().getDrawable(R.drawable.btn_setting_bg));
        mTabLayout.setBackgroundColor(SkinResources.getInstance().getColor(R.color.home_tab_bar_bg_clr));
<ImageView
        android:layout_width="wrap_content"
        android:src="@drawable/app_icon"
        android:layout_height="wrap_content" />

    // ImageView.java 解析属性
    final TypedArray a = context.obtainStyledAttributes(
                attrs, R.styleable.ImageView, defStyleAttr, defStyleRes);
    // 通过TypedArray获取图片
    final Drawable d = a.getDrawable(R.styleable.ImageView_src);
    if (d != null) {
      setImageDrawable(d);
    }

    // TypedArray.getDrawable() 方法
    public Drawable getDrawable(@StyleableRes int index) {
       // 省略部分代码....
       // 加载资源其实是通过mResources去获取的
       return mResources.loadDrawable(value, value.resourceId, mTheme);
    }

换肤要解决的问题:
(1)怎样把皮肤包资源替换掉系统的资源?
利用反射设置

  private Resources getSkinResources(String skinPath) {
        try {
            AssetManager assets = AssetManager.class.newInstance();
            Method addAssetPath = assets.getClass().getMethod("addAssetPath", String.class);
            addAssetPath.invoke(assets, skinPath);
            Resources superRes = mAppContext.getResources();
            Resources temp = new Resources(assets, superRes.getDisplayMetrics(), superRes.getConfiguration());
            return temp;

        } catch (Exception e) {
            Log.i("getSkinResources",e.getStackTrace()+"================="+e.getMessage());
            return null;
        }
    }

如果是apk皮肤包

 final Context context = createPackageContext(skinPackName, Context.CONTEXT_IGNORE_SECURITY);
//layout.setBackgroundDrawable(context.getResources().getDrawable(R.drawable.activity_bg));
            Resources resources = context.getResources();
            int indentify = resources.getIdentifier(context.getPackageName()+":drawable/activity_bg", null, null);
            if(indentify>0){
                layout.setBackgroundDrawable(context.getResources().getDrawable(indentify));
            }

(2)怎样去找到xml中需要换肤的view?
利用LayoutInflateFactory.onCreateView(View parent, String name, Context context, AttributeSet attrs) 方法
阅读源码setContentView(),
LayoutInflaterFactory在系统填充view前会回调该接口,你可以去自定义布局的填充;
LayoutInflater根据xml布局文件来渲染View视图的主要流程:
先是通过布局文件的资源ID创建一个XmlResourceParser解析器对象parser,
再是利用parser递归解析xml布局文件,然后根据解析出的标签名来创建相关View,最终返回层级视图View。
如果LayoutInflater中设置了Factory,那么在创建每一个View时都会调用该Factory的onCreateView()方法,
这个方法就是我们的入口点,如果想在每一个View创建之前做点处理,只需要在Factory的onCreateView()方法中做相关逻辑操作…
已经找到了创建View的切入口。
String name:是控件的名称,如TextView,Button等
AttributeSet attrs 保存了view的一些属性;
String attrName = attrs.getAttributeName(i); 如layout_width,textColor,background
String attrValue = attrs.getAttributeValue(i);是资源的id,如:@17170443

 // 注意需在调用super.onCreate(savedInstanceState);之前设置LayoutInflaterFactory
        //查看源码部分: super.onCreate(savedInstanceState);----installViewFactory----AppCompatDelegateImplV9,
        LayoutInflaterCompat.setFactory(getLayoutInflater(), getSkinCompatDelegate());
        super.onCreate(savedInstanceState);

关联关系:
LayoutInflaterFactory的onCreateView()方法与LayoutInflater.Factory的onCreateView()之间的关联?如果相通则问题解决。
第一层关系:LayoutInflater.Factory的onCreateView()方法实际上是由LayoutInflaterFactory的onCreateView()实现的。
第二层关系:LayoutInflaterCompatBase .setFactory(LayoutInflater inflater, LayoutInflaterFactory factory)
此处传给的LayoutInflaterFactory对象,通过对FactoryWrapper封装,而FactoryWrapper implements LayoutInflater.Factory,从而实现了,表面上看是设置LayoutInflaterFactory,本质上是对LayoutInflater.Factory设置,因此二者的联系建立。

class LayoutInflaterCompatBase {

    *static class FactoryWrapper implements LayoutInflater.Factory*{

        final LayoutInflaterFactory mDelegateFactory;

        FactoryWrapper(LayoutInflaterFactory delegateFactory) {
            mDelegateFactory = delegateFactory;
        }

        *@Override
        public View onCreateView(String name, Context context, AttributeSet attrs) {
            return mDelegateFactory.onCreateView(null, name, context, attrs);
        }*

        public String toString() {
            return getClass().getName() + "{" + mDelegateFactory + "}";
        }
    }

    static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
        inflater.setFactory(factory != null ? new FactoryWrapper(factory) : null);
    }

    static LayoutInflaterFactory getFactory(LayoutInflater inflater) {
        *LayoutInflater.Factory factory = inflater.getFactory();
        if (factory instanceof FactoryWrapper) {
            return ((FactoryWrapper) factory).mDelegateFactory;*
        }
        return null;
    }

}

至此明白:如果想要对LayoutInflater.Factory的onCreateView()方法里做一些操作,只需在外界通过setLayoutInflaterFactory,并覆写其onCreateView()方法即可。

LayoutInflaterCompat.setFactory(getLayoutInflater(), getSkinCompatDelegate());

public class SkinCompatDelegate  implements LayoutInflaterFactory{
    @Override
    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        View view = createView(parent, name, context, attrs);
        if (view == null) return null;

        /*mSkinViews搜集需要换肤的view集合*/
        if (view instanceof Skinnable) {
            mSkinViews.add(new WeakReference<>((Skinnable) view));
        }
        parseSkinAttrs(context, attrs, view);
        return view;
    }
}

(3)将xml中的view换肤。
a . 哪些是需要换肤的view?

 private static final String[] SKIN_ATTRS = {"background", "textColor", "src", "divider", "listSelector","textColorHint", "progressDrawable", "thumb", "drawableLeft", "drawableTop", "drawableRight","drawableBottom"};

  public static boolean isSupportAttrType(String attrName) {
      for (String name : SKIN_ATTRS) {
          if (name.equalsIgnoreCase(attrName)) return true;
      }
      return false;
  }

b. view中某一个属性的如何替换掉?
像R.drawable.text_color_selector
* typeName是text_color_selector;
* entryName是drawable;
* 然后通过这个名称获取该资源在皮肤包中的具体id

Resources res = context.getResources();
        for (int i = 0; i < attrs.getAttributeCount(); i++) {
            SkinAttr skinAttr = null;
            String attrName = attrs.getAttributeName(i);
            String attrValue = attrs.getAttributeValue(i);
            /*isSupportAttrType(attrName) 用来判断需要换肤的view的attr值*/
            if (SkinAttrSupport.isSupportAttrType(attrName)) {
                if (attrValue.startsWith("@")) {
                    try {
                        int id = Integer.parseInt(attrValue.substring(1));
                        /*R.drawable.text_color_selector
                        * typeName是text_color_selector;
                        * entryName是drawable;
                        * 然后通过这个名称获取该资源在皮肤包中的具体id
                        * /然后就是获取皮肤包中的资源id了
                           int drawavleId = apk.getResources().getIdentifier(resEntryName, resTypeName,apk.getPackageName());
                        * */
                        String typeName = res.getResourceTypeName(id);
                        String entryName = res.getResourceEntryName(id);

// 然后就是获取皮肤包中的资源id了
int drawavleId = apk.getResources().getIdentifier(resEntryName, resTypeName,apk.getPackageName());
//通过资源拿到图片(特指apk皮肤包)
Drawable drawable = SkinResources.getInstance().getDrawable(resId);
//给指定view设置
view.setBackgroundDrawable(drawable);

                    } catch (Exception e) {
                        skinAttr = null;
                    }
                }
            }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值