一個ListView佈局的不斷演化

    剛出來工做,就負責一個APP的某塊功能的編寫,該功能就是相似微博那樣的界面。微博界面的編寫其實是很是複雜的,雖然它只是一個ListView,但要想讓這個ListView滑得動,是的,在一些配置低的手機,隨便一個負載內容多的Item,都有可能致使OOM。。。若是隻是簡單的爲了實現了效果,能夠選擇將全部內容都寫在xml文件,若是佈局很差的話,就會出現嵌套過多的狀況,一樣也會出現OOM的狀況。。。就算不會出現OOM的狀況,也能滑得動,也會面臨是否可以滑得快。。。算法

     要想能滑得動,也能滑得快,就要動點腦筋了。緩存

     一開始很是簡單的代碼就是這樣:app

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder;
    if (convertView == null) {
        convertView = inflater.inflate(R.layout.list_item, null);
        holder = new ViewHolder();
        ……
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder)convertView.getTag();
    }
}
/**
 * ViewHolder
 */
private static class ViewHolder {
    ImageView appIcon;
    TextView  appName;
    TextView  appInfo;
}

      這是谷歌推薦的方式,實際上也解決了不少性能上的問題。異步

      Android原生的ListView本來就作了相應的緩存機制,Recycler。ide

      Recycler的工做原理大體以下:組件化

      假設屏幕最多能看到11個item,那麼當第1個item滾出屏幕,這個item的View進入RecycleBin中,第12個要出現前,經過 getView從回收站(RecycleBin)中重用這個View,而後設置數據,而沒必要從新建立一個View。佈局

      這樣即便有上萬個Item,inflate的次數最多就是n,也就是一個屏幕可以容納的個數。性能

      大部分的狀況均可以用這樣的代碼解決,但我以爲對於每一個Adapter都要寫一個ViewHolder實在是太麻煩了,因而進一步將代碼改寫爲這樣:測試

@Override
public View getView(int position, View convertView, ViewGroup parent) {
     if (convertView == null) {
        convertView = inflater.inflate(R.layout.list_item, null);
        }

        ImageView icon = (ImageView)CommonViewHolder.get(convertView, R.id.image_view);
}

     其中CommonViewHolder的代碼以下:優化

public class CommonViewHolder {

    /**
     * 用於獲取ItemView中的控件
     *
     * @param view ItemView
     * @param id   要獲取的控件的id
     * @param <T>  返回的控件的類型
     * @return 返回的控件
     */
    public static <T extends View> T get(View view, int id) {
        SparseArrayCompat<View> viewHolder = (SparseArrayCompat<View>) view.getTag();
        if (viewHolder == null) {
            viewHolder = new SparseArrayCompat<>();
            view.setTag(viewHolder);
        }
        View childView = viewHolder.get(id);
        if (childView == null) {
            childView = view.findViewById(id);
            viewHolder.put(id, childView);
        }
        return (T) childView;
    }
}

      實際上,ViewHolder就是經過setTag方法將相應的控件做爲View的屬性保存起來,而後下次使用的時候就能夠直接複用。

      既然明白了它的原理,就能夠對它進行改造,CommonViewHolder是利用SparseArrayCompact存儲控件。SparseArrayCompact是SparseArrayCompact是SparseArray的兼容類,本質上就是相似Map的鍵值對容器,谷歌宣稱它的性能比Map要好,由於內在的算法已經優化了。

      這裏的工做很簡單,一樣是利用setTag方法保存View中的控件,可是卻把findViewById這樣的工做放在了CommonViewHolder中,這樣就不用每次都要調用findViewById方法了。

      爲了考慮通用,還使用泛型。

      代碼量瞬間就減小了不少,但這時就面臨了一個問題:Item項錯亂了。。。

      在快速滑動的時候,圖片加載錯了,仔細調試,就發現是Recycler機制出現了問題。當快速滑動的時候,本來應該開始加載圖片的控件已經滑出屏幕,而後個人圖片加載是異步的,因此圖片就會加載在後面的Item上。

      解決這個問題的方法一樣得利用setTag方法:

icon.setTag(imageUrl)

....
if(imageUrl.equals(icon.getTag()){
      ....
}

     經過setTag保存ImageView要加載的url信息,而後在下次加載的時候判斷是否相同。

     問題解決了,但看到getView方法中爲了實現微博這樣承載大量信息的界面,不得不編寫大量的業務代碼,並且這樣複用性不好,由於微博詳情的界面和列表項的界面基本同樣,只是有一些不一樣而已。若是再寫一個,就有點傻逼了,但若是不寫,getView方法中確定又要寫更多的判斷。

     爲了解決複用的問題,就開始組件化。

     將微博界面拆成兩個部分:HeaderView和BodyView。HeaderView負責微博做者的基本信息,而BodyView就是微博內容。

     這樣,我只要在getView方法中這樣寫:

add(new HeaderView());
...
add(new BodyView());
...

     也就是說,我從一個靜態佈局改爲動態佈局,這樣我在詳情那裏也能夠複用。

     到了這裏本來也應該結束了,但我又想要爲微博業務編寫測試,但Android中View和業務的代碼是各類糾纏,很難徹底脫離View來測試業務。

     通過思考和查找資料,我找到了一種方式:ViewModel。

     編寫相應的ViewModel做爲Controller,就能夠將View和業務的代碼解綁:

public class ViewModel{
     private String text;
     ...
     public void setText(TextView view){
            view.setText(text);
     }
}

      這樣組件裏面的代碼就更少了,它只要聲明好控件而後傳進來就好了,數據的獲取和綁定都在ViewModel這裏。

      而後咱們來寫一個簡單的測試:

ViewModel model = new ViewModel();
Button button = new Button(context);
button.setText("你好");
model.changeButtonText(button);
assertEquals("我好",  button.getText());

public void changeButtonText(Button button){
       button.setOnClickListener(new OnClickListener(){
               button.setText("我好");
       });
}

     利用ViewModel,咱們能夠方便的測試Android種的業務。

     這是到目前爲止的思考和嘗試,實際上,我認爲代碼還會不斷演化下去,如今已經開始出現MVVM的一些思想的應用,到了最後,沒準就會徹底演化成MVVM模式。

     簡單的代碼,只要不斷思考,慢慢的,所能學到的東西就會變得愈來愈多,最後甚至超出咱們的想象。

相關文章
相關標籤/搜索