主题皮肤切换

1. 一个Icon图标支持两种配色

1.1 通过已有的图片处理成变色图片

方案就是作图的时候控制显示内容的透明度,

第一步:

Bitmap mColorBitmap = Bitmap.createBitmap(mBitmap.getWidth(), mBitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas mCanvas = new Canvas(mColorBitmap);
Paint mPaint = new Paint();
mPaint.setColor(mColor);  

第二步:

//从原位图中提取只包含alpha的位图
Bitmap alphaBitmap = mBitmap.extractAlpha();
//在画布上通过透明度的部分控制显示颜色
mCanvas.drawBitmap(alphaBitmap, 0, 0, mPaint);

ps:使用把图片做成字体,这样可以直接setTextColor
其实这和第二种的原理是相同的,只不过是UI和Android系统帮我们做了

1.2 ImageBitmap 和倍率的关系

如果不设置Density 直接把一个bitmap 设置到ImageVIew上去 就是默认density。
但是可能图片都是三倍图,这时候如果设置到2倍分辩率的手机上就会出现缩小的情况,这时候就要设置图片的density。
bitmap.setDensity(480);
然后再设置到imageView上去

关于图片的缩放系数:http://jayfeng.com/2016/03/22/Android-Bitmap%E9%9D%A2%E9%9D%A2%E8%A7%82/

2.一个轻量级的换肤方案

https://github.com/hongyangAndroid/AndroidChangeSkin

原理是定义可替换的皮肤时候带上一个后缀

<color name="bg_theme">#07172d</color>
<color name="bg_theme_light">@color/color_eeeeee</color>   

对于自定义drawble和图片资源也是一样

discover_icon.png   
discover_icon_light.png  

使用时候:

<ImageView
android:drawableTop="@drawable/discover_icon"
android:tag="skin:drawableTop:drawableTop|skin:tab_color:textColor"
/>

原理每次换肤时候遍历所有的activity,找出所有有skin标签的控件

private void notifyChangedListeners() {

    long time = System.currentTimeMillis();

    LinkedList<Activity> activityStack = LycApplication.getInstance().getActivityStack();

    for (int i = 0, size = activityStack.size(); i < size; i++) {
        if (null != activityStack.get(i)) {
            Activity act= activityStack.get(i);
            if (act instanceof StoryGroundActivity || act instanceof ReviewActivity
                    || act instanceof CommentActivity) {
                apply(activityStack.get(i));
            }
        }
    }

    ELog.w(" Notify ALL SKIN COST: ", (System.currentTimeMillis() - time));
}

找每个元素的过程:

1.递归遍历所有的子元素

public static SkinView getSkinView(View view) {
    Object tag = view.getTag(R.id.skin_tag_id);
    if (tag == null) {
        tag = view.getTag();
    }

    if (tag == null)
        return null;

    if (!(tag instanceof String))
        return null;

    String tagStr = (String) tag;

    List<SkinAttr> skinAttrs = parseTag(tagStr);
    if (!skinAttrs.isEmpty()) {
        changeViewTag(view);
        return new SkinView(view, skinAttrs);
    }
    return null;
}

2.解析出每个SkinView里面的自定义主题 SkinAttr

    private static List<SkinAttr> parseTag(String tagStr) {
    List<SkinAttr> skinAttrs = new ArrayList<SkinAttr>();
    if (TextUtils.isEmpty(tagStr)) return skinAttrs;

    String[] items = tagStr.split("[|]");
    for (String item : items) {
        if (!item.startsWith(SkinConfig.SKIN_PREFIX))
            continue;
        String[] resItems = item.split(":");
        if (resItems.length != 3)
            continue;

        String resName = resItems[1];
        String resType = resItems[2];

        SkinAttrType attrType = getSupprotAttrType(resType);
        if (attrType == null) continue;
        SkinAttr attr = new SkinAttr(attrType, resName);
        skinAttrs.add(attr);
    }
    return skinAttrs;
}

3.应用新的主题

SkinView apply主题的方法:

public void apply() {
    if (view == null) return;

    for (SkinAttr attr : attrs) {
        attr.apply(view);
    }
}

SkinAttr 具体设置主题的方法

HintCOLOR("textColorHint") {
    @Override
    public void apply(View view, String resName) {
        ColorStateList colorlist = getResourceManager().getColorStateList(resName);
        if (colorlist == null)
            return;

        ((EditText) view).setHintTextColor(colorlist);
    }
},

ps: SkinAttrType写的方式很不错,值得学习

踩坑笔录

  • 区别drawable 是color 还是 drawable:
 BACKGROUND("background") {
    @Override
    public void apply(View view, String resName) {

        String targetResName = getResourceManager().appendSuffix(resName);
        Resources mResources=view.getContext().getResources();
        try {
            int colorResult= mResources.getColor(mResources.getIdentifier(targetResName, "color", view.getContext().getPackageName()));
            view.setBackgroundColor(colorResult);
        }catch ( Exception e1){
            try {
                int resId=mResources.getIdentifier(targetResName, "drawable", view.getContext().getPackageName());
                Drawable drawableResult= mResources.getDrawable(resId);
                if (drawableResult == null)
                    return;
                view.setBackgroundResource(resId);
            }catch (Exception e2){
                ELog.e("not found:"+targetResName);
            }
        }


    }
},
  • SkinManager 没有保存SkinView的引用

实际上每次应用主题时候 都是遍历存在Application的Activity列表然后find 每个SkinView 然后应用主题皮肤