Списки могут быть замечены в любом приложении. Это простейший способ для отображения рецептов, контактов или чего угодно другого. Это значит, что в Android должен быть встроенный способ отображения подобного рода данных. Последняя реализация такого инструмента — это RecyclerView. Его ключевой особенностью является эффективность, поскольку он переиспользует свои элементы вместо того чтобы создавать новые, как только появляется новая строка. Прежде чем RecyclerView у нас был ListView, который все еще широко используется. До тех пор пока ListView так же переиспользует view, он остается одним из самых неправильно используемых элементов view в Android на сегодняшний день. Не мало было написано подобных постов ранее, но мы пишем об этом сегодня потому что приложения настроенные неправильно продолжают появляться.

Вот так выглядит код новичка с ArrayAdapter для ListView:

@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 таким образом нет ничего зазорного, если ваш список помещается на экране целиком, но в таком случае вы могли бы создать простой view и избежать хлопот с ArrayAdapter. При использовании ListView мы планируем показывать большие списки с комплексными view. Вот тут-то нам производительность и потребуется. Способ представленный выше является дорогостоящим в плане производительности. Когда пользователь прокручивает список, каждый элемент прикрепливается, при этом вызывая метод findViewById() каждый раз. Когда findViewById() вызван, приходится проходить по всей иерархии View до тех пор пока подходящий Id не будет найден. Это происходит для каждого суб-элемента view что вам необходимо найти! И чем быстрее пользователь прокручивает список, тем заметнее выглядят притормаживания и проседания в производительности. Чтобы исправить это, мы можем использовать static class в связке с 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 пропустить некоторые из настроек необходимых для отображения контента строки. Это view строки, которая в данный момент за экраном. Мы можем переиспользовать эту view для отображения следующей. Когда ListView отображена в начале, все происходит как обычно. До тех пор пока ни одна из view не может быть переиспользована, convertView не null. View прикрепляется точно так же как и в предыдущей версии, но TextViews найдены и ссылки сохранены в ViewHolder. Таким образом мы можем хранить во ViewHolder внутри view вызывая setTag(). Это позволит нам хранить данные внутри view, на который мы будем ссылаться позднее, как показано во второй половине рассмотренного getView().

Изменения, что мы сделали, может быть и не выглядят как что-то большое и значимое, но как только наше приложение станет больше и сложнее, и количество элементов возрастет, неэфективность дала бы знать о себе. Последнее, что мы бы хотели сделать как разработчики — это создать приложение с плохим опытом использования (UX). Помните, каждый вложенныйListView может быть гвоздем в крышку гроба вашего приложения, так что играть с судьбой не стоит!

Комментарии (2)


  1. Suvitruf
    08.11.2015 18:48
    +3

    Автор открыл для себя ViewHolder? Статья запоздала на, минутку, 4 года?


  1. iamironz
    08.11.2015 22:33

    Совет: изучайте больше матчасти вместо такого рода писанины. Помимо того что решение очевидно, setTag/getTag. Непонятно вообще выделение в этого огрызка текста в самостоятельную статью.