当前位置: 首页 > 科技观察

网易云音乐和手机QQ的换肤方法

时间:2023-03-22 16:37:46 科技观察

安卓主题换肤,可以插件形式提供皮肤包,无需重启Activity直接实现无缝切换,高度模仿网易云主题换肤音乐。此链接是本demo打包的样例SkinChangeDemo,大家可以先下载试试效果,皮肤文件需要放在内存卡的根目录下。这是一个关于Android主题皮肤的常见问题。网上也有层出不穷的解决办法。最近很想了解这方面的知识,于是搜索了一下,会有很多介绍这方面的文章,但是最后的结果都不尽如人意。有些确实给出了一些更好的解决方案,但是没有实质性的demo可以参考,所以只能纸上谈兵。有的确实给了一个demo供参考,但是最后的效果并不是我想要的。关于Android的换肤解决技术总结,本文仍然是对Android换肤技术的一个有价值的总结。有兴趣的同学可以多了解一下,算是一个知识普及。今天要实现的一个换肤方案是基于github上的开源框架Android-Skin-Loader。本框架的换肤机制是采用动态加载机制加载换肤包中的内容。无需重启Acitvity即可实时更换皮肤。皮肤包可以从原来的安装包中分离出来,需要自定义(这个皮肤包其实就是一个普通的Android工程,只是只有资源文件,没有类文件)。这样做的好处是可以在线提供皮肤包供用户下载,还可以大大减少安装包的体积,也很不错。插入。其实这个框架是可以直接使用的。几行代码基本可以解决Android的主题换肤,但是一个程序员又怎么会简单的知道怎么用呢?如果是这样的话,那就是真的太low了。遇到一个好的开源项目,我们至少需要粗略地看一下它的源码,走一遍基本的流程,了解它的基本原理,这样才能提高自己的技术。本文实现的demo是在我前段时间发表的《AndroidMaterialDesign兼容库使用详解》一文中的demo改进而来。最终实现的App也是MaterialDesign的设计风格。好了,说了这么多,通过这篇文章你能学到什么?这可能是大家比较关心的一点。基于MaterialDesign风格设计一个app,自己实现一个主题换肤框架。高仿网易云音乐的主题换肤(ps:其实我本来想用这个做标题的,这样做也能增加流量,但不想单纯的当个头条党,最重要的是带上给大家干货)让自己的技术更上一层楼(这个说了也没用)说了这么久,可能有人会忍不住:我是来看干货的,不是来听的给你的盲BB。不要着急,干货马上就到。如果实在觉得无聊,可以直接跳到文末看源码。下面放几张效果图来提神一下。这是网易云音乐的换肤界面。它提供了几个可以在线下载的默认主题和主题。它的切换效果还是很不错的。我以前用过这个软件。我的同学们一定知道。学完这篇文章,你就可以做出类似这篇的换皮效果了。这个动态图像就是我们演示的最终效果。这个demo总体来说比较简单,只提供了三种皮肤。实现了一个基本的换皮效果,主要用于学习和使用。当然更复杂的换皮也可以基于这个Demo来做,这里主要是讲解一下原理。在介绍之前,我们需要先普及一下LayoutInflaterFactory的相关知识。如果你已经知道这方面的知识点,下面这段可以直接跳过。你可能对LayoutInflater并不陌生。当你需要将一个xml文件转换成相应的View时,你必须使用它。我想我不需要介绍如何使用它。LayoutInflater提供了两个方法,setFactory(LayoutInflater.Factoryfactory)和setFactory2(LayoutInflater.Factory2factory),可以让你自定义布局的填充(有点类似于过滤器,我们可以在填充这个View之前做一些额外的事情,但不是相当),Factory2是在API11中添加的。它们提供了以下方法供您覆盖。在里面,你可以自己定义和创建你想要的View。如果在重写的方法中返回null,View将按照系统默认的方式创建。ViewonCreateView(Stringname,Contextcontext,AttributeSetattrs)//LayoutInflater.FactoryViewonCreateView(Viewparent,Stringname,Contextcontext,AttributeSetattrs)//LayoutInflater.Factory2LayoutInflater设置为一个默认的Factory,Activity实现了LayoutInflater.Factory接口,所以在你可以自定义填充View的通过直接重写Activity中的onCreateView。下面这句话是更好理解LayoutInflater.FactoryInflating自己的自定义视图,而不是让系统来做这也是这个Demo比较重要的技术点之一。如果你想了解更多,文末会有参考链接。下面正式开始介绍如何做这个主题换肤。我们先看一下这个demo的工程结构:至于xRecyclerView,可以忽略,我们这里不使用(这个之前用过,和这次没关系),只是一个扩展RecyclerView框架,支持下拉刷新和上拉加载,是github上的一个开源项目。这里先看一下库lib_skinloader(这里大部分内容来自于Android-Skin-Loader的框架,我只是做了一些修改,主要是为了适配AppCompatActivity,原来的框架是基于原来的Activty开发的,感谢再次感谢开源作者),这个库就是今天讲的核心内容。我们都知道,在Android中要想获取资源文件,就必须通过Resources来获取。这个库的核心思想是动态加载第三方包中的包,获取它的Resources然后使用获取到的Resources获取第三方包中的资源内容,最后设置到那个View上我们需要对皮肤变化做出反应。.这里只介绍load和base包。其他包的内容会在讲解中涉及到。1.加载包。我们先看一下加载包的内容(其实这也是今天核心内容的核心)。里面有两个类文件:SkinInflaterFactory、SkinManager我们先来看看SkinManager的实际情况,直接跳到加载方法publicvoidload(StringskinPackagePath,finalILoaderListenercallback){newAsyncTask(){protectedvoidon(){Excalledvoidon()=null){callback.onStart();}}@OverrideprotectedResourcesdoInBackground(String...params){try{if(params.length==1){StringskinPkgPath=params[0];Log.i("loadSkin",skinPkgPath);Filefile=newFile(skinPkgPath);if(file==null||!file.exists()){returnnull;}PackageManagermPm=context.getPackageManager();PackageInfomInfo=mPm.getPackageArchiveInfo(skinPkgPath,PackageManager.GET_ACTIVITIES);skinPackageName=mInfo.packageName;AssetManagerassetManager=AssetManager.class.newInstance();方法addAssetPath=assetManager.getClass().getMethod("addAssetPath",String.class);addAssetPath.invoke(assetManager,skinPkgPath);ResourcessuperRes=context.getResources();ResourcesskinResource=newResources(assetManager,superRes.getDisplayMetrics(),superRes.getConfiguration());SkinConfig.saveSkinPath(context,skinPkgPath);skinPath=skinPkgPath;isDefaultSkin=false;returnskinResource;}returnull;}catch(Exceptione){e.printStackTrace();returnnull;}}protectedvoidonPostExecute(资源结果){mResources=result;if(mResources!=null){if(callback!=null)callback.onSuccess();notifySkinUpdate();}else{isDefaultSkin=true;if(callback!=null)callback.onFailed();}}}.execute(skinPackagePath);}这个方法有两个参数,第一个是皮肤包的路径,第二个是一个简单的回调。doInBackground方法实现了皮肤包资源的动态获取。获取成功后,将Resources赋值给我们在onPostExecute方法中定义的变量,方便我们后续使用。注意,当获取到的Resources不为空,即我们已经获取到了皮肤包里面的资源,我们调用方法notifySkinUpdate()通知接口更换皮肤。如果为空,则仍使用默认皮肤。我们来看看notifySkinUpdate()的实现。通知更新。ISkinUpdate是一个接口,每个需要更新皮肤的Activity都需要实现这个接口。SkinManager类中还有getColor(intresId)、getDrawable(intresId)等方法,就是获取第三方包对应的资源文件。值得注意的是,如果你的第三方包中没有对应的资源文件,那么就会使用默认的资源文件。如果你需要它,你可以添加一些方法,比如getMipmap(intresID)。对了,还有一个更重要的方法忘记说了。此方法是恢复系统默认主题。原理和load差不多,实现起来也简单很多。这就是SkinManager类的全部内容。具体实现请前往源码查看。我在很多地方都发表了评论。我们再来看看SkinInflaterFactory,它主要做了一些填充View相关的工作。我实现的是LayoutInflaterFactory接口,而不是文中提到的LayoutInflater.Factory接口,因为需要兼容AppCompatActivity。如果还是用之前的,会出现一些错误。无论如何,我花了很长时间才得到它。的。无论哪种方式,原则总是相同的。SkinInflaterFactory的作用是收集需要响应皮肤变化的View。我们看一下onCreateView的实现。首先我们判断当前View是否需要换肤。如果没有,我们将返回到默认实现。如果有,我们会自行处理。我们来看看createView方法的实现。好像有很多实现。其实这个方法就是动态创建View。我们看一下parseSkinAttr的实现:这个方法其实就是收集View中改变皮肤时可以改变的属性。当我们改变皮肤时,我们将改变这些属性的值。这里必须注意一点,这个属性的值必须是引用类型(例如:@color/red),不能写死。第二个if判断就是为了这个目的。这里大家可能会有一个疑问,我怎么知道在换皮肤的时候需要改变哪些属性。细心的你一定注意到了这行代码SkinAttrmSkinAttr=AttrFactory.get(attrName,id,entryName,typeName);这里有一个AttrFacory,它的作用是根据属性名动态创建SkinAttr。像这样的一些常量定义在AttrFacory中:这些是我们在更改皮肤时可以更改的属性。SkinAttr是一个抽象类。例如,background将创建一个BackgroundAttr。本项目用到的属性都在attr包中。SkinAttr是比较灵活的地方。如果在换皮肤的时候有什么属性需要改变,就需要实现一个对应的SkinAttr。在parseSkinAttr方法的最后,我们将View和SkinAttr封装到一个SkinItem中,并将其添加到一个集合中。最后需要注意的是,如果当前皮肤不是默认皮肤,则必须应用它。这主要是为了防止换了一些新的页面用皮肤启动,可能会导致不能及时换皮肤的问题。SkinInflaterFactory类还提供了动态添加SkinItems的方法。原理和这里差不多,就不赘述了。load包中的两个类几乎是一样的。在这里理解下面的内容是小菜一碟。相信看完这篇你阅读源码会轻松很多。2、从base包可以看出,这个包肯定是Activity、Fragment、Application的实现,它的作用肯定是把一些公共的方法和属性封装在里面。下面一一分析一下SkinBaseApplication:可以看到这里我们对SkinManager做了一些初始化操作。以后需要换肤的应用一定要记得继承自SkinBaseApplication。SkinBaseActivity我们看一下它的onCreate方法,它使用了我们之前自定义的View的InflaterFactory来代替默认的Factory。记得调用super.onCreate(savedInstanceState);在这个方法之前。SkinBaseActivity也提供了动态添加可以响应换肤需求的View的相关方法。当然需要响应皮肤变化的activity需要继承SkinBaseActivity。具体实现见源码。SkinBaseFragment这个和SkinBaseActivity的思路类似。具体实现见源码。这里我只是提供给大家一个换肤框架的思路,方便大家阅读源码。这个框架就介绍到这里,让我们看看如何使用它。使用的时候一定要记住,Activity必须继承自SkinBaseActivity,Fragment必须继承自SkinBaseFragment,Application必须继承自SkinBaseApplication。当然,让这个框架成为你项目的依赖是绝对必要的。为了演示的简单,这里我只使用以下三种颜色作为可以蒙皮的资源。当然,如果你想使用drawable文件也是可以的,前提是你必须看懂这个demo。让我们看一个布局文件,其中xmlns:skin="http://schemas.android.com/android/skin"是我们自定义的,它在SkinConfig中可用。我们只需要给需要换皮肤的View加上skin:enable=”true”就OK了。我们来看一下MainActivity的部分代码。这里是动态添加需要换皮的View。上面已经介绍了在布局文件中的使用方法以及在代码中的使用方法。我们应该如何改变皮肤?很简单,调用SkinManager的load方法,传入皮肤路径即可。为了简单起见,我的Demo没有在线换皮的功能,只是在本地提供可以换皮的功能。相信大家看到这里已经对如何在线换皮有了一定的了解吧。最后,我们来看看如何开发皮肤包。其实这个是最简单的。皮肤包其实就是一个基本的Android工程,里面没有class文件,只有资源文件。需要注意的是这里的资源文件名一定要和原工程中的一样,并且只包含换皮时需要改变的!比如我的Demo就是简单的对上面三种颜色做了简单的切换。开发了棕色和黑色两款皮肤,所以资源文件中只有三个颜色值。开发完成后,我们需要打包成一个apk文件。为了防止用户点击安装,我们把后缀改成了skin,这样也是可以识别的。如果还有不明白的,可以直接去源码查看。下面我们来看看文章开头的效果图。是不是突然变得有思想了?快动动小指敲一个主题换肤框~~~