当前位置: 主页 >业界资讯 >查看内容
如何正确改善listview性能

IT蓝豹发表于 2015/12/15 15:08

ListView几乎在每个应用中都会用到。用ListView易于展现一些如联系人、菜单等tems信息。这好像可以理所当然地认为Android应该有现成封装好的方式来展现这类数据,最新的实现版就是RecyclerView。它被构建用于高效重复利用views,而不是每次当某个View item进入屏幕时重新创建它。在RecyclerView之前,我们一般使用ListView,而且在今天ListView仍然被广泛使用。虽然ListView也回收views,但它却维持一个空间作为Android中一个最misconfigured的views。我们知道,这个话题在之前已经讨论了很多,但是我今天在这篇博客提及它,是因为在今天我们仍然有很多人在使用ListView时姿势不正确。

下面是LisView的一个所谓标准写法的ArrayAdapter实现代码段。

@Override
    public View getView(int position, View convertView, ViewGroup parent) {
 
        LayoutInflater inflater = (LayoutInflater) context
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View rowView = inflater.inflate(R.layout.view_test_row, parent, false);
 
        TextView testName = (TextView)rowView.findViewById(R.id.text_view_test_name);
        TextView testDesc = (TextView)rowView.findViewById(R.id.text_view_test_desc);
 
        //modify TextViews, in some arbitrary way
 
        return rowView;
    }

像这样写一个AdapterView,当你的list一次性显示在屏幕上时这种写法并没有什么问题,但是当拥有大量的items而且每个item的view都非常复杂的时候。这将引发性能问题,上面代码片段里面的方法非常耗性能。当用户滑动屏幕的时候,对于每个子view都要通过LayoutInflater类加装一遍,并且每次都会调用findViewById()来寻找组件。每次findViewById()被调用的时候,View各层次都会遍历一次直到找到对应的ID,对于每个子View都必须这样去找!如果用户滑动手速比较快的话,就可能出现明显的卡顿现象。

为了解决这个问题,我们可以用一个static的类来关联我们之前未曾用到的convertView。

static class ViewHolder(){
 
        TextView testName;
        TextView testDesc;
        
}
 
    @Override
     public View getView(int position, View convertView, ViewGroup parent) {
 
        View rowView = convertView;  //reference to one of the previous Views in the list that we can reuse.
 
        if(convertView == null) {
 
            LayoutInflater inflater = (LayoutInflater) context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            rowView = inflater.inflate(R.layout.view_test_row, parent, false);
 
            ViewHolder viewHolder = new ViewHolder();
            viewHolder.testName = (TextView) rowView.findViewById(R.id.text_view_test_name);
            viewHolder.testDesc = (TextView) rowView.findViewById(R.id.text_view_test_desc);
 
            rowView.setTag(viewHolder);
        }
 
        ViewHolder holder = (ViewHolder) rowView.getTag();
        
        //in real code these strings should be in res
        holder.testName.setText("Test"+position);
        holder.testDesc.setText("This is number "+position);
 
        return rowView;
    }

但是,convertView又是什么东东呢?它可以运行ListView跳过某行(row)为了显示内容而需要的一些设置,它是某个当前正退出屏幕的子view。我们可以复用这个子view来显示新的行(row)。当最初显示ListView的时候,一切都是正常初始化的。由于没有任何views可被复用,所以convertView是null。此时,view会被重新加载,但是TextViews组件被找到之后会被保存到一个ViewHolder对象中。我们可以通过调用setTag()和getTag()方法来将数据保存到view中,之后如果需要可以从中取出数据。

这个改变看起来也许没什么大的区别,但是当我们的views更复杂、数量更多的时候,其效率差别就会非常明显了。

我们开发者们,最想做的一件是可能就是做出一个拥有非常棒的用户体验感的项目。