Если нельзя, но очень хочется, то нужно обязательно и ничего в мире не стоит того, чтобы делать из этого проблему!


Интересна Java? Кликай по ссылке и изучай!
Если тебе полезно что-то из того, чем я делюсь в своем блоге - можешь поделиться своими деньгами со мной.
с пожеланием
столько времени читатели провели на блоге - 
сейчас онлайн - 

пятница, 17 октября 2014 г.

Моя поделка на Android GroundhogDay

Недавно я писал, что делаю себе поделку, которая будет трекать выполнение моих полезных привычек. И вчера был очередной баттл за 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 виде, пиши в комменты - выложу.

Комментариев нет:

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