当前位置:网站首页>Rethink | open the girl heart mode of station B and explore the design and implementation of APP skin changing mechanism
Rethink | open the girl heart mode of station B and explore the design and implementation of APP skin changing mechanism
2022-04-23 07:46:00 【But smell the plum】
reflection Series blog is a new way of learning for me , For the origin and catalogue of this series, please refer to here .
summary
Skin change function It's not a trick , But it has become very popular , Especially when Android Q Launched Dark Mode after , Most mainstream applications in China provide at least daytime and The night Two modes .
For insensitive users , This function is really chicken ribs , But from another point of view , This is also the product carving Ultimate user experience An attempt in the process , For different situations , Users with different preferences provide more selectivity .
With Bili, Bili For example , In addition to providing the above two topics , Also free of charge Maiden heart Pink theme :

From the perspective of product foresight , The exploration of skin changing function in China is ahead of that in foreign countries , Look at... Abstractly Android Q Of Dark Mode , It's just a new theme , therefore , Developers should put their perspective at a higher level : Provide a set of perfect skin changing scheme for the product , Not just Adapt to dark mode .
Think about that , Developers will not focus on the technology itself —— For the whole skin changing system , covers UI、 product 、 Development 、 test 、 Different concerns of multiple roles such as operation and maintenance , These concerns ultimately rely on R & D to help make decisions , Examples are as follows :
- UI: Different definitions UI Different color properties of components , These attributes end up under different themes , Represents different colors ( In daytime mode, the title is black , But in night mode , The title should be white ).
- product : Define the business process of skin changing function , From the simple skin change home page , Skin changing interaction , To different presentations under different themes 、 Payment strategy, etc .
- Development : Provide R & D capability of skin changing function .
- test : Ensure the stability of skin changing function , For example, automated testing and convenient color selection tools .
- Operation and maintenance : Ensure the rapid positioning and timely solution of online problems .
besides , There are more technical points to think about , such as , With more and more themes , It is bound to lead to APK The increase of package volume , Whether it is necessary to introduce remote dynamic loading (download & install) The ability of ? From the perspective of different roles , We can plan ahead , The next coding is more handy .
This article will focus on Android General description of the whole application system , Readers should put aside their concerns about Details of code implementation My persistence , Think from the needs of different roles , Look at a spot and know the whole leopard , Create a robust and powerful technical support for products .
One 、 Definition UI standard
What is the purpose of skin changing specification ? about UI For designers and developers , Design and development should be based on unified and complete specifications :

