Qué es la SortedList que funciona con RecyclerView.Adaptador?


Android Support Library 22.1 fue lanzado ayer. Se agregaron muchas características nuevas a la biblioteca de soporte v4 y a la v7, entre las cuales android.support.v7.util.SortedList<T> llama mi atención.

Se dice que, SortedList es una nueva estructura de datos, funciona con RecyclerView.Adapter, mantiene las animaciones agregadas/eliminadas/movidas/cambiadas proporcionadas por RecyclerView. Suena como un List<T> en un ListView pero parece más avanzado y poderoso.

Entonces, ¿cuál es la diferencia entre SortedList<T> y List<T>? ¿Cómo podría usarlo? ¿eficientemente? ¿Cuál es la aplicación de SortedList<T> sobre List<T> si es así? ¿Alguien podría publicar algunas muestras?

Cualquier consejo o código será apreciado. Gracias de antemano.

Author: SilentKnight, 2015-04-22

4 answers

SortedList maneja la comunicación con el adaptador de reciclador a través de Callback.

Una diferencia entre SortedList y List se ve en el método auxiliar addAll en la muestra a continuación.

public void addAll(List<Page> items) {
        mPages.beginBatchedUpdates();
        for (Page item : items) {
            mPages.add(item);
        }
        mPages.endBatchedUpdates();
    }
  1. Mantiene el último elemento añadido

Digamos que tengo 10 elementos almacenados en caché para cargar inmediatamente cuando se rellene mi lista de recicladores. Al mismo tiempo, solicito a mi red los mismos 10 elementos porque podrían haber cambiado desde que los almacené en caché. Puedo llamar al mismo método addAll y SortedList lo haré reemplace los cachedItems con fetchedItems bajo el capó (siempre mantiene el último elemento agregado).

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

En un List regular, tendría duplicados de todos mis elementos (tamaño de lista de 20). Con SortedList reemplaza los elementos que son los mismos usando el Callback areItemsTheSame.

  1. Es inteligente acerca de cuándo actualizar las vistas

Cuando se agregan los fetchedItems, onChange solo se llamará si se cambia uno o más de los títulos de Page. Puede personalizar lo que SortedList busca en la devolución de llamada es areContentsTheSame.

  1. Su rendimiento

Si va a agregar varios elementos a una lista de clasificación, BatchedCallback call convierta las llamadas individuales onInserted(index, 1) en una onInserted(index, N) si los elementos se agregan a índices consecutivos. Este cambio puede ayudar a RecyclerView a resolver los cambios mucho más fácilmente.

Muestra

Puede tener un getter en su adaptador para su SortedList, pero acabo de decidir agregar métodos de ayuda a mi adaptador.

Clase de adaptador:

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

Clase de página:

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>

Clase Viewholder:

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


    public PageViewHolder(View itemView) {
        super(itemView);
        textView = (TextView)item.findViewById(R.id.text_view);
    }
}
 94
Author: Amozoss,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-23 18:57:42

SortedList está en v7 support library.

Una implementación SortedList que puede mantener los elementos en orden y también notificar los cambios en la lista de tal manera que pueda vincularse a un RecyclerView.Adapter.

Mantiene los elementos ordenados utilizando el método compare(Object, Object) y utiliza la búsqueda binaria para recuperar elementos. Si los criterios de clasificación de su los elementos pueden cambiar, asegúrese de llamar a los métodos apropiados mientras edita para evitar inconsistencias de datos.

Puede controlar el orden de los elementos y cambiar notificaciones a través del SortedList.Callback parámetro.

Aquí abajo hay una muestra de uso de SortedList, creo que es lo que quieres, echa un vistazo y disfrutar!

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;
        }
    }
}
 14
Author: SilentKnight,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-07 07:31:21

Hay un ejemplo de SortedListActivity en el repositorio de fuentes de la biblioteca de soporte que muestra cómo usar SortedList y SortedListAdapterCallback dentro de un RecyclerView.Adaptador. Desde la raíz del SDK, con la biblioteca de soporte instalada, debería estar en extras/android/support/samples/Support7Demos/src/com/example/android/supportv7/util/SortedListActivity.java (también en github).

La existencia de estas muestras particulares se menciona exactamente una vez en la documentación de Google, al final de una página que trata sobre un tema diferente, así que no te culpo por no lo encuentro.

 5
Author: moskvax,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-01 09:51:44

Acerca de la implementación de SortedList, está respaldada por una matriz de <T> con una capacidad mínima predeterminada de 10 elementos. Una vez que la matriz está completa, la matriz se redimensiona a size() + 10

El código fuente está disponible aquí

De la documentación

Una implementación de lista ordenada que puede mantener los elementos en orden y también notificar los cambios en la lista de tal manera que pueda vincularse a un RecyclerView.Adaptador.

Mantiene los elementos ordenados usando el compare (Object, Objeto) método y utiliza la búsqueda binaria para recuperar elementos. Si los criterios de clasificación de su los elementos pueden cambiar, asegúrese de llamar a los métodos apropiados mientras edita para evitar inconsistencias de datos.

Puede controlar el orden de los artículos y las notificaciones de cambio a través del SortedList.Parámetro Callback.

En cuanto al rendimiento, también agregaron SortedList.BatchedCallback para llevar a cabo múltiples operaciones a la vez en lugar de una a la vez

Una implementación de devolución de llamada que puede notificar por lotes los eventos enviados por SortedList.

Esta clase puede ser útil si desea realizar varias operaciones en un SortedList pero no desea enviar cada evento uno por uno, lo que puede dar lugar a un problema de rendimiento.

Por ejemplo, si va a agregar varios elementos a una lista de clasificación, BatchedCallback call convertir llamadas individuales onInserted (índice, 1) en un onInserted (índice, N) si los elementos se agregan en consecutivo Indice. Este cambio puede ayudar a RecyclerView a resolver los cambios mucho más facilmente.

Si los cambios consecutivos en la lista de clasificación no son adecuados para batching, BatchingCallback los envía tan pronto como tal caso es detectar. Después de completar las ediciones en la lista de clasificación, debe siempre llame a dispatchLastEvent () para vaciar todos los cambios en la devolución de llamada.

 2
Author: Axxiss,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-22 00:51:33