<?xml version="1.0" encoding="utf-8"?> 
<rss version="2.0">

<channel>

<title>Just Developer Blog, заметки с тегом: использование паттерна ViewHolder</title>
<link>http://bibobo.ru/tags/ispolzovanie-patterna-viewholder/</link>
<description></description>
<generator>E2 (v3254; Aegea)</generator>

<item>
<title>Использование паттерна ViewHolder</title>
<guid isPermaLink="true">http://bibobo.ru/all/ispolzovanie-patterna-viewholder/</guid>
<link>http://bibobo.ru/all/ispolzovanie-patterna-viewholder/</link>
<comments>http://bibobo.ru/all/ispolzovanie-patterna-viewholder/</comments>
<description>&lt;p&gt;С этой статьи я бы хотел начать цикл кратких заметок о разработке под мобильные устройства. Приложения под телефоны и планшеты — мое давнее увлечение. К сожалению плотно заняться которым, в силу различных обстоятельств, я смог только сейчас. В прочем хватит лирики, начнём.
В качестве платформы я выбрал Android, как наиболее массовую и открытую систему. В качестве языка программирования сегодня будем использовать C#, писать будем на Xamarin с использованием Android API.&lt;/p&gt;
&lt;p&gt;&lt;input id="button_using_viewholder" type="button" onclick="toggle(cut_using_viewholder, button_using_viewholder)" value="Читать дальше" hiddenText="Свернуть" visibleText="Читать дальше"&gt;&lt;/p&gt;
&lt;div id="cut_using_viewholder" style="display: none;" displayOld=""&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Практически любое мобильное приложение это некий экран с набором однотипных информационных элементов, для отображения которых обычно используется список или какой-либо грид. Из-за того, что платформа мобильная и работает на устройстве с небольшим объёмом памяти и слабым процессором (хаха! В моем первом компьютере было памяти раз в 15 меньше и процессор существенно слабее, чем сейчас в среднем телефоне) операционная система вынуждена ресурсы экономить. Соответственно если речь идёт о списке (ListView в Android), а если точнее об адаптере, который предоставляет данные для списка (например ArrayAdapter в Android) мы сталкиваемся с одной из особенностей, вызванных необходимостью беречь ресурсы.&lt;/p&gt;
&lt;p&gt;Такая особенность заключается в том, что лист не создаёт ячейки под каждую из записей подключенного источника данных. Он создаёт лишь то количество ячеек, сколько может уместиться на экране в данный момент плюс по одной лишней ячейке сверху и снизу. Такое поведение характерно не только для Android, но и для iOS.&lt;/p&gt;
&lt;p&gt;При этом когда пользователь прокручивает список система не удаляет ячейки, она старается их переиспользовать.&lt;/p&gt;
&lt;p&gt;Когда ячейка пропадает из поля видимости пользователя она передаётся в метод GetView() адаптера, в котором происходит заполнение ячейки данными. В этом методе можно решить, переиспользовать уже готовую ячейку или создать новую.&lt;/p&gt;
&lt;p&gt;Все бы ничего, но если лэйаут ячейки сложный то мы напрямую сталкиваемся с необходимостью проводить поиск элементов в лэйауте (TextView, ImageView и т. п.) для заполнения их актуальными данными. Вроде бы ничего страшного, вызвал &lt;i&gt;FindViewById()&lt;/i&gt; с нужным id элемента, получил его и нет проблем, но выполнение метода &lt;i&gt;FindViewById()&lt;/i&gt; это длительная операция, потому что она тянет за собой траверс по XML-коду лэйаута нашей ячейки. В итоге на сложных лэйаутах имеем просадку по производительности скроллинга списка.&lt;/p&gt;
&lt;p&gt;Чтобы решить данную проблему нам необходимо использовать паттерн &lt;b&gt;ViewHolder&lt;/b&gt;. Кратко его суть заключается в следующем:&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Реализуем класс, который в конструкторе принимает нашу &lt;i&gt;View&lt;/i&gt;, делает траверс и ищет все её элементы, которые нам потребуются для заполнения данных;&lt;/li&gt;
&lt;li&gt;Записываем ссылки на эти элементы в соответствующие поля класса, снабжаем их геттерами, чтобы потом к ним обращаться;&lt;/li&gt;
&lt;li&gt;При создании нашей &lt;i&gt;View&lt;/i&gt; — создаём наш &lt;i&gt;класс-ViewHolder&lt;/i&gt; и записываем ссылку на него в свойстве &lt;i&gt;Tag&lt;/i&gt; у нашей &lt;i&gt;View&lt;/i&gt;;&lt;/li&gt;
&lt;li&gt;Теперь при попытке переиспользовать &lt;i&gt;View&lt;/i&gt; нам не нужно делать повторный поиск элементов в лэйауте, достаточно получить ссылку на &lt;i&gt;ViewHolder&lt;/i&gt; из свойства &lt;i&gt;Tag&lt;/i&gt; и обратиться к соответствующим геттерам холдера;&lt;/li&gt;
&lt;li&gt;PROFIT.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Если делать реализацию на Java сложностей возникнуть не должно, но если использовать C# и Xamarin мы столкнемся с корнями, растущими из Java :-)&lt;/p&gt;
&lt;p&gt;Предположим есть такой класс:&lt;/p&gt;
&lt;pre&gt;&lt;pre class="e2-text-code"&gt;&lt;code&gt;public class MyViewHolder{
public TextView MyTextView {get; private set;}

public MyViewHolder(View view){
...
}
}&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;Если теперь попытаться присвоить ссылку на него свойству &lt;i&gt;Tag&lt;/i&gt; нашей &lt;i&gt;View&lt;/i&gt; у нас ничего не получится. Поскольку &lt;i&gt;Tag&lt;/i&gt; принимает объект типа &lt;i&gt;Java.Lang.Object&lt;/i&gt;, а наш класс наследован от типа &lt;i&gt;Object&lt;/i&gt;. Прямое кастование к нужному типу так же не приведёт к успеху. Кастование сначала к &lt;i&gt;Object&lt;/i&gt;, а потом к &lt;i&gt;Java.Lang.Object&lt;/i&gt; не даст ошибки компиляции, зато упадёт в рантайме.&lt;/p&gt;
&lt;p&gt;Что же делать? &lt;b&gt;Все просто :-) наследовать наш класс от типа &lt;i&gt;Java.Lang.Object&lt;/i&gt; и будет счастье!&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Небольшой пример того, как все это работает:&lt;/p&gt;
&lt;pre&gt;&lt;pre class="e2-text-code"&gt;&lt;code&gt;public class TimetableItemAdapter : ArrayAdapter&amp;lt;TimetableItem&amp;gt;
{
	/// &amp;lt;summary&amp;gt;
	/// Класс ViewHolder, нужен что бы исключить множественный траверс по элементам вьюхи, для производительности
	/// &amp;lt;/summary&amp;gt;
	public class TimetableItemViewHolder : Java.Lang.Object{
		public TextView TimeText { get; private set;}
		public TextView DescriptionText { get; private set;}
		public ImageView AcceptButton { get; private set;}

		public TimetableItem BoundedItem { get; set;}

		public TimetableItemViewHolder(View view, TimetableItem item){
			TimeText = view.FindViewById&amp;lt;TextView&amp;gt;(Resource.Id.timetable_time_text);
			DescriptionText = view.FindViewById&amp;lt;TextView&amp;gt;(Resource.Id.timetable_description_text);
			AcceptButton = view.FindViewById&amp;lt;ImageView&amp;gt;(Resource.Id.timetable_accept);

			BoundedItem = item;
		}
	}

	private MainActivity _context;

	public TimetableItemAdapter(Context context, int resource, IList&amp;lt;TimetableItem&amp;gt; objects) : base(context, resource, objects) {
		_context = (MainActivity)context;
	}
			
	public override View GetView(int position, View convertView, ViewGroup parent) {
		View resultView = null;
		TimetableItemViewHolder holder = null;
		TimetableItem item = GetItem(position);

		if(convertView != null){
			resultView = convertView;
			holder = (TimetableItemViewHolder)convertView.Tag;
			holder.BoundedItem = item;
		}else{
			resultView = ((LayoutInflater)_context.GetSystemService(Context.LayoutInflaterService))
				.Inflate(Resource.Layout.timetable_list_item, parent, false);
			holder = new TimetableItemViewHolder(resultView, item);
			resultView.Tag = holder;
		}

		if(item != null &amp;amp;&amp;amp; holder != null){
			holder.TimeText.Text = item.Time;
			holder.DescriptionText.Text = item.Description;
		}

		return resultView;
	}
}&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;Во &lt;i&gt;ViewHolder&lt;/i&gt; не обязательно хранить только ссылки на элементы лэйаута, иногда может потребоваться хранить и какие-либо сопутствующие данные. Например в коде выше во &lt;i&gt;ViewHolder&lt;/i&gt; записывается дополнительно ссылка на элемент из источника данных, для последующего использования где-нибудь. Но в данном случае нужно помнить, что при переиспользовании &lt;i&gt;View&lt;/i&gt; нужно не забыть записать в свойство ссылку на новый элемент источника данных.&lt;/p&gt;
&lt;/div&gt;</description>
<pubDate>Wed, 17 Jun 2015 10:56:43 +0300</pubDate>
</item>


</channel>
</rss>