Как стать автором
Обновить

AlertDialog setMultiChoiceItems, баг или неочевидная особенность

Время на прочтение3 мин
Количество просмотров3K
Привет, Хабр!

Вступление


Последние пару месяцев работаю над одним проектом под Android. Но речь сейчас пойдет не о нем, о нем я постараюсь обязательно написать, но всему свое время

За все время работы над проектом, случалось (и случается) много интересного. Сегодня я хочу рассказать одну небольшую историю.

Начало истории


Как-то вечером, совсем перед новогодними праздниками, работая в офисе и попивая любимый чаек, писал я очередную часть функционала для проекта.

Понадобилось мне создать обычный диалог, с возможностью множественного выбора элементов из списка, с возможностью сразу отметить\снять отметку со всех элементов, сохранить выбранные элементы и при последующим отображением диалога сразу их отметить.
Сказано — сделано.

Собственно сам код:
protected ArrayList<String> items;
protected ArrayList<String> selectedItems;

protected void showMyDialog() {

	int count = items.size();
	boolean[] checkedItems = new boolean[count];

	for (int i = 0; i < count; i++)
		checkedItems[i] = selectedItems.contains(items.get(i));

	AlertDialog.Builder builder = new AlertDialog.Builder(this);
	builder.setMultiChoiceItems(items.toArray(new String[items.size()]),
			checkedItems, new DialogInterface.OnMultiChoiceClickListener() {
				@Override
				public void onClick(DialogInterface dialog, int which,
						boolean isChecked) {

					ListView list = ((AlertDialog) dialog).getListView();
					if (isChecked) {
						if (which == 0) {
							for (int i = 0; i < list.getCount(); ++i)
								list.setItemChecked(i, true);
							selectedItems.clear();
							selectedItems.addAll(items);
						} else
							selectedItems.add(items.get(which));
					} else {
						if (which == 0) {
							for (int i = 0; i < list.getCount(); ++i)
								list.setItemChecked(i, false);
							selectedItems.clear();
						} else
							selectedItems.remove(items.get(which));
					}
				}
			});

	AlertDialog dialog = builder.create();
	dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
	dialog.show();
}


Казалось бы, проблема решена, можно протестировать и идти с чистой совестью отдыхать(ну ладно, поиграть в WOT=) ).

Кнопка отметить\снять отметку со всех элементов работала. Но потом я заметил, если отметить вначале самому пару элементов, потом кнопкой отметить все элементы и сразу же снять отметку со всех, то те пару элементов, которые отмечал сам, остаются отмеченными.
Немного поэкспериментировав, решил, что пора идти отдыхать и продолжить разбираться утром. Перед уходом поднял этот вопрос на stackoverflow.

Утром придя на работу, просмотрев свою тему и не увидев никаких дельных советов, продолжил разбор дальше.
После не продолжительного анализа происходящего, стало понятно, что проблема воспроизводится, только если в функцию
setMultiChoiceItems(CharSequence[] items, boolean[] checkedItems, OnMultiChoiceClickListener listener)
передавать массив checkedItems, если вместо него передавал null, проблема не возникала.
Поднял снова вопрос на stackoverflow, но к сожалению никто так ничего и не посоветовал.

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

Наши дни


Сегодня, просматривая почту, увидел, что кто-то откликнулся на мой багрепорт. После просмотра стало понятно, что еще пару человек столкнулись с этой же проблемой.

Разобравшись немного с делами, решил ещё раз взглянуть на проблему.
В голову пришла идея, хотя мне казалось, что она уже приходила и я её проверял, я проверил еще раз.
Суть идеи заключалась в том, чтобы массив checkedItems сделать полем класса, и в слушателе работать и с ним тоже.
В итоге, метод onClick принял вид:

public void onClick(DialogInterface dialog, int which,
		boolean isChecked) {

	ListView list = ((AlertDialog) dialog).getListView();
	if (isChecked) {
		if (which == 0) {
			for (int i = 0; i < list.getCount(); ++i)
			{
				list.setItemChecked(i, true);
				checkedItems[i]=true;
			}
			selectedItems.clear();
			selectedItems.addAll(items);
		} else
			selectedItems.add(items.get(which));
	} else {
		if (which == 0) {
			for (int i = 0; i < list.getCount(); ++i)
			{
				list.setItemChecked(i, false);
				checkedItems[i]=false;
			}
			selectedItems.clear();
		} else
			selectedItems.remove(items.get(which));
	}
}


И таки да, это решило первоначальную проблему.

Итог.


Можно сделать вывод, хоть он мне и не нравится:
Если в массиве checkedItems состояние элемента указано как не отмеченное (false), то при отметки он отмечается на экране (но не отмечается в массиве) и при снятии отметки, отметка снимается.
Но если в массиве checkedItems состояние элемента указано как отмеченное (true), то при снятии отметки, элемент все равно остается отмеченным на экране, т.к. он остается отмеченным в массиве.

Вот такая получилась история, надеюсь было интересно и вы не зря потратили время, впрочем как и я.
Спасибо за внимание, если есть вопросы, с радостью отвечу.
В следующей статье хочу рассказать уже о самом проекте, должно быть интересно.
Теги:
Хабы:
Всего голосов 11: ↑7 и ↓4+3
Комментарии7

Публикации