天天看点

如何在一个成熟的应用内换肤?前言成熟应用换肤流程结语

全文无图,请慎重阅读,^_^~~~

前言

在16年10月我负责的应用实现了一次夜间模式,那次我基于各种调研结果自己写了一套换肤框架,开源了一年,也有一些小伙伴用到了这套框架,能给大家提供点便利,还是挺开心的。不过本人懒癌已入膏肓,加上平时业务需求忙不过来,开源的框架的维护并没做好,大家多多包涵。

最近我负责的另一个应用又一次实现了夜间模式,在此期间,我对之前框架的不足和缺陷做了一些修复,代码已经提交到Github,大家有需要可以check一下。主要修复了几个点:

  1. 部分界面的LayoutInflator.createView无法创建,这是创建时没有context导致的,通过反射设置context解决了此问题,现在不存在include内的布局不能换肤的问题了;
  2. 动态addViewAttr时没有对相同属性做处理,View内可能会记录多个相同的换肤属性,换肤时会浪费性能,严重的情况下可能会卡死,现在通过Map存储换肤属性列表,确保一个属性名称只有一个换肤属性;
  3. 在之前的版本中DynamicAttr不能转存到SkinAttr中,限制了自定义属性处理的能力,这次我提供了一个keepInstance标志位,如果标志位为true,则SkinAttr内会存储原始的DynamicAttr,以此来提升自定义属性处理扩展性,这下我们可以扩展DynamicAttr存储Text,换肤时重新设置SpanableString了,具体代码不贴了,大家可以到Github上去看SpannableSkinAttrHandler类实现;

以上是前话,这次文章的主题是:如何在一个成熟的应用内实现换肤功能?

成熟应用换肤流程

一般情况下,换肤都是在产品做的量比较不错的时候才会考虑的体验提升性需求,面对的都是一个已经比较成熟的应用,这个应用有很多个交互界面、很多张切片、很多种颜色和Shape定义等等等等。个人经历了两次实现夜间模式的需求,略有一些心得,结合QSkinLoader框架总结了一套实现步骤,在此分享给大家,供大家参考参考。按照这套步骤来集成换肤,应该会节约一些工作量。

第一步,整理主题和Style中的换肤相关属性

