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

Вчера была выпущена

Android Support Library 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>, если это так? Может ли кто-нибудь опубликовать некоторые его образцы?

Будут оценены любые советы или коды. Спасибо заранее.

Ответ 1

SortedList находится в v7 support library.

A 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;
        }
    }
}

Ответ 2

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

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

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

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

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

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

  1. Внимательно оценивает, когда обновлять представления

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

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

Если вы собираетесь добавить несколько элементов в SortedList, вызов BatchedCallback конвертирует отдельные inInserted (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;
    }
}

Viewholder 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);
    }
}

Ответ 3

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

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

Ответ 4

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

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

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

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

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

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

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

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

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

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

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