最佳的UI更新套路

简介: 是这样的,有时候,我们需要创建一个符合业务的View,或者称为UI对象,比如,即时通讯软件的好友列表里面的每个item,那么这个item要有头像,名字,简短描述三个数据项。

之前有段时间写Windows桌面程序悟出的道理。

是这样的,有时候,我们需要创建一个符合业务的View,或者称为UI对象,比如,即时通讯软件的好友列表里面的每个item,那么这个item要有头像,名字,简短描述三个数据项。那么,我们这个View对象,就得有三个对应的方法来设置这三个属性,然后View显示的时候,就显示出我们最新的数据就好了。

最最最开始的时候,我都是耿直的写成这样。

 
 
  1. // 以下是伪代码的形式,并不严谨 
  2. class UserItemView extends View { 
  3.     ImageView mAvatar; 
  4.     TextView mName; 
  5.     TextView mDesc; 
  6.      
  7.     public void setAvatar(Bitmap avatarImage) { 
  8.         mAvatar.setImage(avatarImage); 
  9.     } 
  10.      
  11.     public void setName(String name) { 
  12.         mName.setText(name); 
  13.     } 
  14.      
  15.     public void setDesc(String desc) { 
  16.         mDesc.setText(desc); 
  17.     } 
  18.  

在最开始的时候,这样是OK的,看起来非常完美,没有任何问题。如果某个用户的数据变了,就更新某个数据就好了。

好,现在问题了,这样写的问题在于,当状态/数据之间相互依赖对UI的显示产生影响时,那么就会出问题,代码会混乱。

我们现在提个这样的需求,如果描述(desc)是空的,就显示出头像,如果不是空的,就不显示头像。

需求确实很奇怪,但是实际的工作中一定会遇到类似的情况。

那么,首先我们想当然的改动一下代码吧。

 
 
  1. class UserItemView extends View { 
  2.     ImageView mAvatar; 
  3.     TextView mName; 
  4.     TextView mDesc; 
  5.      
  6.     String mDescData; 
  7.      
  8.     public void setAvatar(Bitmap avatarImage) { 
  9.         mAvatar.setImage(avatarImage); 
  10.     } 
  11.      
  12.     public void setName(String name) { 
  13.         mName.setText(name); 
  14.     } 
  15.      
  16.     public void setDesc(String desc) { 
  17.         mDescData = desc
  18.         if (mDescData == null || mDescData .equals("")) { 
  19.             mAvatar.setVisible(true); 
  20.         } else { 
  21.             mAvatar.setVisible(false); 
  22.         } 
  23.         mDesc.setText(mDescData ); 
  24.     } 
  25.  

你看,现在设置描述的方法里要去管头像的显示情况,这就很恶心了。如果有更多的数据项和状态,更多的UI控件,他们之间有非常多的依赖关系,如果按这样的写法,你的每个方法里面的逻辑都会变得非常恶心,复杂。甚至,设置某个UI的状态还需要依赖其他的数据项,你没办法,只能把无关的数据当参数传入,就会变成这样。

 
 
  1. // !!!爆炸!!!为什么头像要关注其他的数据!!! 
  2. public void setAvatar(Bitmap avatarImage, String desc) { 
  3.     mAvatar.setImage(avatarImage); 
  4.     if (desc== null || desc.equals("")) { 
  5.         mAvatar.setVisible(true); 
  6.     } else { 
  7.         mAvatar.setVisible(false); 
  8.     } 
  9.  

当时,我那个UI已经变得非常恶心了,在这样的情况下,我终于意识到,对UI对象的更新,不能就地去做,UI对象的更新,应该用一个统一的方法来做,而会改变UI显示情况的那些setXXXX方法,只做两件事,一是把数据设到这个对象的成员属性上,另一件事就是调用统一的方法来更新UI。

其实一个标准的设计一直在眼前,直到那一刻,我才意识到和真正的理解。那就是Android中的View。Android中的每个View的子类,都有超级多的set方法,比如TextView,就有setText,setTextColor等等。它就是每个set方法,实际上是给这个对象做一个数据上的变化,然后就不管了。等到系统来调用OnDraw方法的时候,在OnDraw方法中统一的来更新UI。

接下来就简单了。我们的代码改成这样:

 
 
  1. class UserItemView extends View { 
  2.     ImageView mAvatar; 
  3.     TextView mName; 
  4.     TextView mDesc; 
  5.      
  6.     String mNameData; 
  7.     String mDescData; 
  8.     Bitmap mAvatarData; 
  9.      
  10.     public void updateView() { 
  11.         mAvatar.setImage(mAvatarData); 
  12.         if (mDescData== null || mDescData.equals("")) { 
  13.             mAvatar.setVisible(true); 
  14.         } else { 
  15.             mAvatar.setVisible(false); 
  16.         } 
  17.          
  18.         mDesc.setText(mDescData); 
  19.          
  20.         mName.setText(mNameData); 
  21.     } 
  22.      
  23.     public void setAvatar(Bitmap avatarImage) { 
  24.         mAvatarData = avatarImage; 
  25.         updateView(); 
  26.     } 
  27.      
  28.     public void setName(String name) { 
  29.         mNameData = name
  30.         updateView(); 
  31.     } 
  32.      
  33.     public void setDesc(String desc) { 
  34.         mDescData = desc
  35.         updateView(); 
  36.     } 
  37.  

注意到,添加了一个updateView方法,这个方法专门用来将数据更新到UI上,这样写,其他set方法一律只做把数据存进来的事情,updateView方法专门根据当前的数据状态更新UI,这样set方法就干净整洁。而逻辑再复杂的显示逻辑,都不用怕,在updateView里面搞就行了。

我曾经并且一直在维护的一个Activity,它大概有15+个View,20-30个数据和状态,这些数据和状态会谜一般的印象着这些view的显示。当时年轻不懂事,就耿直的在数据变化的后面(下一行),立马就更新UI的状态。有如下这些位置吧:

  • 点击事件
  • 系统回调
  • 网络请求回调
  • 定时器,handler

这么多地方都在更新数据,并且改变UI,维护起来简直要屎。

后来,在我领悟到上面这个技巧后,我重构了一波,里面有个超级大的updateView方法,然后对每个View进行更新。

这里有另一个小技巧,就是你有1w个View,和1w个状态,你按状态分类写,还是按View分类写。这个意思就是:按正常人思维吧,比如你的页面有两种模式,一般人就会写

 
 
  1. if (mode == A) { 
  2.     viewA.xxxxxx 
  3.     viewB.xxxxxx 
  4.     ... 
  5. else if (mode == B) { 
  6.     viewA.xxxxxx 
  7.     viewB.xxxxxx 
  8.     ... 
  9.  

这样写又出问题了,少年,你以为你的状态只有两种吗?你以为每个UI对象,就只依赖一个状态吗?会这么优雅的刚好分布在if和else里面吗?你错了,需求这个东西啊,是非常恶心的,完全不符合逻辑的。

这样写呢,当你状态很多很多的时候,你很难去安排这个view的更新放哪里,如果有新加的状态又影响到它。为了不影响到之前的逻辑,你往往就是再后面再加上一行,这样多了也是难以维护的。

那样怎么做呢?最简单,按View来分类写。你有1w个View吧,那就挨个挨个来,先把第一个View处理好了,第一个View受那些影响呢?全部写出来,if-else也好,什么都好,反正最开始这几行代码,先把View1搞定。

然后再一次写View2,View3的逻辑,这样唯一的缺点就是,你的判断得重复写。看上去很累赘,view1和view2,很可能他们的显示逻辑是相同的,你非常想把他们写在一个if-else里,但是我建议你不要这么做,为了最高的变通性,请将他们分开写。

当你日后维护的时候,哪个View出问题了,你只要找到那一坨就好了,别的都不需要管。




作者:Kross
来源:51CTO
目录
相关文章
|
8月前
|
前端开发 JavaScript
【React工作记录八十】 一步步教你用taro封装一个公司库的下拉组件
【React工作记录八十】 一步步教你用taro封装一个公司库的下拉组件
48 0
|
8月前
|
前端开发
前端学习笔记202307学习笔记第六十天-react源码-commit的第一个子阶段3
前端学习笔记202307学习笔记第六十天-react源码-commit的第一个子阶段3
39 0
|
8月前
|
前端开发
前端学习笔记202307学习笔记第六十天-react源码-commit的第一个子阶段2
前端学习笔记202307学习笔记第六十天-react源码-commit的第一个子阶段2
34 0
|
8月前
|
前端开发
前端学习笔记202307学习笔记第六十天-react源码-commit的第一个子阶段
前端学习笔记202307学习笔记第六十天-react源码-commit的第一个子阶段
32 0
|
7月前
|
JSON 数据格式
通过一个具体的例子,深入了解 SAP UI5 控件数据双向绑定的工作原理和问题排查方法试读版
通过一个具体的例子,深入了解 SAP UI5 控件数据双向绑定的工作原理和问题排查方法试读版
31 0
|
11月前
|
前端开发
【React工作记录五十七】添加按钮的两种方式
【React工作记录五十七】添加按钮的两种方式
117 0
|
11月前
|
前端开发
【React工作记录六十七】前端实现复制文字操作
【React工作记录六十七】前端实现复制文字操作
101 0
|
11月前
|
前端开发
#yyds干货盘点 【React工作记录三十二】ant design table中增加按钮的两种方式
#yyds干货盘点 【React工作记录三十二】ant design table中增加按钮的两种方式
74 0
|
JSON 前端开发 数据格式
#yyds干货盘点 【React工作记录十二】前端对接口参数错误如何解决
#yyds干货盘点 【React工作记录十二】前端对接口参数错误如何解决
148 0
|
前端开发
#yyds干货盘点# 【React工作记录五十七】添加按钮的两种方式
#yyds干货盘点# 【React工作记录五十七】添加按钮的两种方式
169 0
#yyds干货盘点# 【React工作记录五十七】添加按钮的两种方式