换肤框架的搭建

这里写图片描述

首先所有皮肤的view——skinView:如ImageView

public class SkinView {

    private View mSkView;//ImageView
    private List<SkinAttr> mSkinAttrs;//src,backgroud

    public SkinView(View mSkView, List<SkinAttr> mSkinAttrs) {
        this.mSkView = mSkView;
        this.mSkinAttrs = mSkinAttrs;
    }
    public void skin(){
        for (SkinAttr skinAttr : mSkinAttrs) {
            skinAttr.skin(mSkView);
        }
    }
}

所有view的属性——SkinAttr

public class SkinAttr {
    private String mResName;//如资源的名字,meinv.jpg
    private SkinType mSkinType;//textColor,background

    public SkinAttr(String mResName, SkinType mSkinType) {
        this.mResName = mResName;
        this.mSkinType = mSkinType;
    }

    public void skin(View view) {
        mSkinType.skin(view,mResName);
    }
}

每个类型进行判断并设置

public enum SkinType {
    TEXT_COLO("textColor") {
        @Override
        public void skin(View view, String resName) {
            SkinResource resource = getSkinResource();
            ColorStateList color = resource.getColorByName(resName);
            if (color != null) {
                TextView tv = (TextView) view;
                tv.setTextColor(color);
            }
        }
    }, BACKGROUND("background") {
        @Override
        public void skin(View view, String resName) {
            SkinResource resource = getSkinResource();
            Drawable drawable = resource.getDrawableByName(resName);
            ImageView imageView = (ImageView) view;
            if (drawable != null) {
                imageView.setBackgroundDrawable(drawable);
                return;
            }
            //有可能是颜色
            ColorStateList color = resource.getColorByName(resName);
            if (color != null) {
                view.setBackgroundColor(color.getDefaultColor());
            }
        }
    }, SRC("src") {
        @Override
        public void skin(View view, String resName) {
            SkinResource resource = getSkinResource();
            Drawable drawable = resource.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);

    /**
     * 获得资源的名字(meinv.jpg)
     */
    public String getResName() {
        return mResName;
    }

    /**
     * 获得皮肤
     */
    public SkinResource getSkinResource() {
        return SkinManager.getInstance().getSkinResource();
    }
}

皮肤属性解析辅助类SkinAttrSupport

public class SkinAttrSupport {

    private static final String TAG = SkinAttrSupport.class.getSimpleName();

    public static List<SkinAttr> skin(Context context, AttributeSet attrs) {
        List<SkinAttr> skinAttrs = new ArrayList<>();
        int count = attrs.getAttributeCount();
        for (int index = 0; index < count; index++) {//layout_marginTop,----->10.0dip
            //Log.e(TAG, attrs.getAttributeName(index)+",----->"+attrs.getAttributeValue(index));

            //获得属性的名字和值
            String attrName = attrs.getAttributeName(index);
            String attrValue = attrs.getAttributeValue(index);

            SkinType skinType = getSkinType(attrName);

            if (skinType != null) {
                String resName = getResName(context, attrValue);
                //Log.e(TAG,resName);//image_src
                if(TextUtils.isEmpty(resName)){
                    continue;
                }
                SkinAttr skinAttr = new SkinAttr(resName, skinType);
                skinAttrs.add(skinAttr);
            }
        }
        return skinAttrs;
    }

    /**
     * 获得资源的名字
     */
    private static String getResName(Context context, String attrs) {//textSize=15
        if (attrs.startsWith("@")) {
            attrs = attrs.substring(1);//从1开始截取
            int resId = Integer.parseInt(attrs);
            return context.getResources().getResourceEntryName(resId);//设置的图片的资源的名字
        }
        return null;
    }

    /**
     * 获得属性的名字
     */
    private static SkinType getSkinType(String attrName) {
        SkinType[] skinTypes = SkinType.values();
        for (SkinType skinType : skinTypes) {
            if (skinType.getResName().equals(attrName)) {
                return skinType;
            }
        }
        return null;
    }
}

获取资源——skinResource

public class SkinResource {