about UI The designer , stay APP Under different themes , The color of the control is no longer a single value , Instead, use a generic key To define , As shown in the figure above ,「 title 」 The color of the , It should be black during the day #000000, In dark mode, it should be white #FFFFFF, Empathy ,「 Sub title 」、「 Main background color 」、「 Split line color 」, Should follow different themes , Corresponding to different values .
When designers design , Just fill in the corresponding... For each element of the page key, Complete clearly according to the specification UI Design :
| Color Key | Day mode | Dark Mode | remarks |
|---|---|---|---|
| skinPrimaryTextColor | #000000 | #FFFFFF | Title font color |
| skinSecondaryTextColor | #CCCCCC | #CCCCCC | Sub title font color |
| skinMainBgColor | #FFFFFF | #333333 | The main background color of the page |
| skinSecondaryBgColor | #EEEEEE | #000000 | Secondary background 、 The background color of the separator line |
| More … | |||
| skinProgressBarColor | #000000 | #FFFFFF | Progress bar color |
This is more obvious for the efficiency improvement of developers , Developers no longer need to care about the value of specific colors , Just put the corresponding color Fill in the layout :
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World" android:textColor="@color/skinPrimaryTextColor" />
Two 、 Constructing product thinking : Skin bag
How to measure a developer's ability —— Fast response to complex functions 、 Stable delivery ?
If you simply recognize this idea , Then the realization of skin changing function is simple , In title color skinPrimaryTextColor For example , I just need to state two color resources :
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="skinPrimaryTextColor">#000000</color>
<color name="skinPrimaryTextColor_Dark">#FFFFFF</color>
</resources>
The author successfully gets rid of the complex coding implementation , stay Activity I just need 2 Lines of code :
public void initView() {
if (isLightMode) {
// Day mode
tv.setTextColor(R.color.skinPrimaryTextColor);
} else {
// Night mode
tv.setTextColor(R.color.skinPrimaryTextColor_Dark);
}
}
This implementation is not for nothing , In terms of the difficulty of implementation , At least it can protect a few hair follicles of developers .
Of course , There are 「 Optimize space 」, For example, provide encapsulated tools and methods Seemingly get rid of Endless if-else:
/** * Get the real... Under the current skin color resources , all color All must be obtained through this method . **/
@ColorRes
public static int getColorRes(@ColorRes int colorRes) {
// Pseudo code
if (isLightMode) {
// Day mode
return colorRes; // skinPrimaryTextColor
} else {
// Night mode
return colorRes + "_Dark"; // skinPrimaryTextColor_Dark
}
}
// Use this method in the code , Set the color of the title and sub title
tv.setTextColor(SkinUtil.getColorRes(R.color.skinPrimaryTextColor));
tvSubTitle.setTextColor(SkinUtil.getColorRes(R.color.skinSecondaryTextColor));
Obviously ,
return colorRes + "_Dark"This line of code serves asintThe return value of type is invalid , Readers don't need to focus on specific implementations , Because this package is still Not getting rid of the heavy if-else Realization The essence of .
Predictably, , As the number of topics increases , The code related to skin change is becoming more and more bloated , The key question is , The related colors of all controls are strongly coupled to the skin change related code itself , Every UI Containers (Activity/Fragment/ Customize View) And so on Java The code is set manually .
Besides , When the number of skin reaches a certain scale ,color The vast potential of resources will affect apk Volume , Therefore, the dynamic loading and distribution of topic resources is imperative , When users install applications, there is only one theme by default , Other themes Download and install on demand , For example, Taobao. :

Here we are. , Skin bag The concept of should be shipped out , Developers need to treat the color resources of a single theme as a Skin bag , Under different themes , Load and replace different skin packages :
<!-- Daytime mode skin pack colors.xml-->
<resources>
<color name="skinPrimaryTextColor">#000000</color>
...
</resources>
<!-- Dark pattern skin pack colors.xml-->
<resources>
<color name="skinPrimaryTextColor">#FFFFFF</color>
...
</resources>
such , For business code , Developers no longer need to focus on specific topics , You only need to specify the color in the normal way , The system will adjust the color according to the current color resource View Fill in :
<!-- What topic are you currently switching to , The system fills with the corresponding color value -->
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World" android:textColor="@color/skinPrimaryTextColor" />
Back to the original question in this section , Product thinking is also an indispensable ability for an excellent developer : First, list different implementation schemes according to the requirements , Make corresponding trade-offs , Finally, start coding .
3、 ... and 、 Integration thinking
So far, , Everything is still in the stage of requirement proposal and Design , As the needs become clear , Technical difficulties are listed one by one in front of developers .
1. Dynamic refresh mechanism
The first problem developers face : How to achieve after skin change Dynamic refresh function .
Take the wechat registration page as an example , After manually switching to dark mode , Wechat has refreshed the page :

