170
技術社區[雲棲]
最佳的UI更新套路
是這樣的,有時候,我們需要創建一個符合業務的View,或者稱為UI對象,比如,即時通訊軟件的好友列表裏麵的每個item,那麼這個item要有頭像,名字,簡短描述三個數據項。那麼,我們這個View對象,就得有三個對應的方法來設置這三個屬性,然後View顯示的時候,就顯示出我們最新的數據就好了。
最最最開始的時候,我都是耿直的寫成這樣。
- // 以下是偽代碼的形式,並不嚴謹
- class UserItemView extends View {
- ImageView mAvatar;
- TextView mName;
- TextView mDesc;
- public void setAvatar(Bitmap avatarImage) {
- mAvatar.setImage(avatarImage);
- }
- public void setName(String name) {
- mName.setText(name);
- }
- public void setDesc(String desc) {
- mDesc.setText(desc);
- }
- }
在最開始的時候,這樣是OK的,看起來非常完美,沒有任何問題。如果某個用戶的數據變了,就更新某個數據就好了。
好,現在問題了,這樣寫的問題在於,當狀態/數據之間相互依賴對UI的顯示產生影響時,那麼就會出問題,代碼會混亂。
我們現在提個這樣的需求,如果描述(desc)是空的,就顯示出頭像,如果不是空的,就不顯示頭像。
需求確實很奇怪,但是實際的工作中一定會遇到類似的情況。
那麼,首先我們想當然的改動一下代碼吧。
- class UserItemView extends View {
- ImageView mAvatar;
- TextView mName;
- TextView mDesc;
- String mDescData;
- public void setAvatar(Bitmap avatarImage) {
- mAvatar.setImage(avatarImage);
- }
- public void setName(String name) {
- mName.setText(name);
- }
- public void setDesc(String desc) {
- mDescData = desc;
- if (mDescData == null || mDescData .equals("")) {
- mAvatar.setVisible(true);
- } else {
- mAvatar.setVisible(false);
- }
- mDesc.setText(mDescData );
- }
- }
你看,現在設置描述的方法裏要去管頭像的顯示情況,這就很惡心了。如果有更多的數據項和狀態,更多的UI控件,他們之間有非常多的依賴關係,如果按這樣的寫法,你的每個方法裏麵的邏輯都會變得非常惡心,複雜。甚至,設置某個UI的狀態還需要依賴其他的數據項,你沒辦法,隻能把無關的數據當參數傳入,就會變成這樣。
- // !!!爆炸!!!為什麼頭像要關注其他的數據!!!
- public void setAvatar(Bitmap avatarImage, String desc) {
- mAvatar.setImage(avatarImage);
- if (desc== null || desc.equals("")) {
- mAvatar.setVisible(true);
- } else {
- mAvatar.setVisible(false);
- }
- }
當時,我那個UI已經變得非常惡心了,在這樣的情況下,我終於意識到,對UI對象的更新,不能就地去做,UI對象的更新,應該用一個統一的方法來做,而會改變UI顯示情況的那些setXXXX方法,隻做兩件事,一是把數據設到這個對象的成員屬性上,另一件事就是調用統一的方法來更新UI。
其實一個標準的設計一直在眼前,直到那一刻,我才意識到和真正的理解。那就是Android中的View。Android中的每個View的子類,都有超級多的set方法,比如TextView,就有setText,setTextColor等等。它就是每個set方法,實際上是給這個對象做一個數據上的變化,然後就不管了。等到係統來調用OnDraw方法的時候,在OnDraw方法中統一的來更新UI。
接下來就簡單了。我們的代碼改成這樣:
- class UserItemView extends View {
- ImageView mAvatar;
- TextView mName;
- TextView mDesc;
- String mNameData;
- String mDescData;
- Bitmap mAvatarData;
- public void updateView() {
- mAvatar.setImage(mAvatarData);
- if (mDescData== null || mDescData.equals("")) {
- mAvatar.setVisible(true);
- } else {
- mAvatar.setVisible(false);
- }
- mDesc.setText(mDescData);
- mName.setText(mNameData);
- }
- public void setAvatar(Bitmap avatarImage) {
- mAvatarData = avatarImage;
- updateView();
- }
- public void setName(String name) {
- mNameData = name;
- updateView();
- }
- public void setDesc(String desc) {
- mDescData = desc;
- updateView();
- }
- }
注意到,添加了一個updateView方法,這個方法專門用來將數據更新到UI上,這樣寫,其他set方法一律隻做把數據存進來的事情,updateView方法專門根據當前的數據狀態更新UI,這樣set方法就幹淨整潔。而邏輯再複雜的顯示邏輯,都不用怕,在updateView裏麵搞就行了。
我曾經並且一直在維護的一個Activity,它大概有15+個View,20-30個數據和狀態,這些數據和狀態會謎一般的印象著這些view的顯示。當時年輕不懂事,就耿直的在數據變化的後麵(下一行),立馬就更新UI的狀態。有如下這些位置吧:
- 點擊事件
- 係統回調
- 網絡請求回調
- 定時器,handler
這麼多地方都在更新數據,並且改變UI,維護起來簡直要屎。
後來,在我領悟到上麵這個技巧後,我重構了一波,裏麵有個超級大的updateView方法,然後對每個View進行更新。
這裏有另一個小技巧,就是你有1w個View,和1w個狀態,你按狀態分類寫,還是按View分類寫。這個意思就是:按正常人思維吧,比如你的頁麵有兩種模式,一般人就會寫
- if (mode == A) {
- viewA.xxxxxx
- viewB.xxxxxx
- ...
- } else if (mode == B) {
- viewA.xxxxxx
- viewB.xxxxxx
- ...
- }
這樣寫又出問題了,少年,你以為你的狀態隻有兩種嗎?你以為每個UI對象,就隻依賴一個狀態嗎?會這麼優雅的剛好分布在if和else裏麵嗎?你錯了,需求這個東西啊,是非常惡心的,完全不符合邏輯的。
這樣寫呢,當你狀態很多很多的時候,你很難去安排這個view的更新放哪裏,如果有新加的狀態又影響到它。為了不影響到之前的邏輯,你往往就是再後麵再加上一行,這樣多了也是難以維護的。
那樣怎麼做呢?最簡單,按View來分類寫。你有1w個View吧,那就挨個挨個來,先把第一個View處理好了,第一個View受那些影響呢?全部寫出來,if-else也好,什麼都好,反正最開始這幾行代碼,先把View1搞定。
然後再一次寫View2,View3的邏輯,這樣唯一的缺點就是,你的判斷得重複寫。看上去很累贅,view1和view2,很可能他們的顯示邏輯是相同的,你非常想把他們寫在一個if-else裏,但是我建議你不要這麼做,為了最高的變通性,請將他們分開寫。
當你日後維護的時候,哪個View出問題了,你隻要找到那一坨就好了,別的都不需要管。
最後更新:2017-08-30 10:32:26