    private Resources mSkinResource;
    private String mPackName;
    private String TAG=SkinResource.class.getSimpleName();

    public SkinResource(Context context, String skinPath) {
        Resources superResource = context.getResources();

        try {
            AssetManager assets = AssetManager.class.newInstance();//{@hide}

            Method method = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
            method.invoke(assets, skinPath);

            mSkinResource = new Resources
                    (assets, superResource.getDisplayMetrics(), superResource.getConfiguration());
            //获取包名
            mPackName = context.getPackageManager()
                    .getPackageArchiveInfo(skinPath, PackageManager.GET_ACTIVITIES).packageName;

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取资源
     */
    public Drawable getDrawableByName(String resName) {
        try {
            int resourceId = mSkinResource.getIdentifier(resName, "drawable", mPackName);
            //Log.e(TAG,resName+"——>mPackName"+mPackName);
            Drawable drawable = mSkinResource.getDrawable(resourceId);
            return drawable;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 获取颜色
     */
    public ColorStateList getColorByName(String resName) {
        try {
            int resourceId = mSkinResource.getIdentifier(resName, "drawable", mPackName);
            ColorStateList color = mSkinResource.getColorStateList(resourceId);
            return color;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

皮肤管理

public class SkinManager {
    private static final SkinManager mInstance;
    private Context mContext;
    private Map<Activity, List<SkinView>> mSkinView = new HashMap<>();
    private SkinResource mSkinResource;

    public void init(Context context) {
        mContext = context.getApplicationContext();
        String skinPath = SkinPreUtils.getInstance(context).getSkinPath();
        File file = new File(skinPath);
        if (!file.exists()) {
            SkinPreUtils.getInstance(context).clearSkinInfo();
            return;
        }
        //包名不存在
        String packageName = context.getPackageManager().getPackageArchiveInfo
                (skinPath, PackageManager.GET_ACTIVITIES).packageName;
        if (TextUtils.isEmpty(packageName)) {
            SkinPreUtils.getInstance(mContext).clearSkinInfo();
            return;
        }
        mSkinResource = new SkinResource(mContext, skinPath);
    }

    static {
        mInstance = new SkinManager();
    }

    public static SkinManager getInstance() {

        return mInstance;
    }

    //换肤
    public int loadSkin(String skinPath) {

        File file = new File(skinPath);
        if (!file.exists()) {
            return SkinConfig.SKIN_FILE_NOEXSIST;//文件不存在
        }
        //包名不存在
        String packageName = mContext.getPackageManager().getPackageArchiveInfo
                (skinPath, PackageManager.GET_ACTIVITIES).packageName;
        if (TextUtils.isEmpty(packageName)) {
            return SkinConfig.SKIN_FILE_ERROR;//不是一个apk
        }
        //如果当前是一样的就不换肤
        String currentPath = SkinPreUtils.getInstance(mContext).getSkinPath();
        if (currentPath.equals(skinPath)) {
            return SkinConfig.SKIN_CHANGE_NOTHING;
        }
        mSkinResource = new SkinResource(mContext, skinPath);
        changeSkin();
        //4.保存皮肤
        SkinPreUtils.getInstance(mContext).saveSkinPath(skinPath);
        return 0;
    }

    /**
     * 切换皮肤
     */
    private void changeSkin() {
        Set<Activity> keys = mSkinView.keySet();
        for (Activity key : keys) {
            List<SkinView> skinViews = mSkinView.get(key);
            for (SkinView skinView : skinViews) {
                skinView.skin();
            }
        }
    }

    //恢复默认
    public int restoreDefault() {
        String currentSkinPath = SkinPreUtils.getInstance(mContext).getSkinPath();
        if (TextUtils.isEmpty(currentSkinPath)) {
            return SkinConfig.SKIN_CHANGE_NOTHING;
        }

        //当前手机运行的路径
        String skinPath = mContext.getPackageResourcePath();
        mSkinResource = new SkinResource(mContext, skinPath);

        //改变皮肤
        changeSkin();

        //清空皮肤信息
        SkinPreUtils.getInstance(mContext).clearSkinInfo();
        return SkinConfig.SKIN_CHANGE_SUCCESS;
    }

    public List<SkinView> getSkinView(Activity activity) {
        return mSkinView.get(activity);
    }

    public SkinResource getSkinResource() {
        return mSkinResource;
    }

    public void register(Activity activity, List<SkinView> skinViews) {
        mSkinView.put(activity, skinViews);
    }

    /**
     * 检查是否需要切换
     *
     * @param skinView
     */
    public void checkChangeSkin(SkinView skinView) {
        String skinPath = SkinPreUtils.getInstance(mContext).getSkinPath();
        if (!TextUtils.isEmpty(skinPath)) {
            skinView.skin();
        }
    }

    public void unRegister(Activity activity) {
        mSkinView.remove(activity);
    }
}

拦截view

public abstract class BaseSkinActivity extends BaseActivity implements LayoutInflaterFactory {
    private SkinAppCompatViewInflater mAppCompatViewInflater;

    private String TAG = "BaseSkinActivity";
    private static final boolean IS_PRE_LOLLIPOP = Build.VERSION.SDK_INT < 21;
    //只能放一些通用的方法,基本每个activity都需要使用的方法
    // 如果是两个或两个以上的地方要使用,最好写一个工具类


    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        //拦截到View的创建  获取View之后去解析
        LayoutInflater layoutInflater = LayoutInflater.from(this);
        LayoutInflaterCompat.setFactory(layoutInflater, this);//会调用onCreateView方法

        super.onCreate(savedInstanceState);
    }

    public View onCreateView(View parent, final String name, @NonNull Context context,
                             @NonNull AttributeSet attrs) {
        //  //1.创建View
        final View view = createView(parent, name, context, attrs);
//        Log.e(TAG, view + "");
        if (view != null) {
            // 2. 解析属性  src  textColor  background  自定义属性

            // 2.1 一个activity的布局肯定对应多个这样的 SkinView
            List<SkinAttr> skinAttrs = SkinAttrSupport.skin(context, attrs);
            SkinView skinView = new SkinView(view, skinAttrs);

            // 3.统一交给SkinManager管理
            managerSkinView(skinView);
            //4.判断是否需要换肤
            SkinManager.getInstance().checkChangeSkin(skinView);

            return view;
        }
        return view;
    }

    protected void managerSkinView(SkinView skinView) {
        List<SkinView> skinViews = SkinManager.getInstance().getSkinView(this);
        if (skinViews == null) {
            skinViews = new ArrayList<>();
            SkinManager.getInstance().register(this, skinViews);
        }
        skinViews.add(skinView);
    }

    public View createView(View parent, final String name, @NonNull Context context,
                           @NonNull AttributeSet attrs) {
        if (mAppCompatViewInflater == null) {
            mAppCompatViewInflater = new SkinAppCompatViewInflater();
        }

        boolean inheritContext = false;
        if (IS_PRE_LOLLIPOP) {
            inheritContext = (attrs instanceof XmlPullParser)
                    // If we have a XmlPullParser, we can detect where we are in the layout
                    ? ((XmlPullParser) attrs).getDepth() > 1
                    // Otherwise we have to use the old heuristic
                    : shouldInheritContext((ViewParent) parent);
        }

        return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
                IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */
                true, /* Read read app:theme as a fallback at all times for legacy reasons */
                VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
        );
    }

    private boolean shouldInheritContext(ViewParent parent) {
        if (parent == null) {
            // The initial parent is null so just return false
            return false;
        }
        final View windowDecor = getWindow().getDecorView();
        while (true) {
            if (parent == null) {
                // Bingo. We've hit a view which has a null parent before being terminated from
                // the loop. This is (most probably) because it's the root view in an inflation
                // call, therefore we should inherit. This works as the inflated layout is only
                // added to the hierarchy at the end of the inflate() call.
                return true;
            } else if (parent == windowDecor || !(parent instanceof View)
                    || ViewCompat.isAttachedToWindow((View) parent)) {
                // We have either hit the window's decor view, a parent which isn't a View
                // (i.e. ViewRootImpl), or an attached view, so we know that the original parent
                // is currently added to the view hierarchy. This means that it has not be
                // inflated in the current inflate() call and we should not inherit the context.
                return false;
            }
            parent = parent.getParent();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        SkinManager.getInstance().unRegister(this);
    }
}

工具类封装
资源文件配置SkinConfig

public class SkinConfig {
    // SP的文件名称
    public static final String SKIN_INFO_NAME = "skinInfo";

    // 保存皮肤文件的路径的名称
    public static final String SKIN_PATH_NAME = "skinPath";
    // 不需要改变任何东西
    public static final int SKIN_CHANGE_NOTHING = -1;

    // 换肤成功
    public static final int SKIN_CHANGE_SUCCESS = 1;

    // 皮肤文件不存在
    public static final int SKIN_FILE_NOEXSIST = -2;

    // 皮肤文件有错误可能不是一个apk文件
    public static final int SKIN_FILE_ERROR = -3;
}

sp保存

public class SkinPreUtils {

    private static SkinPreUtils mInstance;
    private static Context mContext;

    private SkinPreUtils(Context context) {
        this.mContext = context.getApplicationContext();
    }

    public static SkinPreUtils getInstance(Context context) {
        if (mInstance == null) {
            synchronized (SkinPreUtils.class) {
                if (mInstance == null) {
                    mInstance = new SkinPreUtils(context);
                }
            }
        }
        return mInstance;
    }

    /**
     * 保存当前皮肤路径
     *
     * @param skinPath
     */
    public void saveSkinPath(String skinPath) {

        mContext.getSharedPreferences(SkinConfig.SKIN_INFO_NAME, Context.MODE_PRIVATE)
                .edit().putString(SkinConfig.SKIN_PATH_NAME, skinPath).commit();
    }

    /**
     * 获取皮肤的路径
     *
     * @return 当前皮肤路径
     */
    public String getSkinPath() {
        return mContext.getSharedPreferences(SkinConfig.SKIN_INFO_NAME, Context.MODE_PRIVATE)
                .getString(SkinConfig.SKIN_PATH_NAME, "");
    }

    /**
     * 清空皮肤路径
     */
    public void clearSkinInfo() {
        saveSkinPath("");
    }

}

SkinAppCompatViewInflate:是Google官方提供的源码,因为源码不是public所以不能直接使用

public class SkinAppCompatViewInflater {

        private static final Class<?>[] sConstructorSignature = new Class[]{
                Context.class, AttributeSet.class};
        private static final int[] sOnClickAttrs = new int[]{android.R.attr.onClick};

        private static final String[] sClassPrefixList = {
                "android.widget.",
                "android.view.",
                "android.webkit."
        };

        private static final String LOG_TAG = "AppCompatViewInflater";

        private static final Map<String, Constructor<? extends View>> sConstructorMap
                = new ArrayMap<>();

        private final Object[] mConstructorArgs = new Object[2];

        public final View createView(View parent, final String name, @NonNull Context context,
                                     @NonNull AttributeSet attrs, boolean inheritContext,
                                     boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
            final Context originalContext = context;

            // We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
            // by using the parent's context
            if (inheritContext && parent != null) {
                context = parent.getContext();
            }
            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 it's 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 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, android.support.v7.appcompat.R.styleable.View, 0, 0);
            int themeId = 0;
            if (useAndroidTheme) {
                // First try reading android:theme if enabled
                themeId = a.getResourceId(android.support.v7.appcompat.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(android.support.v7.appcompat.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);
            }
        }

}

使用

 public void skin(View view) {
        //换肤
       String skinPath= Environment.getExternalStorageDirectory().getAbsolutePath()+File.separator
                + "Download"+File.separator+"meinv.skin";
        int loadSkin = SkinManager.getInstance().loadSkin(skinPath);
    }

    public void skin1(View view) {
        //默认
        SkinManager.getInstance().restoreDefault();
    }

    public void skin2(View view) {
        //跳转
        Intent intent = new Intent(this, MainActivity.class);
        startActivity(intent);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值