Readers can't help asking , What is the meaning of dynamic refresh , Let the current page rebuild or APP Can't you restart ?
Of course it works , however unreasonable , Because page reconstruction means the loss of page state , The user cannot accept a form page. The filled information is reset ; And if you want to make up for this problem , Rebuild the save of the append state for each page (Activity.onSaveInstanceState()), From an implementation point of view , It's also a huge amount of work .
Therefore, dynamic refresh is imperative —— Whether the user switches the skin package in the application , Or manually switched the dark mode of the system , How do we distribute this notice , Ensure that all pages complete the corresponding refresh ?
2. Save all pages Activity
The reader knows , We can go through Application.registerActivityLifecycleCallbacks() Methods all... In the application were observed Activity Life cycle of , It also means that we can hold all Activity:
public class MyApp extends Application {
// All in the current application Activity
private List<Activity> mPages = new ArrayList();
@Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
mPages.add(activity);
}
@Override
public void onActivityDestroyed(@NonNull Activity activity) {
mPages.remove(activity);
}
// ... Omit other lifecycles
});
}
}
With all the Activity References to , Developers can get a skin change notice , Try to make all pages View To renew the skin .
3. The question of cost
But a huge mystery came into view , For controls , The concept of skin renewal itself does not exist .
What does that mean ? When the skin change notice arrives , I can't make TextView Update text color , It can't make View Update background color —— They are just system controls , The execution is the most basic logic , To put it bluntly , Developers can't code at all .
Some students said , Then I'll just let the whole page View All the trees View Can you re render them all ? Sure , But back to the original question , That's all View Its own state has also been reset ( such as EditText The text is cleared ), Step back , Even if this is acceptable , So the whole View Re rendering of trees can also have a significant impact on performance .
that , How to Save the cost of dynamic page refresh ?
Developers hope , When skin change occurs , Only the specified properties of the specified control are dynamically updated , such as ,TextView Focus only on updates background and textColor,ViewGroup Focus on the background, Other properties do not need to be reset and modified , Make the best use of every performance of the equipment :
public interface SkinSupportable {
void updateSkin();
}
class SkinCompatTextView extends TextView implements SkinSupportable {
public void updateSkin() {
// Update with the latest resources background and textColor
}
}
class SkinCompatFrameLayout extends FrameLayout implements SkinSupportable {
public void updateSkin() {
// Update with the latest resources background
}
}
As the code shows ,SkinSupportable It's an interface , The classes that implement this interface mean that they all support dynamic refresh , When a skin change occurs , We just need to get the current Activity, And by traversing View Trees , Let all SkinSupportable All the implementation classes of updateSkin Method to refresh itself , Then the whole page is refreshed , At the same time, it will not affect View Other properties of itself .
Of course , This also means that developers need to encapsulate conventional controls in a round of coverage , And provide the corresponding dependencies :
implementation 'skin.support:skin-support:1.0.0' // The basic control supports , such as SkinCompatTextView、SkinCompatFrameLayout etc.
implementation 'skin.support:skin-support-cardview:1.0.0' // Third party controls support , such as SkinCompatCardView
implementation 'skin.support:skin-support-constraint-layout:1.0.0' // Third party controls support , such as SkinCompatConstraintLayout
In the long run , Encapsulate controls one by one , Provide dependencies that can be combined and selected , For the designer of the skin changing library , The development cost of the library itself is not high .
4. Pull one hair and move the whole body
But developers in charge of business development complain endlessly .
According to the current design , Isn't it Engineering xml file All controls in need of replacement ?
<!-- Before using -->
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World" android:textColor="@color/skinPrimaryTextColor" />
<!-- Need to be replaced with -->
<skin.support.SkinCompatTextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World" android:textColor="@color/skinPrimaryTextColor" />
Look at it from another Angle , This is an additional cost , If you want to remove or replace the skin changing library one day , So it's like a new refactoring .
Therefore, designers need to avoid similar Pull one hair and move the whole body The design of the , It's best to let developers feel the beauty of the skin changing library without perception Dynamic update .
5. Starting point : LayoutInflater.Factory2
Yes
LayoutInflaterReaders who don't know , You can refer to my This article .
understand LayoutInflater Our readers should know , In parsing xml File and instantiate View In the process of ,LayoutInflater Through self Factory2 Interface , Intercept and create the basic control as the corresponding AppCompatXXXView, It avoids reflection creation View Impact on performance , It also ensures downward compatibility :
switch (name) {
// analysis xml, The basic components all pass through new Way to create
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;
// ...
default:
// Others are created by reflection
}
A picture to cover it :

