How to completely resort RecyclerView's SortedList
Asked Answered
D

3

6

The RecyclerView library recently added the new SortedList class. Suppose I have a callback that implements a compare() method that can change over time, i.e. the underlying Comparator can be switched out. What's the best way to tell the SortedList to completely resort its data?

Dogface answered 22/4, 2015 at 12:48 Comment(3)
If I had to guess, beginBatchedUpdates(), recalculatePositionOfItemAt() (for each item), and endBatchedUpdates(), but that's just a guess based upon the API.Alberthaalberti
worst case scenario, the developer.android.com/reference/android/support/v7/widget/util/… callback has methods for when an element is changed, but the right answer is probably @Alberthaalberti 's answer.Argybargy
That's not a bad start. But calling recalculatePositionOfItemAt() for every index won't resort it completely. Indices will change which leads to some items being skipped.Dogface
D
2

Here's my own take on it (written in Kotlin):

list.beginBatchedUpdates()

val indices = (0..list.size() - 1).toArrayList()

while (!indices.isEmpty()) {
    val i = indices.first()
    val item = list.get(i)

    list.recalculatePositionOfItemAt(i)

    [suppress("USELESS_CAST_STATIC_ASSERT_IS_FINE")]
    indices.remove(list.indexOf(item) as Any) //cast to disambiguate remove()
}

list.endBatchedUpdates()

As you can see, I'm tracking the new index after every call to recalculatePositionOfItemAt() so every item is resorted only once and no item is skipped.

This works but seems really wasteful because recalculatePositionOfItemAt() will resize the underlying array twice to remove and then readd the item. indexOf will then perform a new binary search even though the index is already known.

Edit: This seems to lead to an infinite loop if items compare as equal.

Alternative approach (remove all, then add all):

list.beginBatchedUpdates()

val copy = (list.size() - 1 downTo 0).map { list.removeItemAt(it) }
copy.forEach { list.add(it) }

list.endBatchedUpdates()
Dogface answered 22/4, 2015 at 13:11 Comment(0)
S
0

Unfortunately, there is no api for this. Just copy the source and add that method yourself. Backing data is just an array so you can call Arrays.sort on it, then call notify data set changed. Tracking changes is not trivial when the whole world changes so not really suitable case for sorted list. Please create a feature request on b.Android.com.

Solidify answered 23/4, 2015 at 17:1 Comment(2)
Yeah, I realized animating a complete resorting on more than 5 items looks really weird anyway.Dogface
Btw, you don't need to copy the source. Since the underlying array mData is package private, you can access it via a helper in the same package.Dogface
N
0

The following method worked for me to switch to a new comparator to re-sort the list. I found the animation much smoother with an additional call to notifyDataSetChanged()

private final List<MyObject> mTmp = new ArrayList<>();
private Comparator<MyObject> mComparator = DEFAULT_COMPARATOR;

private final SortedList<MyObject> mSortedList = new SortedList<>(MyObject.class, new SortedList.Callback<MyObject>() {
    @Override
    public int compare(MyObject a, MyObject b) {
        return mComparator.compare(a, b);
    }

    // and other methods...
});

public void changeComparator(@NonNull Comparator<MyObject> comp) {
    mComparator = comp;

    mSortedList.beginBatchedUpdates();

    for(int i = 0; i < mSortedList.size(); ++i) {
        mTmp.add(mSortedList.get(i));
    }
    mSortedList.clear();
    mSortedList.addAll(mTmp);
    mTmp.clear();

    mSortedList.endBatchedUpdates();

    notifyDataSetChanged();
}
Nadbus answered 22/2, 2020 at 18:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.