android-自定義換膚(2)

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);
        }
    }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章