therefore ,LayoutInflater The implementation idea of itself provides us with a very good starting point , We just need to intercept this logic , Delegate the instantiation of the control to the skin changing library :

As shown in the figure , We use SkinCompatViewInflater Interception replaces the system LayoutInflater Its own logic , With CardView For example , When parsing labels , take CardView The generated logic is delegated to the following dependent Libraries , If the corresponding dependency is added to the project , Then the corresponding SkinCompatCardView, It naturally supports the dynamic skin changing function .
Of course , The realization of all this logic , Originating from the project, add corresponding dependencies , And then in APP Initialize at startup :
implementation 'skin.support:skin-support:1.0.0'
implementation 'skin.support:skin-support-cardview:1.0.0'
// implementation 'skin.support:skin-support-constraint-layout:1.0.0' // Not added ConstraintLayout Skin changing support
// App.onCreate()
SkinCompatManager.withApplication(this)
.addInflater(new SkinAppCompatViewInflater()) // Basic control skin change
.addInflater(new SkinCardViewInflater()) // cardView
//.addInflater(new SkinConstraintViewInflater()) // Not added ConstraintLayout Skin changing support
.init();
With ConstraintLayout For example , When there is no corresponding dependency (), It will be constructed by reflection by default , Generate the corresponding... Of the tag itself ConstraintLayout, Because it is not realized SkinSupportable, Naturally, there will be no skin change and update .
such , The designer of the library provides enough flexibility for the skin changing library , It not only avoids drastic modifications to the existing project , And ensure extremely low use and migration costs , If I wish remove perhaps Replace Skin changing library , Just delete build.gradle Dependence and Application Just initialize the code in .
Four 、 In depth discussion
Next, the author will make an in-depth discussion on more details of the skin changing library itself .
1、 Skin package loading strategy
The strategy pattern It is also well reflected in the design process of skin changing library .
For different skin packs , Its load 、 The installation strategy should be different , for instance :
- 1、 Every
APPThere is a default skin package ( Usually in daytime mode ), The policy needs to be loaded immediately after installation ; - 2、 If the skin pack is remote , The user clicks to switch the skin , Need to pull... From a remote location , After the download is successful, install and load ;
- 3、 Skin package download and installation succeeded , After that, it should be from the local SD Load the card ;
- 4、 Other custom loading policies , For example, the remote skin package is encrypted , Decryption after local loading, etc .
therefore , The designer should abstract the loading and installation of the skin package into a SkinLoaderStrategy Interface , It is convenient for developers to configure on demand more conveniently and flexibly .
Besides , Since the loading behavior itself is most likely a time-consuming operation , Therefore, the scheduling of threads should be controlled , And pass the definition in time SkinLoaderListener Callback , Timely inform the loading progress and results :
/** * Skin package loading strategy . */
public interface SkinLoaderStrategy {
/** * Load skin package . */
String loadSkinInBackground(Context context, String skinName, SkinLoaderListener listener);
}
/** * Skin package loading monitor . */
public interface SkinLoaderListener {
/** * Start loading . */
void onStart();
/** * Loading successful . */
void onSuccess();
/** * Loading failed . */
void onFailed(String errMsg);
}
2、 Further performance savings
In this paper , The author mentioned , Because I have all the Activity References to , So the skin changing library after skin changing , You can try to make all pages View To renew the skin .
actually 「 Update all pages 」 It's usually unnecessary , A more reasonable way is to provide a configurable item , When skin change is successful , By default, only the foreground is refreshed Activity, Other pages are in onResume Update after execution , This can significantly reduce the performance impact of rendering .
Besides , Repeat each skin change View Refreshing the tree is also a time-consuming operation , It can be done by LayoutInflater establish View At the same time as the tree , Will be realized SkinSupportable Of View Exists in a collection to which the page belongs , When a skin change occurs , You only need to target View Just update .
Last , In the above text Activity and View Are held by weak references , To reduce the possibility of memory leakage .
3、 Provide the skin changing ability of picture resources
since color Resources can support skin changes ,drawable Of course, resources should also provide support , In this way, the display of the page can be more diversified , Usually this scenario is applied to the background of the page , For this, readers can refer to Taobao APP Skin changing function and effect :
| resources Key | Day mode | Dark Mode | remarks |
|---|---|---|---|
| skinPrimaryTextColor | #000000 | #FFFFFF | Title font color |
| skinSecondaryTextColor | #CCCCCC | #CCCCCC | Sub title font color |
| skinMainBgDrawable | A picture | B picture | Main background of the page |
| skinProgressBarDrawable | C Animation | D Animation | Load box animation |
| More … |
Summary
A summary is not a summary , There's more to expand , such as :
- 1、
AndroidIn the systemResourcesClass is how to implement the replacement of resources , What has been done in the skin changing library ? - 2、
LayoutInflaterThe source code clearly states , OneLayoutInflaterCan only be set oncesetFactory2(), Otherwise, an exception will be thrown , that , When is the skin changing library carried outFactory2What about the injection , Why is it designed like this ? - 3、 How to further expand the functions of the skin changing library according to the needs , For example, provide support for single page without skin change , And provide support for multiple pages using different skin packages ?
- 4、 How to provide more test phases 、 Tools available in the operation and maintenance phase ?
- 5、 As of the time of the author's writing ,2021 Google IO A new proposal was put forward at the conference UI Design concept Material You, take The theme The concept of from
APPUp to the entire operating system , Whether it has a new impact on the existing skin changing function ?
There is no end to realization , What developers can do is through continuous multi-directional reflection , Provide products with the possibility to show more value , To go a step further , Complete the phased leap of their own professional ability .
thank
The construction of this design idea , Refer to the GitHub At present star The largest number of skin changing Libraries Android-skin-support , Thanks to the author ximsfei It provides such an excellent design for developers .
Thank you. Bili, Bili 、 Nuggets 、 TaoBao 、 WeChat Several excellent applications provide a variety of skin changing functions for this article .
Thank you again for .
About me
Hello, I am a But smell the plum , If you think the article is valuable to you , welcome ️, You are welcome to pay attention to my Blog perhaps GitHub.
If you think the article is not good enough , Please also go through Focus on Urge me to write better articles —— In case I improve one day ?
版权声明
本文为[But smell the plum]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/04/202204230622454534.html
边栏推荐
猜你喜欢