QSkinLoader 不支持主题和style换肤,包括各种style方式设置的文字颜色,界面背景等,第一步需要排查是否存在这样的使用,如果有,需要调整为与换肤无关的主题,比如style中的windowBackground之类应使用QSkinLoader的背景切换方式,TextView的apperence应替换为textColor和textSize两个属性设置,不能用style=”@style/*”这种表示方法。

这一步可以放在第四步之后进行,之所以我在放在第一步,是为了希望小伙伴们清楚的明白QSkinLoader的缺点,别做了好多工作,发现这个框架不能用,那就杯具了。

注意,这里并不是说使用QSkinLoader就不能使用style,这里强调的是,不能在style内使用影响换肤相关的属性,比如大家仍然可以用style设置activity是否fullScreen,是否noTitleBar等等。

第二步,集成QSkinLoader

集成方法之前写过了,见链接,此处略。

第三步,颜色引用全部定义为引用

QSkinLoader内对颜色换肤必须使用引用,所以需要梳理产品内所有的颜色使用,全部归类到xml中定义;不要有Color.parseColor/android.R.color.*之类的使用;建议全局搜索字符串#、parseColor、android.R.color等进行替换,也可以用颜色正则表达式,这个网上很多。

第四步,删除无用资源

梳理项目中所有无用的资源,剔除,包括颜色、drawable、布局等所有资源,这一步能给视觉同事减少很多无用工作量。

第五步,按颜色规范裁剪颜色使用

如果项目视觉有颜色使用规范的,梳理所有的颜色使用是否符合规范,不符合规范的,和视觉同事一起规整,形成一个最小的颜色集合,能减少视觉和开发同事映射颜色时的工作量。

第六步,视觉同事提供对应颜色集合

视觉同事提供所有在用的颜色对应的换肤颜色,背景色对背景色,文字颜色对文字颜色,前景色对前景色。

第七步,视觉同事提供对应的切片集合

将代码中所有在用的切片资源打包给视觉同事,视觉同事输出所有对应的换肤切片,切片大小不能变化。这一步需要注意,可能部分切片是用蒙层来实现换肤的,就不需要输出对应的换肤切片了。比如夜间模式,有很多切片是可以用盖蒙层的方案来减少亮度的,就不需要提供对应的夜间模式切片。

第八步,考虑图片蒙层

如果换肤需要对动态加载的服务端图片盖蒙层(比如夜间模式需求需要盖半透明黑色蒙层减少图片亮度),建议在XML中将ImgeView/ImageButton的定义改为org.qcode.qskinloader.view.ShadowImageView,然后加上属性skin:drawShadow=”@color/night_shadow_color”。注意night_shadow_color的颜色由视觉同事给出,正常模式下值为白色,换肤模式下不支持半透明效果,真正生效的蒙层效果是RGB部分。如果定义为#A5000000,生效的是000000,图片就看不到啦,我自己也在这被坑过,^_^。

第九步,开发同事集成换肤对应的切片和颜色

对于使用APK 皮肤包 的方式,开发同事在皮肤工程中放入所有的对应切片(drawable-xhdpi/drawable-xxdpi等文件夹下一一对应,对应第六步的切片集),对应色值(对应第四步的颜色集),对应color文件夹(res/color下定义的ColorStateList,一般是直接全部复制过去即可),对应drawable文件夹(res/color下定义的各种shape,一般也是直接全部复制过去即可),生成一个皮肤包;

对于使用 前后缀换肤 的方式,开发同事按前缀拼接好上述资源,存在在对应位置即可;

到这一步后,基本上换肤效果就能展示出来了,不过可能还有很多界面不协调,因为可能我们在代码里动态设置了资源,而这些资源也要通知框架存储对应换肤属性。

第十步,替换动态设置的属性

开发同事替换代码中所有动态设置背景资源、前景资源、文字颜色等的代码,关键词有:

setImageDrawable

setImageResource

setBackgroundColor

setBackground

setBackgroundResource

setBackgroundDrawable

setTextColor

将这些代码全部用SkinManager.with(view).addViewAttrs(attrName, resId).applySkin(false);这样方法替换掉。注意几点:

  1. attrName定义在SkinAttrName中,resId就是对应的资源;
  2. 调用setViewAttrs/addViewAttrs需要视具体情况而定,如果这个View仅这一个换肤属性,则用setViewAttrs,否则建议用addViewAttrs。
  3. 此处都应调用applySkin,传入true表示子元素也换肤,传入false表示仅当前元素换肤,此处是对当前元素设置属性,一般传入false即可。为什么这里需要调用applySkin?是因为QSkinLoader对于默认皮肤的情况下并未全局换肤,如果此处不调用applySkin,可能存在元素属性未生效的情况。

完成前面所有步骤后,整个换肤工作基本已经完成了一大半,但是可能在局部区域还是无法换肤,一般都是自定义View属性导致的,此时就进行最后一步任务。

第十一步,使用自定义属性处理器处理复杂换肤需求

走查各界面,看哪些地方有问题,使用自定义属性处理器处理。现在的自定义属性处理器功能还是很强大的:

  1. 可以对某个属性定义一种换肤方式,比如对自定义View的defTextColor属性换肤;
  2. 也可以对指定View在换肤时执行某个动作,比如RecyclerView换肤时清除缓存View,或者某个自定义View换肤时执行notifyDatasetChanged。
  3. 甚至可以同时集成换肤属性和动作,比如调整SpannableString内的颜色,我们需要解析换肤对应的颜色,并执行动作setText来换肤。

以上十一步全部完成后,一个成熟的应用也就能支持换肤了,如果是APK方式换肤,我们就可以从服务端下载皮肤包,达到动态换肤效果了,是不是很清晰,哈哈!

结语

这就是我两次集成夜间模式形成的一个总结,算是一个规范流程,按照这套方法集成QSkinLoader,工作量评估会更加准确,工作流程也更加清晰,不过还要注意一点:如果有多位开发同事一起处理第十步的时候,怎么划分模块,减少代码提交冲突,需要额外思考。

eh~~, that is all, thanks for reading!

继续阅读