Что такое SortedList‹T›, работающий с RecyclerView.Adapter?

Библиотека поддержки Android 22.1 была выпущена вчера. В библиотеку поддержки v4 и v7 было добавлено много новых функций, среди которых мое внимание привлекает android.support.v7.util.SortedList<T>.

Говорят, что SortedList — это новая структура данных, работает с RecyclerView.Adapter, поддерживает анимацию добавления/удаления/перемещения/изменения элементов, предоставленную RecyclerView. Звучит как List<T> в ListView, но кажется более продвинутым и мощным.

Итак, в чем разница между SortedList<T> и List<T>? Как я могу использовать его эффективно? Каково соблюдение SortedList<T> над List<T>, если это так? Может ли кто-нибудь выложить несколько образцов?

Любые советы или коды будут оценены. Заранее спасибо.


person SilentKnight    schedule 22.04.2015    source источник
comment
Это должен быть список, отсортированный на основе Comparable-реализации универсального типа T. Если нет, то Google потерял некоторые очки в отделе соглашения об именах.   -  person A Nice Guy    schedule 22.04.2015
comment
Это список, в котором элементы сортируются в соответствии с обратным вызовом сравнения. Таким образом, вы можете добавлять элементы, и они будут отображаться в нужном месте (а не в конце). Взгляните на Javadoc. developer.android.com/reference/android/support/v7/ использовать/   -  person Thilo    schedule 22.04.2015
comment
Самое главное, как его использовать с RecyclerView.Adapter? Кто-нибудь пишет примеры по этому поводу?   -  person SilentKnight    schedule 22.04.2015


Ответы (4)


SortedList обрабатывает связь с адаптером Recycler через Callback.

Одно различие между SortedList и List видно во вспомогательном методе addAll в примере ниже.

public void addAll(List<Page> items) {
        mPages.beginBatchedUpdates();
        for (Page item : items) {
            mPages.add(item);
        }
        mPages.endBatchedUpdates();
    }
  1. Сохраняет последний добавленный элемент

Скажем, у меня есть 10 кэшированных элементов для немедленной загрузки, когда мой список ресайклеров заполнен. В то же время я запрашиваю у своей сети те же 10 элементов, потому что они могли измениться с тех пор, как я их кэшировал. Я могу вызвать тот же метод addAll, и SortedList заменит cachedItems на fetchedItems под капотом (всегда сохраняет последний добавленный элемент).

// After creating adapter
myAdapter.addAll(cachedItems)
// Network callback
myAdapter.addAll(fetchedItems)

В обычном List у меня были бы дубликаты всех моих элементов (размер списка 20). С помощью SortedList он заменяет одинаковые элементы с помощью обратного вызова areItemsTheSame.

  1. Это умно о том, когда обновлять Views

При добавлении fetchedItems onChange будет вызываться только в том случае, если один или несколько заголовков Page изменились. Вы можете настроить то, что SortedList ищет в обратном вызове areContentsTheSame.

  1. Его исполнитель

Если вы собираетесь добавить несколько элементов в SortedList, вызов BatchedCallback преобразует отдельные вызовы onInserted(index, 1) в один onInserted(index, N), если элементы добавляются в последовательные индексы. Это изменение может помочь RecyclerView гораздо проще разрешать изменения.

Образец

У вас может быть геттер на вашем адаптере для вашего SortedList, но я просто решил добавить вспомогательные методы в свой адаптер.

