美文网首页ITHello World安卓程序员
Android主题换肤 无缝切换

Android主题换肤 无缝切换

作者: _SOLID | 来源:发表于2016-04-17 22:04 被阅读52638次

    今天再给大家带来一篇干货。 Android的主题换肤 ,可插件化提供皮肤包,无需Activity的重启直接实现无缝切换,可高仿网易云音乐的主题换肤。

    这个链接是本次的Demo打包出来的样本SkinChangeDemo,可以去下载下来先试试效果,皮肤文件需放到存储卡的根目录下。

    关于Android的主题换肤都是个老生常谈的问题了。网上给出的方案也是层出不穷,最近我也是很想去了解这方面的知识,所以我去搜一下就会有一大堆介绍这方面的文章,但是最后的结果都是不尽人意的,有的确实是给出了一些比较好的解决方案,但是没有一个实质性的Demo可以参考,所以也只能是纸上谈兵罢了,有的呢,确实是给出了一个参考的Demo但是最后的结果不是我想要的。关于Android的换肤方案技术的总结,这篇文章还是挺有参考价值的Android换肤技术总结。感兴趣的同学可以去了解下,就当做是一个知识的普及。

    今天我要实现的一个换肤方案是基于github上的这个开源框架Android-Skin-Loader这个框架的换肤机制是使用动态加载的机制去加载皮肤包里面的内容,无需Acitvity重启即可实现皮肤的实时更换,皮肤包是可以与原安装包相分离的,需要自己定做(这个皮肤包其实也就是一个普通的Android项目,只是只有资源文件没有类文件而已),这样做的好处就是可以在线提供皮肤包供用户去下载,也可以大大的减少安装包的体积,同时也很好的实现了插件化。其实这个框架是可以拿来直接来用的,直接几行代码基本上就可以解决Android的主题换肤,但是作为一个程序员怎么可以只是简单的知道怎么用就行了吗?如果真是这样就真的太low了。遇到一个好的开源项目我们至少需要把他的源码大致看一下,走一下基本的流程,了解一下他的基本原理,这样我们在技术上才会有所提升。本文实现的Demo是基于在我前段时间发布的Android Material Design 兼容库的使用详解一文中的Demo改进的。最后实现的App也是MaterialDesign的设计风格。

    好了说了这么多,通过本文你可以学到什么,这个可能是大家比较关心的一点

    • 设计出一个基于MaterialDesign风格的App
    • 自己实现一个主题换肤的框架
    • 高仿网易云音乐的主题换肤(ps:其实本来我想以这个作为标题的,这样做也可以增加流量,可我不想单纯的做个标题党,给大家带来干货才是最重要的)
    • 让你的技术更上一层楼(这个说了也是白说)

    说了这么久可能就会有人按捺不住了:我是来看干货的,不是来这听你瞎BB的。不要急干货马上来。如果实在感觉枯燥可以直接跳到文末去看源码。下面先来几张效果图来爽一下

    网易云音乐换肤界面

    这个是网易云音乐的换肤界面,他提供了几个默认的,也提供了可以在线下载的主题,他的切换效果还是非常赞的,用过这个软件的同学肯定是知道的。学习完本文后就可以做出类似于这个换肤效果。


    Demo最终效果图

    这个动态图是最终我们这个Demo实现的效果,这个Demo总体来说还是比较简单的,只提供了三种皮肤。实现了一个基本的换肤效果,主要还是用于拿来学习使用。当然更复杂的换肤基于这个Demo也是可以办到的,这里主要还是去讲解原理。

    在介绍之前还需要先给大家普及一下LayoutInflaterFactory相关的知识。如果已经知道了这方面的知识点,下面这一段可以直接略过。

    对于LayoutInflater大家可能都不太陌生,当你需要把xml文件转化成对应View的时候就必须用到它,我想对于他怎么使用的就不用我介绍了。LayoutInflater 提供了setFactory(LayoutInflater.Factory factory)和setFactory2(LayoutInflater.Factory2 factory)两个方法可以让你去自定义布局的填充(有点类似于过滤器,我们在填充这个View之前可以做一些额外的事,但不完全是),Factory2 是在API 11才添加的。
    他们提供了下面的方法让你去重写。在这里面你完全可以自己去定义去创建你所想要的View,如果在你在重写的方法中返回null的话,就会以系统默认的方式去创建View。
    <pre>
    View onCreateView(String name, Context context, AttributeSet attrs)//LayoutInflater.Factory
    </pre>
    <pre>
    View onCreateView(View parent, String name, Context context, AttributeSet attrs)//LayoutInflater.Factory2
    </pre>

    LayoutInflater都被设置了一个默认的Factory,Activity 是实现了LayoutInflater.Factory接口的,因此在你的Activity中直接重写onCreateView就可以自定义View的填充了。

    下面这句是对LayoutInflater.Factory一个比较好的理解

    Inflating your own custom views, instead of letting the system do it

    这个也是这个Demo其中的一个比较重要技术点。如果有想更详细了解的文末会有参考链接。


    下面就正式开始介绍怎么去做这个主题换肤吧。

    先来看看这个Demo的项目结构:

    项目结构图

    至于xRecyclerView可以不用管,这里我们用不到(这是之前用到的,与本次无关),他只是一个RecyclerView的一个扩展框架,支持下拉刷新和上拉加载,是一个在github上的一个开源项目。

    这里我们直接来看看lib_skinloader这个库吧(这里面的内容大部分是来源于Android-Skin-Loader这个框架,我只做了部分修改,主要是适配AppCompatActivity,原框架是基于最初的Activty开发的,在这里再次感谢开源作者),这个库就是今天所讲的核心内容

    lib_skinloader包结构图

    我们都知道在Android中如果想去获取资源文件都必须通过Resources去获取。这个库的核心思想就是动态的去加载第三方包里面的包,获取到其Resources然后以获取到的这个Resources去获取第三方包里面的资源内容,最后设置到我们有需响应皮肤更改的View上。

    这里我就只介绍load和base两个包,其他包的内容在讲解的时候会涉及到

    1.load包

    我们先来看看这个load包里面的内容(其实这里就是今天核心内容的核心)。

    load包

    里面有两个类文件:SkinInflaterFactory、SkinManager

    我们先来看看SkinManager的实现,直接跳到load方法
    <pre>
    public void load(String skinPackagePath, final ILoaderListener callback) {

        new AsyncTask<String, Void, Resources>() {
    
            protected void onPreExecute() {
                if (callback != null) {
                    callback.onStart();
                }
            }
    
            @Override
            protected Resources doInBackground(String... params) {
                try {
                    if (params.length == 1) {
                        String skinPkgPath = params[0];
                        Log.i("loadSkin", skinPkgPath);
                        File file = new File(skinPkgPath);
                        if (file == null || !file.exists()) {
                            return null;
                        }
    
                        PackageManager mPm = context.getPackageManager();
                        PackageInfo mInfo = mPm.getPackageArchiveInfo(skinPkgPath, PackageManager.GET_ACTIVITIES);
                        skinPackageName = mInfo.packageName;
    
                        AssetManager assetManager = AssetManager.class.newInstance();
                        Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
                        addAssetPath.invoke(assetManager, skinPkgPath);
    
    
                        Resources superRes = context.getResources();
                        Resources skinResource = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
    
                        SkinConfig.saveSkinPath(context, skinPkgPath);
    
                        skinPath = skinPkgPath;
                        isDefaultSkin = false;
                        return skinResource;
                    }
                    return null;
                } catch (Exception e) {
                    e.printStackTrace();
                    return null;
                }
            }
    
            protected void onPostExecute(Resources result) {
                mResources = result;
    
                if (mResources != null) {
                    if (callback != null) callback.onSuccess();
                    notifySkinUpdate();
                } else {
                    isDefaultSkin = true;
                    if (callback != null) callback.onFailed();
                }
            }
    
        }.execute(skinPackagePath);
    }
    

    </pre>

    这个方法有两个参数,第一个是皮肤包的路径,第二个就是一个简单的回调

    其中doInBackground方法里面就实现了动态的去获取皮肤包的Resources,当获取成功之后,在onPostExecute方法中就将这个Resources赋值到我们定义好的变量中去,以方便我们之后的使用,注意到当获取到的这个Resources不为空时,也就是我们已经获取到了皮肤包里面的资源,我们就调用notifySkinUpdate()这个方法来通知界面去更改皮肤,如果为空就还是使用默认的皮肤。

    我们来看看notifySkinUpdate()的实现


    notifySkinUpdate

    这里很简单,就是去遍历mSkinObservers这个集合,然后去通知更新。对于ISkinUpdate是一个接口,每个需要皮肤更新的Activity都需要去实现这个接口。

    SkinManager这个类里面还有诸如getColor(int resId)、getDrawable(int resId)这样的方法,就是去获取第三方包对应的资源文件,值得注意的是如果你的第三方包里没有对应的资源文件,那么就会使用默认的资源文件,如果你有需求,你完全可以去添加一些类似getMipmap(int resID)这样的方法。

    对了,还有一个比较重要的方法忘了讲


    restoreDefaultTheme

    这个方法就是恢复到系统的默认主题,原理和load都差不多,实现还简单了很多。SkinManager这个类就说这么多,详细实现请到源码中去查看,很多地方我都给了注释。

    我们再来看看SkinInflaterFactory,在这里面主要就是做一些填充View相关的一些工作。我实现的是LayoutInflaterFactory这个接口而不是文章之前提到的LayoutInflater.Factory这个接口是因为这里需要与AppCompatActivity兼容,如果你还是用之前的那个就会出现一些错误,反正我刚弄的时候是折腾了很久的。不管怎么样原理始终是一样的。SkinInflaterFactory的作用就是去搜集那些有需要响应皮肤更改的View。
    我们来看看onCreateView的实现

    onCreateView

    首先我们先去判断这个当前将要View是否有更改皮肤的需求,如果没有我们就返回默认的实现。如果有,我们就自己去处理
    来看看createView方法的实现

    createView

    看起来很多,其实这个方法就是去动态的去创建View。

    下面来看看parseSkinAttr的实现:

    parseSkinAttr
    这个方法其实就是去搜集View中换肤的时候可以更改的属性,当我们换肤的时候就是去更改的这些属性的值,这里你必须要注意一点,这个属性的值一定要是引用类型的(例如:@color/red),千万不能写死,第二个if的判断就是这个作用。到这里可能你就会有个疑问,我怎么知道哪些属性在换肤的时候需要更改。如果你细心一点肯定注意到了这行代码
    <pre>
    SkinAttr mSkinAttr = AttrFactory.get(attrName, id, entryName, typeName);
    </pre>

    这里有个AttrFacory他的作用就是根据属性名,动态的去创建SkinAttr。在AttrFacory中定义了一些类似于这样的常量:



    这就是我们换肤的时候可以更改的那些属性。SkinAttr是一个抽象类,比如background就会去创建一个BackgroundAttr,本项目所用到的属性全都在attr包中。SkinAttr是比较灵活的一个地方,如果你有哪个属性在换肤的时候需要更改,你就去实现一个对应的SkinAttr。

    在parseSkinAttr这个方法的最后我们将View和SkinAttr封装成了一个SkinItem然后添加到一个集合中去,最后还需注意的是,如果当前皮肤不是默认皮肤,一定要去apply一下,这样做主要是防止换了皮肤启动一些新的页面有可能导致换肤不及时的问题。SkinInflaterFactory这个类里面还提供了动态的添加SkinItem的方法,原理都和这里差不多,我就不过多的去说了。

    load包里面的这两个类讲的差不多了,这里看懂了后面的内容也就是小菜一碟了,我相信你看了这里再去看源码一定会轻松地多。

    2.base包

    base包结构

    可以看见这个包里面肯定就是Activity、Fragment、Application的实现,作用肯定就是封装一些公用的方法和属性在里面。

    下面我们一个一个来分析

    • SkinBaseApplication:


      SkinBaseApplication

    可以看到这里我们对SkinManager做了一些初始化的操作。以后我们有需要皮肤更改需求的应用一定要记得一定要继承于SkinBaseApplication。

    • SkinBaseActivity
      我们来看看其onCreate方法


      SkinBaseActivity

      在这里使用了我们之前自定义的View的InflaterFactory,来替换默认的Factory。记住一定要在super.onCreate(savedInstanceState);这个方法之前调用。SkinBaseActivity里面还提供了动态添加可以响应皮肤更改需求的View的相关方法。当然需要响应换肤更改的Activity都需要继承SkinBaseActivity。详细实现请看源码。

    • SkinBaseFragment
      这个和SkinBaseActivity的思想差不多。具体实现看源码,这里我只是给大家提供这个换肤框架的思想,让大家在看源码的时候更轻松。

    这个框架就介绍到这,下面我们来看看怎么去使用。

    在使用的时候一定要记得要Activity要去继承于SkinBaseActivity,Fragment要继承于SkinBaseFragment,Application要继承于SkinBaseApplication。当然把这个框架做为你的项目依赖项肯定是必不可少的。为了Demo的简单,这里我只使用了下面三个颜色作为可以换肤的资源,当然如果你想要使用drawable文件也是可以办到的,前提是你一定要把这个Demo看懂。


    来看一个布局文件


    其中
    <code>xmlns:skin="http://schemas.android.com/android/skin"</code>
    是我们自定义的,在SkinConfig有。
    我们只需在有皮肤更改需求的View中加入skin:enable="true" 就OK了。

    再来看看MainActicvity的部分代码


    这里就是动态的添加有皮肤更改需求的View。

    上面就介绍完了在布局文件中使用方法和在代码中使用方法。

    我们应该怎么去换肤呢?很简单,只需调用SkinManager的load方法就可以了,把皮肤路径传进去就可以了,我的这个Demo为了简单起见,没有做在线换肤的功能,只是在本地提供了可以更换的皮肤,看到这里我相信你对怎样在线换肤已经有想法了。

    怎样去换肤

    最最后我们来看看怎么去开发皮肤包。其实这个是最简单的,皮肤包实际上就是一个基本的Android项目,里面不包含类文件,只有资源文件。这里只需注意 这里的资源文件名字一定要和原项目中的相同,并且只用包含那些在皮肤更改时需要改变的那些就行了!例如我的这个Demo就只是简单对上面的三种颜色做了简单的切换。开发了棕色和黑色两款皮肤,所以资源文件中只有三个color的值,开发完成之后我们需要将其打包成apk文件,为防止用户点击安装,我们将其后缀改成了skin,这样做也具有标识性。如果还是不太清楚可以直接去源码中查看。

    这下再来看一看文章开头效果图是不是突然变得有思路了,快动起你的小手指去敲一个主题换肤的框架吧~~~


    Demo最终效果图

    好了,本文到此结束。很感谢你的耐心看完!

    源码传送:MaterialDesignDemo 欢迎大家Star和Fork,bug肯定是在所难免的,有问题多多讨论。

    参考链接:

    1. LayoutInflater Factories(需梯子)
    2. How to Get the Right LayoutInflater
    3. Android apk动态加载机制的研究
    4. Android换肤技术总结

    文章更新 2017-03-15

    这篇文章已经写了有一段时间了,文中的Demo也是完全可用的,之前的这个Demo只是提供了一个大概的思路,在实际的使用中还是存在很多的问题,所以在写文章后的不久我就将换肤这个模块从Demo提取出来了,并且一直在改进当中,现在已经有了一个比较稳定的版本,可以直接gradle引用,库子项目地址:ThemeSkinning。有很读者提出各种各样的问题,其实这些问题大部分在这个库的Readme文档中都是有说明的,没找到的可以去已经关闭的 issue 里面看看有没有想要的答案,都是早已经解决了的。当然如果还有其他问题,欢迎提 issue,有你们的支持这个库才越来越完善!

    相关文章

      网友评论

      • yezhengy:为什么我开发完成后打包的apk1600多k,而你的skin文件夹下改的.skin文件只有20k???
      • 812ee2da5522:使用的是XML布局,但是在代码里动态设置图片资源文件和颜色资源都失败的!在XML里面设置就是好的,这个怎么解决?
      • Todo2:组件化和插件化的开发里程总结
        https://www.jianshu.com/p/df2a6717009d
      • 未知账号:请教一个问题:
        PackageManager mPm = context.getPackageManager();
        PackageInfo mInfo = mPm.getPackageArchiveInfo(skinPkgPath, PackageManager.GET_ACTIVITIES);
        这段代码中最后获取的 mInfo 一直为null,但是skin 文件我已经拷贝到了我的根目录,且调试发现的确存在,这可能是什么原因导致的呢?
        谁的春春不迷茫:@未知账号 恩好的谢谢楼主,我之后也发现了,弄好了。
        未知账号:@青春不迷茫_Lee 你好,我解决了,是因为我没有加权限,加上权限并在手机上同意获取该权限就可以了
        谁的春春不迷茫:你好 楼主解决了吗。我刚好也碰到这个问题
      • leilifengxingmw:看完不点个赞,评论一下,感觉良心不安啊。:smile:
      • ea277a83bc90:想问支持入selector类型的资源的换肤吗,研究了半天没弄出来。
      • 兟甡:楼主 有个问题请教一下 如果颜色是代码动态设置透明度时 ,颜色怎么更换呢
      • Candy有雪吃:我怎么图片不能更换呢?颜色字体都没有问题,就是换图片不行,用的是1.3.1版本
      • Candy有雪吃:兄弟很给力,刚好有这个需求,文章看完,靠谱,等会下载源码好好看看
      • 60f6e371ac98:老哥,selector文件不支持怎么办
        _SOLID:@鹏鹏鹏先森_简 支持的 你看下文末换肤库的链接
      • vvweilong:Runtime exception
      • Mr_Tommy:动态加载的view怎么搞?例如RecyleView的ItemView
      • JerryloveEmily:如果用了微信的资源名称混淆方案来减小压缩包,如上通过资源id名称,做外接动态apk皮肤包的资源映射 就会有问题,当然也有解决方案 需要在换肤的地方去资源混淆的映射表里找出原有的资源名称 那就可以解决了, setFactory很强大,这种思想还有很多可以玩的东西
      • 53ed2850e837:皮肤包是放在sdcard卡的哪个目录下都可以吗,还是说一定在要放在项目的assets目录下。
      • 阳光大宅男y:PackageManager mPm = context.getPackageManager();
        PackageInfo mInfo = mPm.getPackageArchiveInfo(skinPkgPath, PackageManager.GET_ACTIVITIES);

        mInfo始终返回为null,时路径错了么
        _SOLID:@阳光大宅男y 路径不正确是个原因,也有可能你的文件不是一个apk文件
      • bd3cad376f3a:第三种方法,夜间模式下打开一个新的页面,会先加载默认的白天的布局,闪一下
        _SOLID:@末以北 库的原理应该不会存在闪的情况,请问你是使用的文末给的库吗?
      • da5f9afd4272:为什么布局文件中改变textview的背景 skin:enable=“true”报错,不能切换背景颜色
      • 在雨天中守望晴天:recyclerview内部的控件换颜色这么写啊,直接在item写skin:enable="true"不行
        _SOLID: @在雨天中守望晴天 github有完整的demo
      • bcd6902c4273:我想请问,自选颜色那个功能怎么做呢
        _SOLID: @001_a804 这个目前还不支持的
      • 孤独蓝天:Android换肤技术总结 打开不了
      • 若无初见:用了他的application 就不能全屏怎么搞
        若无初见:如果在代码中又重新设置了颜色好像就不行了。。。
        若无初见:@_SOLID 嗯嗯 我后来发现changeStatusColor这个方法 是修改状态栏颜色,注释就可以了。
        _SOLID:全屏与Application没关系
      • 若无初见:奇怪了,不能换图片背景和textview的字体颜色吗?
        _SOLID: @若无初见 最新的是文末我给的那个链接,你用的是不是那个?
        若无初见:发现两个问题,第一个imageview textivew不行第二个为什么点击换肤的功能有的view会消失啊。我是拷出skinloader里面的包然后做的。这样是不是有问题?
        _SOLID: @若无初见 肯定可以的,你去看看我给的那个demo
      • 若无初见:好好学习下,这次刚好有这个需求。
      • COLOR_EDDY:亲 可以加你联系方式么 我看工程看不出来 皮肤图片放在哪里 找不到切换的图标
        _SOLID:我简书上个人信息上有我的联系方式。ThemeSkining那个库README文档写的很详细,你可以仔细看下。
      • 真的放飞自我:对比Android-Skin-Loader框架,你采用skin:enable来控制哪些控件需要换肤,这样会提高性能,但是代码的侵入性提高了一些。
      • 流水潺湲: Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'solid.ren.skinlibrary.attr.base.SkinAttr solid.ren.skinlibrary.attr.base.SkinAttr.clone()' on a null object reference

        在用你的burgessjp/ThemeSkinning 这个库时出现错误,使用了这个方法 dynamicAddView(fab, "backgroundTint", R.color.colorPrimary);
        什么原因啊
        流水潺湲: @流水潺湲 我明白了,需要自定义,修改一些配置
      • f385b313ecbf:非常不错,思路清楚方便好用
      • 99b1bc786a8b:我用的是ionic2混合开发,不知道怎么实现换肤。
        99b1bc786a8b:@f385b313ecbf ionic2怎么换:flushed:
        f385b313ecbf:兄弟,混合应用天生就能换肤啊
        _SOLID:@打哈哈农具 这里的换肤主要是针对原生的app
      • cf908b4323ca:不错。这个很有用。
      • Yeung_Yeung:你好,有个问题想请教一下,基于上面的原理,其实就是通过把新的皮肤包apk加进AssetManager里 让再创建一个Resources,加载的时候使用这个Resources去load一些新的资源,但是这里有个问题既然皮肤包里面的资源名跟原始包的是一样的,打包皮肤包apk的时候怎么控制资源id不冲突的问题
        真的放飞自我:皮肤apk是单独打包的,不存在资源id冲突问题
        _SOLID:@ivesyeung 这里其实不存在id冲突的问题,我们是根据资源名获取该资源在皮肤包里的id,然后直接获取到对应的drawble 、color在原始apk中使用的
      • 80ac41b8844a:可以实现类似京东淘宝那种底部, 有tab_select,tab_norma l两种状态的图片皮肤吗
        80ac41b8844a:@_SOLID 昨天研究了大半晚上.怎么实现有两种状态切换的view呀,比如imageview,图片,textview的选中,取消选中样式,望给个思路,谢谢
        _SOLID: @80ac41b8844a 可以的
      • 1a54f4ee4f91:不知道楼主有没有遇到过,替换皮肤后,某些控件被隐藏了,今天才学习的,做了一个小DEMO,结果遇到这种问题。
        _SOLID:@_ChenZhiJian 出现这个的原因可能是在改变状态栏颜色的时候的问题,你用的是不是 https://github.com/burgessjp/ThemeSkinning 这个?
        1a54f4ee4f91:准确来说不是被隐藏了,是如果SkinBaseActivity继承的是AppCompatActivity话,换肤后,控件会忘上移。暂时不知道怎么解决,请楼主留意下
      • sendtion:赞一个
      • zp_风:make一下
      • 922b6fb6f9bc:楼主 源码的地址我这里访问不了啊,求一份源码发到邮箱1624950137@qq.com多谢了啊
        _SOLID:@922b6fb6f9bc 普通的文件读取就行了
        _SOLID:@922b6fb6f9bc 文章开头那个链接应该是能访问的。
        922b6fb6f9bc:还有一个问题楼主,我想图片资源都在SD卡的文件夹里面读取,然后切换到另一个文件夹的路径,里面的图片资源名字都是一样的,这样让view都更新,不重启activity,有对应的思路方法吗,求指点啊
      • 半指烟城:博主,SkinManager这个文件中的attach(ISkinUpdate observer) 方法中的第二个判断是不是写错了?? if (!mSkinObservers.contains(mSkinObservers)) {mSkinObservers.add(observer);}
        _SOLID:@半指烟城 嗯,源码已经改了
      • c5dc1c310212:楼主,这个我下载这个demo,怎么出不来那个 皮肤包,然后就是那个得从sd卡里获取,而sd卡里又没有
      • d6cd92b6d4ac:太好了正是我想要的
      • 相互交流:楼主如果我只想全局改变字体的大小或者颜色,而不是改变字体的形状,,有什么解决方案吗?
        _SOLID:@相互交流 目前换肤库没对字体大小做支持,但是应该是能做到的,你阅读一下源码就懂了,更改颜色是支持的。
      • 9fec296c96e0: java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.pm.PackageManager android.content.Context.getPackageManager()' on a null object reference 这个是怎么回事,初学者求助
      • 冰冰的冻结:换肤原理看了好久没看懂
      • ibrucekong:在华为手机上报错:E/initialize resources: theme package errors! 但是在Genymotion和小米手机note上均正常运行。请问这是怎么解决呢?
        ibrucekong:@_SOLID 这个可以跑【THX】,我再调调我的 :smile:
        _SOLID:@ibrucekong 你试过https://github.com/burgessjp/ThemeSkinning这个没
        ibrucekong:@ibrucekong 乐视、华为也不能跑,联想能跑 :sob:
      • tyh520life:我想问一下assets下那两个皮肤文件是怎么生成的
        tyh520life:@_SOLID 后缀怎么不是.apk 是手动改的么
        _SOLID:@tyh520life 就是一个普通的apk文件。文章中有说明的
      • _SOLID:这个估计你网络原因,24.0.0版本的v4包没下载下来
      • KUN叔:那个themeSkining的项目我运行不了,
        Error:A problem occurred configuring project ':app'.
        > Could not find support-v4.jar (com.android.support:support-v4:24.0.0).
        Searched in the following locations:
        https://jcenter.bintray.com/com/android/support/support-v4/24.0.0/support-v4-24.0.0.jar
        你知道是为啥吗?
      • 84ed7ae0e2ba:
        LayoutInflaterCompat.setFactory(getLayoutInflater(), mSkinInflaterFactory);这就能兼容AppCompatActivity的吗?
        84ed7ae0e2ba:好吧 我知道 我看了LayoutInflaterCompat里面的源码理解了。
      • fendo:赞一个
      • ibrucekong:问楼楼一个问题 我切换皮肤后 感觉整个布局都往上移动了一个statusBar的高度 ,这是怎么回事呢 :sob:
        ibrucekong:@_SOLID 是啊 这个透明状态栏有解决方案吗。但我切换皮肤后,一切正常,状态栏切换成了设置的颜色,等我销毁该布局,再进该布局时,状态栏透明效果才出来,这是什么原理啊
        ibrucekong:嗯,是的。:smile:我在根布局上试着加了android:fitSystemWindows="true"就OK了
        _SOLID: @ibrucekong 那个是透明状态栏的问题
      • 1a19ff1ab712:我实现的时候发现了一个问题,不确定是不是我自己的额问题,就是切换主题要换掉的TextView的颜色,我直接在布局中添加textcolor是没问题的,但是我要是把textColor属性写到这个textView的style里面就不可以实现该textView换肤了
        1a19ff1ab712:@1a19ff1ab712不好意思,android 4.4上面的问题是我自己自己代码的问题,因为继承换肤之前也使用了SystemBarTintManager,放在了activity的基类里面,所以重复设置了。
        1a19ff1ab712:@_SOLID 这样。我使用大神的demo是时候发现透明状态栏在换肤后有点不太稳定,有时候换掉了,有时候没有。android4.4真机测试的效果的是切换主题按钮所在的activity换掉了,再开一个activity是没换掉的。android5.0模拟机测试情况是换肤的时候很顺利,但是点击别的界面偶尔发生闪烁,闪烁后透明状态栏效果就消失了
        _SOLID: @1a19ff1ab712 换肤不支持style
      • ec95b5891948: :+1: 不错哦!
      • 47fe3088500c: :sob: 太厉害了 就需要这种硬菜干货
      • 4d123055aeac:这样切换皮肤 ,只换了标题栏,列表里的文字 颜色没有变。不知道你这个 能不能对listview,popwindow, textview ,,,等控件的字体或背景换肤。 我以前也做过一个换肤的 ,不过比较麻烦,原理是 获取皮肤包资源,再替换主包 context. 通过 id名称来引用控件(getResources().getIdentifier())。
        _SOLID:@牛志华 我这个的实现原理和你说的应该差不多。你说的这些都是可以办到的,不过像系统的Dialog这些暂时还不行 。换肤框架地址:https://github.com/burgessjp/ThemeSkinning,欢迎提供问题反馈~~
      • 573a725e1add:赞一个
      • XiaLong:对于简单换主题颜色,可以看看这里http://www.jianshu.com/p/1cd30cedb5d7
      • nbpzjy:好东西!!!
      • 1bc9410cd3f5:我试着把你的关键的布局文件和源码文件移植到Eclipse中, 好多错误哦~~v4包里的LayoutInflaterCompat之类以及v7包都引用不了哎。。呜呜。。。
        57a32d90f9cf: @1bc9410cd3f5 你最好用Android studio.
      • 1a35baf1c9e1:大神你好 我现在有一个问题,我在自己尝试做一个皮肤。当时不知道怎么制作????怎么生成*.skin的文件呢?生成功后放到asses下面吗?
        _SOLID:@軒_宝 文章开始的是我项目最新的地址,里面有很详细的介绍,还有一个Demo
      • 全科:fork
        _SOLID:@quanke 谢谢支持,这个库子刚创建,目前已发布1.1.0版本,支持直接从网络加载皮肤文件,欢迎多多提供意见。
      • I喵先生:请问下如果加载的是zip包要怎么实现呢
      • 馒头先生:不知道网易云音乐的自定义颜色换肤方案是怎么样的,感觉很炫酷。
      • e5497a4bf107:赞\(≧▽≦)/
      • BuxBWT:skin:enable="true" 在xml 设置这个了之后 还需要在activity动态设置dynamicAddSkinEnableView 么?

        我发现有的view需要在代码中动态设置,有的不需要 这是为什么
        _SOLID:@stefchang ToolBar的创建是依赖布局文件的,不能直接 skin:enable="true" ,必须动态添加
        BuxBWT:@_SOLID 好像不是的吧。。。比如例子里面的MainActivity dynamicAddSkinEnableView(mToolbar, "background", R.color.colorPrimary); 我把代码注释了,然后在xml里面把相应的toolbar设置 skin:enable="true" 好像换肤就不行了。。
        _SOLID:@stefchang skin:enable="true"设置了就不用设置后者了,后者主要是用于不能直接new的那些组件,或者以代码形式生成的
      • zhaoyubetter:要实现 界面中,弹出的 对话框,如例子中的 dialog 的 edittext 下划线颜色改变,
        配合使用 Activty.setTheme() 并 通过配置 attr,这样可以实现,颜色改变;
        不知道,博主,对于 界面中dialog 的 换肤,如何处理?谢谢!
        zhaoyubetter:@_SOLID 谢谢
        _SOLID:@zhaoyubetter 之前看到了,本来计划回复,后来忘了,不好意思。对于Dialog 如果每次都是new 一个的话,更换主题是很难办到的,如果使用自定义的Dialog应该没问题。
      • 68d3a4f2bc9a:不好意思,,看漏了
        _SOLID:@胖胖儿徐😳
      • 68d3a4f2bc9a:我想问问skin_black.skin是什么文件,它们和主题包是什么关系呀??
        _SOLID:@胖胖儿徐 文章里说的不是很清楚了吗,那个其实就是一个apk 文件,只不过改了下名字而已。这个就是主题包
      • 034a77616c2e:大哥,我遇到一个问题,用了换肤后,toolbar左边的标题变成了黑色(安卓5.0以下),我研究了好久都没办法解决,如果加上<item name="android:textColorPrimary">@color/white</item>就可以变成白色,但这样左边导航栏的菜单也全白了,一直找不到解决办法,所以来请教你了
        034a77616c2e:@_SOLID 好的,我去试一下,我的应用之前没这个问题的,是用了换肤才这样,很奇怪。我下载你的app也是5.0以下就变黑色。
        _SOLID:@034a77616c2e 这个不是换肤的原因,MD本来主要就是针对5.0以上的手机,所以在5.0以下的手机使用肯定会有一些瑕疵,对于你这个问题,我的做法就是换掉系统默认的图标,还有手动的去设置字体颜色,最新的源码已经解决了,你可以下载下来看看
      • 4d7d8eba04d2:支持一下
      • 九爷写安卓:学习学习
      • 在Azkaban写代码:谢谢分享
      • 嘿张开心:谢谢分享
      • Jimmy5Zhang:1、皮肤包你是如何放到本地目录的?
        2、博客中那个demo演示视频什么软件做的?
        _SOLID:@Jimmy5Zhang as自带的录制视频只能用真机
        Jimmy5Zhang: @_SOLID 你是用模拟器跑的程序喽?
        _SOLID:@Jimmy5Zhang 1.我是用QQ接收,然后进文件夹剪切过去
        2.录制视频AS自带的
      • imesong:现在各种库的原理都类似,我也写了一篇换肤的总结,http://www.jianshu.com/p/8c4b2171fa8c,期待更多交流
        _SOLID:@imesong 我和你使用的是同一个开源库。
      • 034a77616c2e:这个类SkinBaseFragment也报错了
        The method onAttach(Context) of type SkinBaseFragment must override or implement a supertype method
        _SOLID:@034a77616c2e 这个单看这个错误不知道原因。
      • 034a77616c2e:报错了哦

        The method setSelectedTabIndicatorColor(int) is undefined for the type TabLayout
        (类TabLayoutAttr)

        请教一下怎么回事呢?我是初学者,感谢。
        _SOLID:@034a77616c2e 建议把IDE 切换到AS 上来,AS 比eclipse 方便的多,并且现在的开源项目全是基于AS 的
        034a77616c2e:@_SOLID 谢谢哈,估计是版本不一样吧,我的提示这个类没有对应的方法,我用eclipse的哦。
        _SOLID:@034a77616c2e TabLayout是design库中的,请确保项目已经构建完成。
      • e9a44a336dfe:不错。收藏研究
      • 4ce627cf4dbf:可见看到的这篇文章,吓得我赶紧叫老师过来了…… :joy:
        _SOLID:@双胞胎女孩 可以直接拿去用
      • YoungDou:赞\(≧▽≦)/
      • peiandsky:挺好
        _SOLID:@peiandsky 可以去下载源码看看
      • 08a80639d366:赞一个
        _SOLID:@茫忙 谢谢,欢迎star和fork哦。
      • 205fb3dca1be:好赞~
      • 程序员Anthony:星期天都没闲着 :+1:
        _SOLID:@CameloeAnthony 其实这篇文章星期五就写的差不多了,只是现在才发出来😁

      本文标题:Android主题换肤 无缝切换

      本文链接:https://www.haomeiwen.com/subject/rbnglttx.html