• 导赏!广美研究生导师带你看2018研究生毕业作品展 2019-04-20
  • 【沙湾风光】百花盛开 迎六月 2019-04-20
  • 【专题】传统文化点亮精彩生活 2019-04-10
  • 四川九寨沟发生特大泥石流 冲毁民房、省道205线被埋 2019-04-10
  • 探寻秘境阿勒泰《章棋的视频日志》 2019-04-06
  • 讲述波兰女子拯救百名犹太儿童的故事 美国教师获森德勒奖 2019-04-06
  • 一个理想的数列递减,看着就想笑,根本放不出什么屁来 2019-04-05
  • 给脑部做个“大扫除” 让大脑充分放松 2019-04-05
  • 海上洄游时 北海狗 连续两周深睡眠 2019-04-03
  • “石家庄太行大街发生重大事故”是谣言!传谣者被拘留 2019-04-01
  • 4号线为端午节“加班” 2019-04-01
  • 这18家小众颜高又难找的家居店,一次性帮你搜罗全了! 2019-03-24
  • “首届中国非处方药行业品牌宣传月”活动将在北京举办 2019-03-24
  • 《礼记》中的礼乐制度与“生活政治” 2019-03-23
  • 众泰T300 1.5L CVT车型上市 6.98万起 2019-03-23
  • 广东快乐十分任三口诀:深入浅出换肤相关技术以及如何实现

    版权声明:本文为博主原创文章,未经博主允许不得转载。 //www.sde9.com/GULINHAI12/article/details/87810187
    广东快乐十分20选8计划 www.sde9.com

    人生一切难题,知识给你答案

    温馨提示:阅读本文需要60-70分钟
    微信公众号:顾林海

    完成换肤需要解决两个问题:

    未命名文件 (15).png

    如何获取换肤的View,利用LayoutInflater内部接口Factory2提供的onCreateView方法获取需要换肤的View,我们从setContentView方法的具体作用来了解LayoutInflater.Factory2接口的作用,以具体源码进行分析,MainActivity代码如下:

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    }
    

    MainActivity继承自AppCompatActivity,AppCompatActivity是Android Support Library包下的类,点击进入AppCompatActivity的setContentView方法:

        @Override
        public void setContentView(@LayoutRes int layoutResID) {
            getDelegate().setContentView(layoutResID);
        }
    

    通过getDelegate()方法返回一个AppCompatDelegate对象,并调用AppCompatDelegate对象的setContentView方法。

        @NonNull
        public AppCompatDelegate getDelegate() {
            if (mDelegate == null) {
                mDelegate = AppCompatDelegate.create(this, this);
            }
            return mDelegate;
        }
    

    通过AppCompatDelegate的create方法创建AppCompatDelegate对象:

        public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
            return create(activity, activity.getWindow(), callback);
        }
    

    通过create方法返回AppCompatDelegate对象:

        private static AppCompatDelegate create(Context context, Window window,
                AppCompatCallback callback) {
            if (Build.VERSION.SDK_INT >= 24) {
                return new AppCompatDelegateImplN(context, window, callback);
            } else if (Build.VERSION.SDK_INT >= 23) {
                return new AppCompatDelegateImplV23(context, window, callback);
            } else {
                return new AppCompatDelegateImplV14(context, window, callback);
            }
        }
    

    AppCompatDelegate对象的创建是根据SDK的不同版本而创建的,其中AppCompatDelegateImplN、AppCompatDelegateImplV23以及AppCompatDelegateImplV14的继承结构如下图所示:

    未命名文件 (13).png

    AppCompatDelegate是一个抽象类,AppCompatDelegateImplBase也是抽象类,主要对AppCompatDelegate功能的扩展,具体的实现类是AppCompatDelegateImplV9,以上根据SDK版本创建的类都继承自AppCompatDelegateImplV9。

    继续回到AppCompatActivity的setContentView方法:

        @Override
        public void setContentView(@LayoutRes int layoutResID) {
            getDelegate().setContentView(layoutResID);
        }
    

    获取AppCompatDelegate对象后,通过该对象的setContentView方法设置ContentView,这个setContentView方法的具体调用是在AppCompatDelegateImplV9中,查看源码如下:

    //android.support.v7.app.AppCompatDelegateImplV9
    
        @Override
        public void setContentView(int resId) {
            ensureSubDecor();
            ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
            contentParent.removeAllViews();
            //注释1
            LayoutInflater.from(mContext).inflate(resId, contentParent);
            mOriginalWindowCallback.onContentChanged();
        }
    
    

    setContentView方法最核心的地方就是在注释1处,通过LayoutInflater加载layout.xml文件,contentParent是我们创建布局后所要添加进去的一个容器,在创建Activity时会创建顶层视图,也就是DecorView,DecorView其实是PhoneWindow中的一个内部类,它会加载相应的系统布局。如下图:

    未命名文件 (14).png

    DecorView就是我们Activity显示的全部视图包括ActionBar,其中ContentView布局是由我们来创建的,并通过LayoutInflater添加到ContentView中。

    进入LayoutInflater的inflate方法中。

        public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
            return inflate(resource, root, root != null);
        }
        
        public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
            final Resources res = getContext().getResources();
            if (DEBUG) {
                Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                        + Integer.toHexString(resource) + ")");
            }
    
            final XmlResourceParser parser = res.getLayout(resource);
            try {
                return inflate(parser, root, attachToRoot);
            } finally {
                parser.close();
            }
        }
    
    

    通过资源大管家,也就是Resources来加载layout文件,最后通过inflate方法的一步步调用,会走到createViewFromTag方法,该方法内部会对每个标签生成对应的View对象。

        View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
                boolean ignoreThemeAttr) {
            ...
    
            try {
                View view;
                if (mFactory2 != null) {
                    //注释1
                    view = mFactory2.onCreateView(parent, name, context, attrs);
                } 
                ...
                if (view == null && mPrivateFactory != null) {
                    view = mPrivateFactory.onCreateView(parent, name, context, attrs);
                }
                //注释2
                if (view == null) {
                    final Object lastContext = mConstructorArgs[0];
                    mConstructorArgs[0] = context;
                    try {
                        if (-1 == name.indexOf('.')) {
                            view = onCreateView(parent, name, attrs);
                        } else {
                            view = createView(name, null, attrs);
                        }
                    } finally {
                        mConstructorArgs[0] = lastContext;
                    }
                }
    
                return view;
            } catch (InflateException e) {
                throw e;
    
            } catch (ClassNotFoundException e) {
                final InflateException ie = new InflateException(attrs.getPositionDescription()
                        + ": Error inflating class " + name, e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
    
            } catch (Exception e) {
                final InflateException ie = new InflateException(attrs.getPositionDescription()
                        + ": Error inflating class " + name, e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            }
        }
    
    

    经过一些列调用进入注释2处,通过mFactory2的onCreateView方法创建对应的View对象,mFactory2的赋值时机需要我们回到MainActivity代码中进行一步步查看:

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    }
    

    进入AppCompatActivity的onCreate方法中:

        protected void onCreate(@Nullable Bundle savedInstanceState) {
            final AppCompatDelegate delegate = getDelegate();
            //注释1
            delegate.installViewFactory();
            delegate.onCreate(savedInstanceState);
            ...
            super.onCreate(savedInstanceState);
        }
    

    注释1处调用了delegate的installViewFactory方法,这个delegate对象是通过getDelegate()方法:

        @NonNull
        public AppCompatDelegate getDelegate() {
            if (mDelegate == null) {
                mDelegate = AppCompatDelegate.create(this, this);
            }
            return mDelegate;
        }
    

    这段代码应该很熟悉了吧,也就是说最终调用AppCompatDelegateImplV9的installViewFactory方法,查看源码:

    class AppCompatDelegateImplV9 extends AppCompatDelegateImplBase
            implements MenuBuilder.Callback, LayoutInflater.Factory2 {
        ...
        @Override
        public void installViewFactory() {
            LayoutInflater layoutInflater = LayoutInflater.from(mContext);
            if (layoutInflater.getFactory() == null) {
                //注释1
                LayoutInflaterCompat.setFactory2(layoutInflater, this);
            } else {
                if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImplV9)) {
                    Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
                            + " so we can not install AppCompat's");
                }
            }
        }
        ...
    }
    

    AppCompatDelegateImplV9本身也实现了LayoutInflater.Factory2接口,在注释1处调用LayoutInflaterCompat的setFactory2方法并传入layoutInflater实例以及自身AppCompatDelegateImplV9对象。

    进入LayoutInflaterCompat的setFactory2方法:

    public void setFactory2(LayoutInflater inflater, LayoutInflater.Factory2 factory) {
                //注释1
                inflater.setFactory2(factory);
    
                final LayoutInflater.Factory f = inflater.getFactory();
                if (f instanceof LayoutInflater.Factory2) {
                    forceSetFactory2(inflater, (LayoutInflater.Factory2) f);
                } else {
                    // Else, we will force set the original wrapped Factory2
                    forceSetFactory2(inflater, factory);
                }
            }
    

    注释1处将getDelegate()方法获取到的AppCompatDelegate对象(具体实现类是AppCompatDelegateImplV9)通过inflater的setFactory2传入进去。

    进入LayoutInflater的setFactory2:

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

    到这里我们知道了LayoutInflater的成员变量mFactory2就是AppCompatDelegateImplV9对象(AppCompatDelegateImplV9实现LayoutInflater.Factory2接口)。

    继续回到createViewFromTag方法中:

        View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
                boolean ignoreThemeAttr) {
            ...
    
            try {
                View view;
                if (mFactory2 != null) {
                    //注释1
                    view = mFactory2.onCreateView(parent, name, context, attrs);
                } 
                ...
                if (view == null && mPrivateFactory != null) {
                    view = mPrivateFactory.onCreateView(parent, name, context, attrs);
                }
                //注释2
                if (view == null) {
                    final Object lastContext = mConstructorArgs[0];
                    mConstructorArgs[0] = context;
                    try {
                        if (-1 == name.indexOf('.')) {
                            view = onCreateView(parent, name, attrs);
                        } else {
                            view = createView(name, null, attrs);
                        }
                    } finally {
                        mConstructorArgs[0] = lastContext;
                    }
                }
    
                return view;
            } catch (InflateException e) {
                throw e;
    
            } catch (ClassNotFoundException e) {
                final InflateException ie = new InflateException(attrs.getPositionDescription()
                        + ": Error inflating class " + name, e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
    
            } catch (Exception e) {
                final InflateException ie = new InflateException(attrs.getPositionDescription()
                        + ": Error inflating class " + name, e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            }
        }
    
    

    注释1处调用mFactory2的onCreateView方法,也就是调用AppCompatDelegateImplV9的onCreateView方法。

    进入AppCompatDelegateImplV9的onCreateView方法:

        @Override
        public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
            ...
            return createView(parent, name, context, attrs);
        }
    

    进入AppCompatDelegateImplV9的createView方法

        @Override
        public View createView(View parent, final String name, @NonNull Context context,
                @NonNull AttributeSet attrs) {
            ...
    
            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 */
            );
        }
    

    调用mAppCompatViewInflater的createView方法,继续进入:

        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;
    
            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 = createTextView(context, attrs);
                    verifyNotNull(view, name);
                    break;
                case "ImageView":
                    view = createImageView(context, attrs);
                    verifyNotNull(view, name);
                    break;
                case "Button":
                    view = createButton(context, attrs);
                    verifyNotNull(view, name);
                    break;
                case "EditText":
                    view = createEditText(context, attrs);
                    verifyNotNull(view, name);
                    break;
                case "Spinner":
                    view = createSpinner(context, attrs);
                    verifyNotNull(view, name);
                    break;
                case "ImageButton":
                    view = createImageButton(context, attrs);
                    verifyNotNull(view, name);
                    break;
                case "CheckBox":
                    view = createCheckBox(context, attrs);
                    verifyNotNull(view, name);
                    break;
                case "RadioButton":
                    view = createRadioButton(context, attrs);
                    verifyNotNull(view, name);
                    break;
                case "CheckedTextView":
                    view = createCheckedTextView(context, attrs);
                    verifyNotNull(view, name);
                    break;
                case "AutoCompleteTextView":
                    view = createAutoCompleteTextView(context, attrs);
                    verifyNotNull(view, name);
                    break;
                case "MultiAutoCompleteTextView":
                    view = createMultiAutoCompleteTextView(context, attrs);
                    verifyNotNull(view, name);
                    break;
                case "RatingBar":
                    view = createRatingBar(context, attrs);
                    verifyNotNull(view, name);
                    break;
                case "SeekBar":
                    view = createSeekBar(context, attrs);
                    verifyNotNull(view, name);
                    break;
                default:
                    // The fallback that allows extending class to take over view inflation
                    // for other tags. Note that we don't check that the result is not-null.
                    // That allows the custom inflater path to fall back on the default one
                    // later in this method.
                    view = createView(context, name, attrs);
            }
    
            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;
        }
    
    

    整个调用流程图如下:

    未命名文件 (17).png

    mAppCompatViewInflater的createView方法主要通过switch/case形式对相应的标签名字创建对应的View对象,比如TextView调用createTextView方法创建TextView对象。这里有个问题,如果是自定义的View或是在这里并没有判断的View的话,View就为null。

    继续回到createViewFromTag方法中:

        View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
                boolean ignoreThemeAttr) {
            ...
    
            try {
                View view;
                if (mFactory2 != null) {
                    //注释1
                    view = mFactory2.onCreateView(parent, name, context, attrs);
                } 
                ...
                if (view == null && mPrivateFactory != null) {
                    view = mPrivateFactory.onCreateView(parent, name, context, attrs);
                }
                //注释2
                if (view == null) {
                    final Object lastContext = mConstructorArgs[0];
                    mConstructorArgs[0] = context;
                    try {
                        if (-1 == name.indexOf('.')) {
                            //注释3
                            view = onCreateView(parent, name, attrs);
                        } else {
                            //注释4
                            view = createView(name, null, attrs);
                        }
                    } finally {
                        mConstructorArgs[0] = lastContext;
                    }
                }
    
                return view;
            } catch (InflateException e) {
                throw e;
    
            } catch (ClassNotFoundException e) {
                final InflateException ie = new InflateException(attrs.getPositionDescription()
                        + ": Error inflating class " + name, e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
    
            } catch (Exception e) {
                final InflateException ie = new InflateException(attrs.getPositionDescription()
                        + ": Error inflating class " + name, e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            }
        }
    
    

    注释1处在上面已经解析过了就是对layout文件中的标签类型创建对应的View对象,如果是自定义的View或是layout文件中相应的View标签在这里并没有判断(毕竟系统不可能全部都判断到),这时View就为null。进入注释2处对View为null的情况进行处理。

    注释3处如果不是全限定名的类名调用onCreateView方法:

        protected View onCreateView(View parent, String name, AttributeSet attrs)
                throws ClassNotFoundException {
            return onCreateView(name, attrs);
        }
        protected View onCreateView(String name, AttributeSet attrs)
                throws ClassNotFoundException {
            return createView(name, "android.view.", attrs);
        }
    

    如果不是全限定的类名,默认加上“android.view.”。

    继续往下追踪:

        public final View createView(String name, String prefix, AttributeSet attrs)
                throws ClassNotFoundException, InflateException {
            Constructor<? extends View> constructor = sConstructorMap.get(name);
            if (constructor != null && !verifyClassLoader(constructor)) {
                constructor = null;
                sConstructorMap.remove(name);
            }
            Class<? extends View> clazz = null;
    
            try {
                Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
    
                if (constructor == null) {
                    // Class not found in the cache, see if it's real, and try to add it
                    clazz = mContext.getClassLoader().loadClass(
                            prefix != null ? (prefix + name) : name).asSubclass(View.class);
    
                    if (mFilter != null && clazz != null) {
                        boolean allowed = mFilter.onLoadClass(clazz);
                        if (!allowed) {
                            failNotAllowed(name, prefix, attrs);
                        }
                    }
                    constructor = clazz.getConstructor(mConstructorSignature);
                    constructor.setAccessible(true);
                    sConstructorMap.put(name, constructor);
                } else {
                    // If we have a filter, apply it to cached constructor
                    if (mFilter != null) {
                        // Have we seen this name before?
                        Boolean allowedState = mFilterMap.get(name);
                        if (allowedState == null) {
                            // New class -- remember whether it is allowed
                            clazz = mContext.getClassLoader().loadClass(
                                    prefix != null ? (prefix + name) : name).asSubclass(View.class);
    
                            boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                            mFilterMap.put(name, allowed);
                            if (!allowed) {
                                failNotAllowed(name, prefix, attrs);
                            }
                        } else if (allowedState.equals(Boolean.FALSE)) {
                            failNotAllowed(name, prefix, attrs);
                        }
                    }
                }
    
                Object lastContext = mConstructorArgs[0];
                if (mConstructorArgs[0] == null) {
                    // Fill in the context if not already within inflation.
                    mConstructorArgs[0] = mContext;
                }
                Object[] args = mConstructorArgs;
                args[1] = attrs;
                //注释1
                final View view = constructor.newInstance(args);
                if (view instanceof ViewStub) {
                    // Use the same context when inflating ViewStub later.
                    final ViewStub viewStub = (ViewStub) view;
                    viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
                }
                mConstructorArgs[0] = lastContext;
                return view;
    
            } catch (NoSuchMethodException e) {
                final InflateException ie = new InflateException(attrs.getPositionDescription()
                        + ": Error inflating class " + (prefix != null ? (prefix + name) : name), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
    
            } catch (ClassCastException e) {
                // If loaded class is not a View subclass
                final InflateException ie = new InflateException(attrs.getPositionDescription()
                        + ": Class is not a View " + (prefix != null ? (prefix + name) : name), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } catch (ClassNotFoundException e) {
                // If loadClass fails, we should propagate the exception.
                throw e;
            } catch (Exception e) {
                final InflateException ie = new InflateException(
                        attrs.getPositionDescription() + ": Error inflating class "
                                + (clazz == null ? "<unknown>" : clazz.getName()), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
        }
    
    

    上面代码比较多,总结就是在注释1处通过反射创建相应的View对象。

    到这里我们知道了Layout资源文件的加载是通过LayoutInflater.Factory2的onCreateView方法实现的。也就是如果我们自己定义一个实现了LayoutInflater.Factory2接口的类并实现onCreateView方法,在该方法中保存需要换肤的View,最后给换肤的View设置插件中的资源。

    加载外部资源可以通过反射创建AssetManager对象,反射调用AssetManager的addAssetPath方法加载外部资源,最后创建Resources对象并传入刚创建的AssetManager对象,通过刚创建的Resources对象获取相应的资源。

    首先获取需要换肤的View,怎么知道哪些View需要换肤,可以通过自定义属性来判断,新建attr.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="Skin">
            <attr name="skinChange" format="boolean" />
        </declare-styleable>
    </resources>
    

    skinChange用于判断View是否需要进行换肤。编写我们的布局文件:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="//schemas.android.com/apk/res/android"
        xmlns:app="//schemas.android.com/apk/res-auto"
        xmlns:tools="//schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:skinChange="true"
        android:background="@drawable/girl"
        android:orientation="vertical">
    
        <Button
            android:id="@+id/btn_skin"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/text_color"
            app:skinChange="true"
            android:text="点击进行换肤"
            tools:ignore="MissingPrefix" />
    
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:skinChange="true"
            android:textSize="15sp"
            android:textColor="@color/text_color"
            android:text="这是一段文本,当点击进行换肤时,颜色会进行相应的变化"
            tools:ignore="MissingPrefix" />
    
        <ImageView
            android:layout_width="100dp"
            android:layout_height="100dp"
            app:skinChange="true"
            android:src="@drawable/level"
            android:layout_marginTop="10dp"
            tools:ignore="MissingPrefix" />
    </LinearLayout>
    

    新建SkinFactory类并实现自LayoutInflater.Factory2接口:

    public class SkinFactory implements LayoutInflater.Factory2 {
    
      public class SkinFactory implements LayoutInflater.Factory2 {
    
        private AppCompatDelegate mDelegate;
    
        static final Class<?>[] mConstructorSignature = new Class[]{Context.class, AttributeSet.class};//
        final Object[] mConstructorArgs = new Object[2];
        private static final HashMap<String, Constructor<? extends View>> sConstructorMap = new HashMap<String, Constructor<? extends View>>();
        static final String[] prefix = new String[]{
                "android.widget.",
                "android.view.",
                "android.webkit."
        };
    
        public void setDelegate(AppCompatDelegate delegate) {
            this.mDelegate = delegate;
        }
    
        @Override
        public View onCreateView(String name, Context context, AttributeSet attrs) {
            return null;
        }
    
        @Override
        public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
    
            View view = mDelegate.createView(parent, name, context, attrs);
            if (view == null) {
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
                        view = createViewByPrefix(context, name, prefix, attrs);
                    } else {
                        view = createViewByPrefix(context, name, null, attrs);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
    
            //保存需要换肤的View
            SkinChange.getInstance().saveSkin(context, attrs, view);
    
            return view;
        }
    
        private  View createViewByPrefix(Context context, String name, String[] prefixs, AttributeSet attrs) {
    
            Constructor<? extends View> constructor = sConstructorMap.get(name);
            Class<? extends View> clazz = null;
    
            if (constructor == null) {
                try {
                    if (prefixs != null && prefixs.length > 0) {
                        for (String prefix : prefixs) {
                            clazz = context.getClassLoader().loadClass(
                                    prefix != null ? (prefix + name) : name).asSubclass(View.class);
                            if (clazz != null) break;
                        }
                    } else {
                        clazz = context.getClassLoader().loadClass(name).asSubclass(View.class);
                    }
                    if (clazz == null) {
                        return null;
                    }
                    constructor = clazz.getConstructor(mConstructorSignature);
                } catch (Exception e) {
                    e.printStackTrace();
                    return null;
                }
                constructor.setAccessible(true);
                //缓存
                sConstructorMap.put(name, constructor);
            }
            Object[] args = mConstructorArgs;
            args[1] = attrs;
            try {
                //通过反射创建View对象
                return constructor.newInstance(args);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    
    

    Factory2的onCreateView的实现的逻辑与源码差不多,通过系统的AppCompatDelegate的createView方法创建View,如果创建的View为空,通过反射创建View对象,最主要的一步是SkinChange.getInstance().saveSkin方法,用于保存换肤的View,具体代码如下,新建SkinChange类:

    public class SkinChange {
    
        private SkinChange(){}
    
        public static SkinChange getInstance(){
            return Holder.SKIN_CHANGE;
        }
    
         private static class Holder{
             private static final SkinChange SKIN_CHANGE=new SkinChange();
        }
    
        private List<SkinChange.Skin> mSkinListView = new ArrayList<>();
    
        public List<SkinChange.Skin> getSkinViewList(){
            return mSkinListView;
        }
    
        public void saveSkin(Context context, AttributeSet attrs, View view) {
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Skin);
            boolean skin = a.getBoolean(R.styleable.Skin_skinChange, false);
            if (skin) {
                final int Len = attrs.getAttributeCount();
                HashMap<String, String> attrMap = new HashMap<>();
                for (int i = 0; i < Len; i++) {
                    String attrName = attrs.getAttributeName(i);
                    String attrValue = attrs.getAttributeValue(i);
                    attrMap.put(attrName, attrValue);
                    Log.d("saveSkin","attrName="+attrName+"  attrValue="+attrValue);
                }
    
                SkinChange.Skin skinView = new SkinChange.Skin();
                skinView.view = view;
                skinView.attrsMap = attrMap;
                mSkinListView.add(skinView);
            }
    
        }
    
        public static class Skin{
            View view;
            HashMap<String, String> attrsMap;
        }
    }
    
    

    将属性skinChange为true的View以及它的所有属性保存起来。

    新建BaseActivity,实现onCreate方法,在setContentView方法之前替换LayoutInflater的成员变量mFactory2:

    public abstract class BaseActivity extends AppCompatActivity {
    
        private SkinFactory mSkinFactory;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            if(null == mSkinFactory){
                mSkinFactory=new SkinFactory();
            }
            mSkinFactory.setDelegate(getDelegate());
            LayoutInflater layoutInflater=LayoutInflater.from(this);
            layoutInflater.setFactory2(mSkinFactory);
            super.onCreate(savedInstanceState);
        }
    }
    

    运行效果如下:

    wq7.gif

    从控制台打印的信息我们已经知道哪些View的属性需要进行换肤,剩下的就是加载外部apk中的资源,创建LoadResources类:

    public class LoadResources {
    
        private Resources mSkinResources;
        private Context mContext;
        private String mOutPkgName;
    
        public static LoadResources getInstance() {
            return Holder.LOAD_RESOURCES;
        }
    
        private LoadResources() {
        }
    
        private static class Holder{
            private static final LoadResources LOAD_RESOURCES=new LoadResources();
        }
        public void init(Context context) {
            mContext = context.getApplicationContext();
        }
    
        public void load(final String path) {
            File file = new File(path);
            if (!file.exists()) {
                return;
            }
            PackageManager mPm = mContext.getPackageManager();
            PackageInfo mInfo = mPm.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);
            mOutPkgName = mInfo.packageName;
            AssetManager assetManager;
            try {
                assetManager = AssetManager.class.newInstance();
                Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
                addAssetPath.invoke(assetManager, path);
                mSkinResources = new Resources(assetManager,
                        mContext.getResources().getDisplayMetrics(),
                        mContext.getResources().getConfiguration());
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    
        public int getColor(int resId) {
            if (mSkinResources == null) {
                return resId;
            }
            String resName = mSkinResources.getResourceEntryName(resId);
            int outResId = mSkinResources.getIdentifier(resName, "color", mOutPkgName);
            if (outResId == 0) {
                return resId;
            }
            return mSkinResources.getColor(outResId);
        }
    
        public Drawable getDrawable(int resId) {
            if (mSkinResources == null) {
                return ContextCompat.getDrawable(mContext, resId);
            }
            String resName = mSkinResources.getResourceEntryName(resId);
            int outResId = mSkinResources.getIdentifier(resName, "drawable", mOutPkgName);
            if (outResId == 0) {
                return ContextCompat.getDrawable(mContext, resId);
            }
            return mSkinResources.getDrawable(outResId);
        }
    }
    
    

    LoadResources类非常简单,通过反射创建AssetManager,并执行addAssetPath来加载外部apk,最后创建一个外部资源的Resources。

    新建接口ISkinView用于约定换肤方法:

    public interface ISkinView {
        void change(String path);
    }
    

    创建SkinChangeBiz并实现ISkinView接口:

     public class SkinChangeBiz implements ISkinView {
    
        private static class Holder {
            private static final ISkinView SKIN_CHANGE_BIZ = new SkinChangeBiz();
        }
    
        public static ISkinView getInstance() {
            return Holder.SKIN_CHANGE_BIZ;
        }
    
        @Override
        public void change(String path) {
            File skinFile = new File(Environment.getExternalStorageDirectory(), path);
            LoadResources.getInstance().load(skinFile.getAbsolutePath());
            for (SkinChange.Skin skinView : SkinChange.getInstance().getSkinViewList()) {
                changeSkin(skinView);
            }
        }
    
        void changeSkin(SkinChange.Skin skinView) {
            if (!TextUtils.isEmpty(skinView.attrsMap.get("background"))) {
                int bgId = Integer.parseInt(skinView.attrsMap.get("background").substring(1));
                String attrType = skinView.view.getResources().getResourceTypeName(bgId);
                if (TextUtils.equals(attrType, "drawable")) {
                    skinView.view.setBackgroundDrawable(LoadResources.getInstance().getDrawable(bgId));
                } else if (TextUtils.equals(attrType, "color")) {
                    skinView.view.setBackgroundColor(LoadResources.getInstance().getColor(bgId));
                }
            }
    
            if (skinView.view instanceof TextView) {
                if (!TextUtils.isEmpty(skinView.attrsMap.get("textColor"))) {
                    int textColorId = Integer.parseInt(skinView.attrsMap.get("textColor").substring(1));
                    ((TextView) skinView.view).setTextColor(LoadResources.getInstance().getColor(textColorId));
                }
            }
    
        }
    
    }
    
    
    

    SkinChangeBiz的change方法中先加载外部资源,再遍历之前保存的换肤View,对相关属性进行设置。

    前期工作已经准备好了,剩下的创建皮肤插件,新建工程,添加需要换肤的资源,注意资源名必须与宿主的资源名一样,皮肤插件的sdk版本也必须保持一致,皮肤插件工程就不贴出来了,比较简单。

            mBtnSkin.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //进行换肤
                    SkinChangeBiz.getInstance().change("skinPlugin.apk");
                }
            });
    

    运行效果如下:

    wq8.gif

    github地址请点击这里

    没有更多推荐了,广东快乐十分20选8计划

    展开

    热门文章

  • 导赏!广美研究生导师带你看2018研究生毕业作品展 2019-04-20
  • 【沙湾风光】百花盛开 迎六月 2019-04-20
  • 【专题】传统文化点亮精彩生活 2019-04-10
  • 四川九寨沟发生特大泥石流 冲毁民房、省道205线被埋 2019-04-10
  • 探寻秘境阿勒泰《章棋的视频日志》 2019-04-06
  • 讲述波兰女子拯救百名犹太儿童的故事 美国教师获森德勒奖 2019-04-06
  • 一个理想的数列递减,看着就想笑,根本放不出什么屁来 2019-04-05
  • 给脑部做个“大扫除” 让大脑充分放松 2019-04-05
  • 海上洄游时 北海狗 连续两周深睡眠 2019-04-03
  • “石家庄太行大街发生重大事故”是谣言!传谣者被拘留 2019-04-01
  • 4号线为端午节“加班” 2019-04-01
  • 这18家小众颜高又难找的家居店,一次性帮你搜罗全了! 2019-03-24
  • “首届中国非处方药行业品牌宣传月”活动将在北京举办 2019-03-24
  • 《礼记》中的礼乐制度与“生活政治” 2019-03-23
  • 众泰T300 1.5L CVT车型上市 6.98万起 2019-03-23
  • 11选5技巧 搜狐彩票图表频道 超级大乐透开奖 彩票中大奖安全吗 台湾所有彩票中奖视频 最新体彩七星彩走势图 竞彩足球比分如何分析 彩客网完场比分直播 排列五综合走势图 新疆时时彩三星和值 哪个网站看海南飞鱼开奖 燕赵风采20选5开奖 福彩中心投注站 南国彩票论坛海口 博发娱乐城 生肖时时彩直播