Класс адаптера:

  public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private SortedList<Page> mPages;

    public MyAdapter() {
        mPages = new SortedList<Page>(Page.class, new SortedList.Callback<Page>() {
            @Override
            public int compare(Page o1, Page o2) {
                return o1.getTitle().compareTo(o2.getTitle());
            }

            @Override
            public void onInserted(int position, int count) {
                notifyItemRangeInserted(position, count);
            }

            @Override
            public void onRemoved(int position, int count) {
                notifyItemRangeRemoved(position, count);
            }

            @Override
            public void onMoved(int fromPosition, int toPosition) {
                notifyItemMoved(fromPosition, toPosition);
            }

            @Override
            public void onChanged(int position, int count) {
                notifyItemRangeChanged(position, count);
            }

            @Override
            public boolean areContentsTheSame(Page oldItem, Page newItem) {
                // return whether the items' visual representations are the same or not.
                return oldItem.getTitle().equals(newItem.getTitle());
            }

            @Override
            public boolean areItemsTheSame(Page item1, Page item2) {
                return item1.getId() == item2.getId();
            }
        });

    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.viewholder_page, parent, false);
        return new PageViewHolder(view);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        PageViewHolder pageViewHolder = (PageViewHolder) holder;
        Page page = mPages.get(position);
        pageViewHolder.textView.setText(page.getTitle());
    }

    @Override
    public int getItemCount() {
        return mPages.size();
    }

    // region PageList Helpers
    public Page get(int position) {
        return mPages.get(position);
    }

    public int add(Page item) {
        return mPages.add(item);
    }

    public int indexOf(Page item) {
        return mPages.indexOf(item);
    }

    public void updateItemAt(int index, Page item) {
        mPages.updateItemAt(index, item);
    }

    public void addAll(List<Page> items) {
        mPages.beginBatchedUpdates();
        for (Page item : items) {
            mPages.add(item);
        }
        mPages.endBatchedUpdates();
    }

    public void addAll(Page[] items) {
        addAll(Arrays.asList(items));
    }

    public boolean remove(Page item) {
        return mPages.remove(item);
    }

    public Page removeItemAt(int index) {
        return mPages.removeItemAt(index);
    }

    public void clear() {
       mPages.beginBatchedUpdates();
       //remove items at end, to avoid unnecessary array shifting
       while (mPages.size() > 0) {
          mPages.removeItemAt(mPages.size() - 1);
       }
       mPages.endBatchedUpdates();
    }
}

Класс страницы:

public class Page {
    private String title;
    private long id;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }
}

XML-файл для просмотра:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/text_view"
        style="@style/TextStyle.Primary.SingleLine"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

Класс просмотра:

public class PageViewHolder extends RecyclerView.ViewHolder {
    public TextView textView;


    public PageViewHolder(View itemView) {
        super(itemView);
        textView = (TextView)item.findViewById(R.id.text_view);
    }
}
person Amozoss    schedule 22.04.2015
comment
Это лучший способ использовать SortedList в качестве поля RecyclerView.Adapter? - person SilentKnight; 23.04.2015
comment
Я бы сказал, что программист должен решить, какой дизайн лучше всего подходит для него. В моем случае было бы разумнее, чтобы адаптер обрабатывал все данные, а SortedList представлял данные внутри. - person Amozoss; 23.04.2015
comment
Обратите внимание, что есть SortedListAdapterCallback, который принимает RecyclerView.Adapter в качестве параметра конструктора и обрабатывает для вас методы onInserted() и kin. - person CommonsWare; 11.05.2015
comment
Реализация неправильная. Это нарушает договор между equals() и compareTo(). Вы должны проверить в compareTo(), равны ли элементы и вернуть 0 перед сравнением заголовков. В противном случае вы окажетесь в сценариях с дубликатами в качестве SortedList, поскольку он зависит от контракта. - person Paul Woitaschek; 26.07.2015
comment
Любой способ использовать его с общим классом? - person Mukul; 16.08.2015
comment
Что делать, если пользователь хочет динамически изменить порядок сортировки с помощью настройки? Как вы делаете это динамически в адаптере? - person Raghunandan; 24.09.2015
comment
Как бы вы справились со случаем, когда при обновлении элементов у меня есть элементы новые, обновленные и УДАЛЕННЫЕ? .addAll не поможет, так как не удаляет уже удаленные элементы, а .clear() сбросит список (мне нужно это проверить). Может ли .beginBatchUpdate помочь, инкапсулировав цикл из .remove(), за которым следует .addAll()? - person David Corsalini; 22.10.2015
comment
@PaulWoitaschek прав, я получил дубликаты элементов. Как вы решили проблему дублирования? - person Otieno Rowland; 25.12.2015
comment
@PaulWoitaschek У меня такая же проблема с дубликатами, вы нашли решение? - person Igori S; 13.10.2016

