一个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模式。

     简单的代码,只要不断思考,慢慢的,所能学到的东西就会变得愈来愈多,最后甚至超出咱们的想象。

相关文章
相关标签/搜索