- Производительность для списков — convertView, ViewHolder
- ViewHolder
- Пример Android ViewHolder Pattern
- Что с шаблоном ViewHolder?
- Без шаблона проектирования ViewHolder
- С помощью шаблона проектирования ViewHolder
- ListView в Android: Кастомизация списков
- Пример: ListActivity с собственным шаблоном.
- Пример: ListActivity с гибким шаблоном
- Продвинутые ListActivity
- Мультивыбор
- Хедер и Футер
- SimpleCursorAdapter
- Android ViewHolder Pattern Example
- What’s with the ViewHolder pattern?
- Without the ViewHolder Design Pattern
- With the ViewHolder Design Pattern
- Let’s Code!
- What’s Next?
Производительность для списков — convertView, ViewHolder
Очень часто мы хотим видеть список со значками. Для этого обычно создаётся разметка с TextView и ImageView, далее реализуется свой адаптер. Для небольших списков вам можно не заботиться о производительности списка, как правило, тормоза не ощущаются. Но если списки становятся слишком большими, то производительность резко падает. Почему так происходит?
При работе с большими списками следует быть осторожными, особенно, если вы создаёте собственные адаптеры с использованием картинок и других элементов. Вы можете легко превысить допустимые лимиты на память и получить ошибку в работе приложения. Это происходит из-за того, что в методе getView() сразу создаются объекты, занимающие память. Вот стандартный сценарий использования плохого адаптера, что называется «в лоб», когда в методе getView() происходит формирование элемента списка:
В этом примере происходит раздувание макета каждый раз, когда необходимо вернуть вид для отображения на экране. А происходит это при любой попытке прокрутить список.
Подобного кода следует избегать. Существует небольшая хитрость, чтобы снизить затраты и повысить производительность.
В методе getView() вторым параметром идёт convertView, который отвечает за выводимый компонент на экране. Когда формируется список и на экране появляются только видимые элементы списка, то параметр равен null. Когда мы начинаем прокручивать список, то верхний элемент становится невидимым, а контейнер для верхнего элемента списка перемещается вниз для следующего элемента. Происходит повторное использование одних и тех же контейнеров для элементов списка. При этом convertView принимает значение выводимого компонента.
Вы должны проверять convertView на наличие содержимого и переназначать его, отправляя новые данные в существующий шаблон, если convertView не пустой.
Система стирает элементы вашего списка, которые уже не отображаются на экране и передаёт управление ими в метод getView() через параметр convertView. Ваш адаптер может использовать этот вид и избежать «раздутие» шаблона для этого элемента. Это сохраняет память и уменьшает загрузку процессора.
Улучшенный вариант будет следующим:
В коде сравнивается convertView на null и уже в этом случае идет раздувание макета. Если не равно null, значит контейнер уже существует и мы можем просто переписать данные в нём. Производительность подобного решения почти в 2.5 раза выше, чем стандартное решение на списке из 10 тысяч записей.
ViewHolder
Существует ещё одна методика для улучшения производительности работы больших списков — использование класса ViewHolder. Метод findViewById() достаточно тяжёлый в плане потребления ресурсов, так что нужно избегать его, если в нём нет прямой необходимости. ViewHolder сохраняет ссылки на необходимые в элементе списка шаблоны. Этот ViewHolder прикреплён к элементу методом setTag(). Каждый элемент списка может содержать применённую ссылку. Если элемент очищен, мы можем получить ViewHolder через метод getTag().
Создание элемента списка происходит по мере необходимости и производительность у данного решения чуть выше, чем у предыдущего примера.
Данные решения применимы к GridView и другим элементам, использующим адаптеры.
Напишем простой пример, чтобы наглядно увидеть переиспользование контейнеров для элементов списка. Добавьте на экран активности список ListView и подготовьте простой макет для элемента списка в файле res/layout/list_item.xml.
Напишем код для активности с адаптером.
Запустите пример и смотрите логи. Вначале вы увидите приблизительно такое:
Попробуйте тихонько сдвинуть список до появления следующего элемента. Как только первый элемент уйдёт за пределы экрана, то новый контейнер создаваться не будет и вместо null вы увидите ссылку на уже существующий контейнер.
Источник
Пример Android ViewHolder Pattern
Теперь мы собираемся закодировать плавную прокрутку нашего Android ListView. В предыдущем посте мы попытались понять, как работает ListView с адаптером. На этот раз речь пойдет о производительности.
Я сделал это отдельным постом, потому что Android ListView иногда трудно понять . Я имею в виду: «Сначала мы должны сделать основы, а затем применить оптимизацию».
Что с шаблоном ViewHolder?
Шаблон проектирования ViewHolder позволяет получить доступ к каждому представлению элемента списка без необходимости поиска, сохраняя ценные циклы процессора. В частности, он позволяет избежать частого вызова findViewById () во время прокрутки ListView, и это сделает его гладким.
Без шаблона проектирования ViewHolder
Хорошо, давайте раскопаем это и посмотрим, как это работает без шаблона ViewHolder.
Давайте посмотрим на наш предыдущий метод getView () в ArrayAdapterItem.java
- При первой загрузке convertView имеет значение null. Нам придется накачать наш элемент списка и найти TextView с помощью findViewById ().
- Во второй раз, когда он был загружен, convertView не является нулевым, хорошо! Мы не должны раздувать это снова. Но мы снова будем использовать findViewById ().
- В следующий раз, когда он был загружен, convertView определенно не равен нулю. Но findViewById () постоянно вызывается, он будет работать, но он замедляет производительность, особенно если у вас много элементов и представлений в вашем ListView.
С помощью шаблона проектирования ViewHolder
Теперь давайте посмотрим, как это работает с шаблоном ViewHolder.
Источник
ListView в Android: Кастомизация списков
Продолжение статьи о ListView в Android, в котором мы рассмотрим более сложные примеры его использования, такие, как иконки на элементах списка и добавление чекбоксов к этим элементам. Так же мы рассмотрим возможности по оптимизации кода.
Напомню, что статья является переводом этой статьи с разрешения ее автора.
Пример: ListActivity с собственным шаблоном.
Вы можете создать свой собственный шаблон для элементов списка и применить его к своему Адаптеру. Шаблон будет одинаковым для каждого элемента списка, но дальше мы разберем как сделать его более гибким. В нашем примере мы добавим иконку к каждому элементу списка.
Создайте файл шаблона «rowlayout.xml» в папке res/layout вашего проекта «de.vogella.android.listactivity».
Измените свою Деятельность на следующую. Код почти такой же, как и в предыдущем примере, единственная разница в том, что мы используем наш собственный шаблон в ArrayAdapter и указываем адаптеру какой элемент пользовательского интерфейса будет содержать текст. Мы не делали этого в предидущей статье, поскольку мы использовали стандартный шаблон.
Пример: ListActivity с гибким шаблоном
Оба предыдущих примера используют один шаблон сразу для всех строк. Если вы хотите изменить вид определенных строк, вам нужно определить свой адаптер и заместить метод getView().
Этот метод ответственен за создание отдельных элементов вашего ListView. getView() возвращает Вид. Этот Вид фактически является Шаблоном (ViewGroup) и содержит в себе другие Виды, например, ImageView или TextView. С getView() вы так же можете изменить параметры индивидуальных видов.
Чтобы прочитать шаблон из XML в getView(), вы можете использовать системный сервис LayoutInflator.
В этом примере мы расширяем ArrayAdapter, но так же мы можем реализовать непосредственно BaseAdapter.
Определение простого адаптера
Очень просто создать свой Адаптер, не обращая внимания на его оптимизацию. Просто получайте в своей Деятельности данные, которые хотите отобразить и сохраняйте их в элемент списка. В вашем getView() установите ваш предопределенный шаблон для элементов и получите нужные вам элементы с помощью findViewById(). После этого вы можете определить их свойства.
Наш пример использует две картинки: «no.png» и «ok.png». Я положил их в папку «res/drawable-mdpi». Используйте свои картинки. Если не нашли таковых, то просто скопируйте «icon.png» и, с помощью графического редактора, немного измените их.
Создайте класс «MySimpleArrayAdapter», который будет служить нашим Адаптером.
Чтобы использовать этот Адаптер, измените класс MyList на следующее
Когда вы запустите это приложение, вы увидите список с элементами, с разными значками на некоторых из них.
Оптимизация производительности вашего собственного адаптера
Создание Java объектов для каждого элемента — это увеличение потребления памяти и временные затраты. Как уже говорилось, Андроид стирает элементы (виды) вашего списка, которые уже не отображаются и делегируют управление ими в метод getView() через параметр convertView.
Ваш Адаптер может использовать этот вид и избежать «раздутие» Шаблона для этого элемента. Это сохраняет память и уменьшает загрузку процессора.
В вашей реализации вы должны проверять convertView на наличие содержимого и переназначать его, отправляя новые данные в существующий Шаблон, если convertView не пустой.
Наша реализация так же использует модель ViewHolder. Метод findViewById() достаточно ресурсоемок, так что нужно избегать его, если в нем нет прямой необходимости.
ViewHolder сохраняет ссылки на необходимые в элементе списка Шаблоны. Этот ViewHolder прикреплен к элементу методом setTag(). Каждый Вид может содержать примененную ссылку. Если элемент очищен, мы можем получить ViewHolder через метод getTag(). Это выглядит нагруженным, но, на самом деле, работает быстрее, чем повторяющиеся вызовы findViewById().
Обе техники (переназначение существующих видов и модель ViewHolder) увеличивают производительность примерно на 15%, особенно на больших объемах данных.
Продолжая использовать проект «de.vogella.android.listactivity», создайте класс «MyArrayAdapter.java».
Продвинутые ListActivity
Обработка долгого нажатия на элементе
Вы так же можете добавить LongItemClickListener к виду. Для этого получите ListView через метод getListView() и определите обработку длительного нажатия через метод setOnItemLongClickListener().
Элементы, взаимодействующие с моделью данных
Ваш шаблон элемента списка так же может содержать Виды, взаимодействующие с моделью данных. Например, вы можете использовать Checkbox в элементе списка и, если чекбокс включен, вы можете менять данные, отображаемые в элементе.
Мы до сих пор используем тот же проект. Создайте шаблон элемента списка «rowbuttonlayout.xml».
создайте для этого примера класс Model, который содержит название элемента и его содержимое, если он чекнут.
Создайте следующий Адаптер. Этот Адаптер добавит обработку изменения Checkbox. Если чекбокс включен, то данные в модели тоже меняются. Искомый Checkbox получает свою модель через метод setTag().
В завершение измените свой ListView на следующий.
Когда вы запустите ваше приложение, вам будет доступна отметка элементов, которая будет отражаться на вашей модели.
Мультивыбор
Так же можно сделать одиночный и мультивыбор. Посмотрите следующие сниппеты для примера. Чтобы получить выбранные элементы используйте listView.getCheckedItemPosition() или listView.getCheckedItemPositions(). Вы так же можете использовать listView.getCheckedItemIds(), чтобы получить ID выбранных элементов.
Хедер и Футер
Вы можете поместить произвольные элементы вокруг своего списка. Например, вы можете создать шаблон со списком между двумя TextView. Если вы так сделаете, то вы должны указать id «@android:id/list» к ListView, т.к. ListActivity ищет Вид с таким идентификатором. В таком случае один TextView всегда будет видимым над ListView (Хедер), а другой будет виден внизу. Если вы хотите использовать Футер и Хедер только в конце/начале списка, чтобы они не были фиксированными, то нужно использовать view.setHeaderView() или view.setFooterView(), например:
SimpleCursorAdapter
Если вы работаете с базой данных или же с контентом непосредственно, вы можете использовать SimpleCursorAdapter, чтобы перенести данные в ваш ListView.
Создайте новый проект «de.vogella.android.listactivity.cursor» с деятельностью «MyListActivity». Создайте такую деятельность.
Убедитесь, что вы дали приложению доступ к контактам. (Используйте «android.permission.READ_CONTACTS» в AndroidManifest.xml).
Спасибо за внимание. Комментарии и поправки к переводу приветствуются, т.к. даже в исходнике встречаются ошибки и опечатки.
Прошу прощения за репост, изначально не отметил как перевод, поскольку недавно здесь. Большое спасибо за наводку jeston, принял к сведению и научился на ошибках.
Источник
Android ViewHolder Pattern Example
Posted by: Mike Dalisay in Android Core September 20th, 2013 17 Comments Views
Now we are going to code the smooth scrolling of our Android ListView. In the previous post, we tried to understand how the ListView with adapter works. This time, it will be all about performance.
I did this a separate post because Android ListView is difficult to understand at times. What I have in mind is, “we have to do the basics first, and then apply the optimization.”
What’s with the ViewHolder pattern?
The ViewHolder design pattern enables you to access each list item view without the need for the look up, saving valuable processor cycles. Specifically, it avoids frequent call of findViewById() during ListView scrolling, and that will make it smooth.
Without the ViewHolder Design Pattern
Okay, let’s dig it out and see how it works without the ViewHolder pattern.
Let’s take a look at our previous getView() method in ArrayAdapterItem.java
- The first time it was loaded, convertView is null. We’ll have to inflate our list item layout and find the TextView via findViewById().
- The second time it was loaded, convertView is not null, good! We don’t have to inflate it again. But we’ll use findViewById() again.
- The following times it was loaded, convertView is definitely not null. But findViewById() is constantly called, it will work but, it slows down the performance especially if you have lots of items and Views in your ListView.
With the ViewHolder Design Pattern
Now let’s see how it works with the ViewHolder pattern.
- The first time it was loaded, convertView is null. We’ll have to inflate our list item layout, instantiate the ViewHolder, find the TextView via findViewById() and assign it to the ViewHolder, and set the ViewHolder as tag of convertView.
- The second time it was loaded, convertView is not null, good! We don’t have to inflate it again. And here’s the sweet thing, we won’t have to call findViewById() since we can now access the TextView via its ViewHolder.
- The following time it was loaded, convertView is definitely not null. The findViewById() is never called again, and that makes our smooth ListView scrolling.
Let’s Code!
So here it is, we’ll make use of the Android ViewHolder pattern in our ListView (in just 3 steps!).
Step 1: Add the following static class on our ArrayAdapterItem.java file
Step 2: Our getView() will now look like this:
Step 3: For the sake of testing, we’re going to put thousands of items in our ListView. On our MainActivity.java, our showPopUp() will now look like this:
I tested this code with as much as 2,000 items, and the performance is still smooth and great.
What’s Next?
If you have other ideas regarding this topic, please drop it in the comment section below. I’m more than willing to update this post and improve the life of mankind.
In the next post, we’ll try to use the AsyncTask to load image into the ListView. Something like how the Google Play Store app does it.
Источник