SortedList is in v7 support library.

Реализация SortedList, которая может упорядочивать элементы, а также уведомлять об изменениях в списке, чтобы его можно было привязать к RecyclerView.Adapter.

Он упорядочивает элементы с помощью метода compare(Object, Object) и использует двоичный поиск для извлечения элементов. Если критерии сортировки ваших элементов могут измениться, убедитесь, что вы вызываете соответствующие методы при их редактировании, чтобы избежать несоответствия данных.

Вы можете контролировать порядок элементов и изменять уведомления с помощью параметра SortedList.Callback.

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

public class SortedListActivity extends ActionBarActivity {
    private RecyclerView mRecyclerView;
    private LinearLayoutManager mLinearLayoutManager;
    private SortedListAdapter mAdapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.sorted_list_activity);
        mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        mRecyclerView.setHasFixedSize(true);
        mLinearLayoutManager = new LinearLayoutManager(this);
        mRecyclerView.setLayoutManager(mLinearLayoutManager);
        mAdapter = new SortedListAdapter(getLayoutInflater(),
                new Item("buy milk"), new Item("wash the car"),
                new Item("wash the dishes"));
        mRecyclerView.setAdapter(mAdapter);
        mRecyclerView.setHasFixedSize(true);
        final EditText newItemTextView = (EditText) findViewById(R.id.new_item_text_view);
        newItemTextView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
            @Override
            public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
                if (id == EditorInfo.IME_ACTION_DONE &&
                        (keyEvent == null || keyEvent.getAction() == KeyEvent.ACTION_DOWN)) {
                    final String text = textView.getText().toString().trim();
                    if (text.length() > 0) {
                        mAdapter.addItem(new Item(text));
                    }
                    textView.setText("");
                    return true;
                }
                return false;
            }
        });
    }

    private static class SortedListAdapter extends RecyclerView.Adapter<TodoViewHolder> {
        SortedList<Item> mData;
        final LayoutInflater mLayoutInflater;
        public SortedListAdapter(LayoutInflater layoutInflater, Item... items) {
            mLayoutInflater = layoutInflater;
            mData = new SortedList<Item>(Item.class, new SortedListAdapterCallback<Item>(this) {
                @Override
                public int compare(Item t0, Item t1) {
                    if (t0.mIsDone != t1.mIsDone) {
                        return t0.mIsDone ? 1 : -1;
                    }
                    int txtComp = t0.mText.compareTo(t1.mText);
                    if (txtComp != 0) {
                        return txtComp;
                    }
                    if (t0.id < t1.id) {
                        return -1;
                    } else if (t0.id > t1.id) {
                        return 1;
                    }
                    return 0;
                }

                @Override
                public boolean areContentsTheSame(Item oldItem,
                        Item newItem) {
                    return oldItem.mText.equals(newItem.mText);
                }

                @Override
                public boolean areItemsTheSame(Item item1, Item item2) {
                    return item1.id == item2.id;
                }
            });
            for (Item item : items) {
                mData.add(item);
            }
        }

        public void addItem(Item item) {
            mData.add(item);
        }

        @Override
        public TodoViewHolder onCreateViewHolder(final ViewGroup parent, int viewType) {
            return new TodoViewHolder (
                    mLayoutInflater.inflate(R.layout.sorted_list_item_view, parent, false)) {
                @Override
                void onDoneChanged(boolean isDone) {
                    int adapterPosition = getAdapterPosition();
                    if (adapterPosition == RecyclerView.NO_POSITION) {
                        return;
                    }
                    mBoundItem.mIsDone = isDone;
                    mData.recalculatePositionOfItemAt(adapterPosition);
                }
            };
        }

        @Override
        public void onBindViewHolder(TodoViewHolder holder, int position) {
            holder.bindTo(mData.get(position));
        }

        @Override
        public int getItemCount() {
            return mData.size();
        }
    }

    abstract private static class TodoViewHolder extends RecyclerView.ViewHolder {
        final CheckBox mCheckBox;
        Item mBoundItem;
        public TodoViewHolder(View itemView) {
            super(itemView);
            mCheckBox = (CheckBox) itemView;
            mCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                @Override
                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                    if (mBoundItem != null && isChecked != mBoundItem.mIsDone) {
                        onDoneChanged(isChecked);
                    }
                }
            });
        }

        public void bindTo(Item item) {
            mBoundItem = item;
            mCheckBox.setText(item.mText);
            mCheckBox.setChecked(item.mIsDone);
        }

        abstract void onDoneChanged(boolean isChecked);
    }

    private static class Item {
        String mText;
        boolean mIsDone = false;
        final public int id;
        private static int idCounter = 0;

        public Item(String text) {
            id = idCounter ++;
            this.mText = text;
        }
    }
}
person SilentKnight    schedule 15.05.2015
comment
Извините, что комментирую это спустя несколько месяцев, но я единственный, кто испытывает серьезные проблемы при добавлении нескольких предметов, вызывающих переработку? Если я добавляю, скажем, 12 элементов, он сходит с ума и начинает запускать какие-то движения, плюс удаляет все ранее введенные данные... - person fiipi; 23.12.2015
comment
Извините, я имел в виду примеры кодов, просто позвольте мне немного уточнить это. Может быть, лучше задать отдельный вопрос. - person fiipi; 24.12.2015
comment
TL;DR: используйте SortedListAdapterCallback при создании SortedList<> - person lionello; 18.12.2016

