Недавно я писал, что делаю себе поделку, которая будет трекать выполнение моих полезных привычек. И вчера был очередной баттл за ListView - я хотел чтобы он работал по-моему, а Androd Framework не поддавался. Ну накрутили там... Но через пару часов задача была решена.
Что я хотел? Банальный ListVew с элементами которые имеют чекбоксик. При клике на чекбоксик он выделяется и через секунду юлемент удаляется. Если я за эту секунду снял чекбоксик (передумал), то удаления не произойдет. Так же можно повыделять/поснимать за это время еще и другие чекбоксики, секунда до удаления отсчитывается от последнего клика.
Как любитель code reuse я не мог не выделить эту фичу в отдельный мегастатический монстр :) Я в последнее время долго кодил на JavaScript так что особо не судите за интерфейсность.
package apofig.com.myway;
import android.os.Handler;
import android.widget.ListView;
import java.util.LinkedList;
import java.util.List;
public class ListViewCustomizer {
interface WhenDelete {
void doit(List<String> removed);
}
interface OnClick {
void click(int position);
}
public static OnClick setupListViewThatHideChecked(final ListView listView, final List<String> list, final WhenDelete doit) {
final List<Integer> lastPositions = new LinkedList<Integer>(); // будем хранить тут те позиции по которым кликнули
// эта штука позволит нам сделать отложенный во времени вызов кода
final Handler handler = new Handler();
// вот этот код будет вызываться через секунду после того как был сделан последний клик
final Runnable runnable = new Runnable() {
public void run() {
// если ничего не выбрали, то и удалять нечего
if (lastPositions.isEmpty()) return;
// тут храним имена удаленных элементов, его вернем клиенту
List<String> items = new LinkedList<String>();
while (!lastPositions.isEmpty()) { // по все удаленным
Integer index = lastPositions.remove(0); // первый элемент списка
// если за это время его успели анчекнуть, пропускаем
if (!listView.isItemChecked(index)) continue;
items.add(list.get(index)); // сохраняем его
list.remove(index.intValue()); // удаляем
// нам после удаления элемента надо подтянуть и чекбоксики, потому как они хранятся в другом месте
for (int j = index.intValue(); j < list.size(); j++) {
listView.setItemChecked(j, listView.isItemChecked(j + 1));
}
// ну и пересчитать индексы, потому что все после удаленного стали на 1 меньше
for (int i = 0; i < lastPositions.size(); i++) {
Integer removed = lastPositions.remove(i);
if (removed > index) {
removed--;
}
lastPositions.add(i, removed);
}
}
if (items.isEmpty()) return; // чеи в правду ничего не удалили? выходим
// выполняем клиентский код если надо
if (doit != null) {
doit.doit(items);
}
// чистим
lastPositions.clear();
}
};
// этот обработчик следит за тем, чтобы запустить второй, через секунду после того как отыграет последний клик
final int[] count = {0};
final Runnable runnable2 = new Runnable() {
public void run() {
count[0]--;
if (count[0] == 0) {
handler.postDelayed(runnable, 1000);
}
}
};
// а этот код должен дернуть клиент, каждый раз когда кликнули по элементу и указать position
return new OnClick() {
@Override
public void click(int position) {
// странно, но тут не надо инвертировать, потому как значение isItemChecked( - такое как надо, но
// оно не применится к чекбоксику, если не вызвать еще и setItemChecked(
boolean checked = listView.isItemChecked(position);
listView.setItemChecked(position, checked);
// добавляем в скписок выделенных только чекнутые, анчекнутые удаляем
if (checked) {
lastPositions.add(Integer.valueOf(position));
} else {
lastPositions.remove(Integer.valueOf(position));
}
count[0]++;
// Спасибо http://stackoverflow.com/q/8177830
handler.postDelayed(runnable2, 1000);
}
};
}
}
А используется он так
public void onCreate(Bundle savedInstanceState) {
// бла бла бла
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ListView listView = (ListView) findViewById(R.id.lvMain);
// исходные данные и адаптер
List<String> list = new LinkedList<String>(Arrays.asList("one", "two", "three", "four", "five"));
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_checked, list);
listView.setAdapter(adapter);
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); // можно выделять несколько
// настраиваем компоненту
final ListViewCustomizer.OnClick onClick =
ListViewCustomizer.setupListViewThatHideChecked(listView, list,
new ListViewCustomizer.WhenDelete() {
@Override
public void doit(List<String> removed) {
// перерисовываем список в соответствии с изменившимися данными
adapter.notifyDataSetChanged();
// выводим на экранчик сообщение с именами удаленных элементов
Toast.makeText(MainActivity.this, splitWith("\n", removed), Toast.LENGTH_SHORT).show();
}
});
// передаем импульс онклика
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
onClick.click(position);
}
});
}
// метод превращения списка в строку разделенных разделителем
private static String splitWith(String separator, List<String> removed) {
String info = "";
for (String m : removed) {
info += m + separator;
}
info = info.substring(0, info.length() - separator.length());
return info;
}
Вот еще xml-ки
<!-- Спасибо http://dajver.blogspot.co.uk/2013/09/listview-edittext.html -->
<!-- Спасибо http://startandroid.ru/en/uroki/vse-uroki-spiskom/15-urok-6-vidy-layouts-kljuchevye-otlichija-i-svojstva -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<ListView
android:id="@+id/lvMain"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</ListView>
</LinearLayout>
Может кому-то пригодится... Если надо проект в zip виде, пиши в комменты - выложу.

Комментариев нет:
Отправить комментарий