Scrapy 修改爬虫结束时统计数据中的时间为当前系统时间

How to judge whether a point is within a polygon (including complex polygons or a large number of polygons)

js之DOM事件

页面实时显示当前时间

MySQL index

SAP PI/PO Soap2Proxy 消费外部ws示例

Super classic & Programming Guide (red and blue book) - Reading Notes

SAP PI/PO功能运行状态监控检查

防抖和节流

SVG中Path Data数据简化及文件夹所有文件批量导出为图片
随机推荐
Xamarin版的C# SVG路径解析器
Install and configure Taobao image NPM (cnpm)
颜色转换公式大全及转换表格(31种)
给定区段范围内字符串自生成代码
9. Common functions
C#使用拉依达准则(3σ准则)剔除异常数据(.Net剔除一组数据中的奇异值)
系统与软件安全研究(二)
typescript字典的使用
UnityShader基础
js之排他思想及案例
简单理解==和equals,String为什么可以不用new
12. Constraints
系统与软件安全研究(一)
ABAP 从CDS VIEW 发布OData Service示例
js之预解析
Apache Hudi 如何加速传统的批处理模式?
js之DOM学习三种创建元素的方式
利用Lambda表达式解决c#文件名排序问题(是100大还是11大的问题)
Mongodb 启动警告信息处理
MySQL storage engine