В исходном репозитории библиотеки поддержки есть пример SortedListActivity, который демонстрирует, как использовать SortedList и SortedListAdapterCallback внутри RecyclerView.Adapter. В корневом каталоге SDK с установленной библиотекой поддержки он должен находиться по адресу extras/android/support/samples/Support7Demos/src/com/example/android/supportv7/util/SortedListActivity.java (также на github).

Существование этих конкретных примеров упоминается ровно один раз в документации Google. , внизу страницы, посвященной другой теме, поэтому я не виню вас за то, что вы ее не нашли.

person moskvax    schedule 01.05.2015
comment
мертвые ссылки. вы знаете, где это сейчас? - person MidasLefko; 21.12.2016

Что касается реализации SortedList, она поддерживается массивом <T> с минимальной емкостью по умолчанию 10 элементов. Когда массив заполнен, размер массива изменяется до size() + 10.

Исходный код доступен здесь

Из документации

Реализация отсортированного списка, которая может упорядочивать элементы, а также уведомлять об изменениях в списке, чтобы его можно было привязать к RecyclerView.Adapter.

Он упорядочивает элементы с помощью метода compare(Object, Object) и использует двоичный поиск для извлечения элементов. Если критерии сортировки ваших элементов могут измениться, убедитесь, что вы вызываете соответствующие методы при их редактировании, чтобы избежать несоответствия данных.

Вы можете управлять порядком элементов и изменять уведомления через параметр SortedList.Callback.

Что касается производительности, они также добавили SortedList.BatchedCallback. выполнять несколько операций одновременно, а не по одной за раз

Реализация обратного вызова, которая может пакетно уведомлять о событиях, отправленных SortedList.

Этот класс может быть полезен, если вы хотите выполнять несколько операций над SortedList, но не хотите отправлять каждое событие одно за другим, что может привести к проблемам с производительностью.

Например, если вы собираетесь добавить несколько элементов в SortedList, вызов BatchedCallback преобразует отдельные вызовы onInserted(index, 1) в один onInserted(index, N), если элементы добавляются в последовательные индексы. Это изменение может помочь RecyclerView гораздо проще разрешать изменения.

Если последовательные изменения в SortedList не подходят для пакетной обработки, BatchingCallback отправляет их сразу же после обнаружения такого случая. После того, как ваши изменения в SortedList завершены, вы всегда должны вызывать dispatchLastEvent(), чтобы сбросить все изменения в обратный вызов.

person Axxiss    schedule 22.04.2015
comment
Это хорошо объясняет. Samples будет лучше. - person SilentKnight; 22.04.2015
comment
В настоящее время у меня нет времени, чтобы придумать образец, прочитать документацию и погрузиться в нее. - person Axxiss; 22.04.2015