| /* |
| * Copyright 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package androidx.recyclerview.widget; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| |
| import java.util.List; |
| |
| /** |
| * {@link RecyclerView.Adapter RecyclerView.Adapter} base class for presenting List data in a |
| * {@link RecyclerView}, including computing diffs between Lists on a background thread. |
| * <p> |
| * This class is a convenience wrapper around {@link AsyncListDiffer} that implements Adapter common |
| * default behavior for item access and counting. |
| * <p> |
| * While using a LiveData<List> is an easy way to provide data to the adapter, it isn't required |
| * - you can use {@link #submitList(List)} when new lists are available. |
| * <p> |
| * A complete usage pattern with Room would look like this: |
| * <pre> |
| * {@literal @}Dao |
| * interface UserDao { |
| * {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC") |
| * public abstract LiveData<List<User>> usersByLastName(); |
| * } |
| * |
| * class MyViewModel extends ViewModel { |
| * public final LiveData<List<User>> usersList; |
| * public MyViewModel(UserDao userDao) { |
| * usersList = userDao.usersByLastName(); |
| * } |
| * } |
| * |
| * class MyActivity extends AppCompatActivity { |
| * {@literal @}Override |
| * public void onCreate(Bundle savedState) { |
| * super.onCreate(savedState); |
| * MyViewModel viewModel = new ViewModelProvider(this).get(MyViewModel.class); |
| * RecyclerView recyclerView = findViewById(R.id.user_list); |
| * UserAdapter<User> adapter = new UserAdapter(); |
| * viewModel.usersList.observe(this, list -> adapter.submitList(list)); |
| * recyclerView.setAdapter(adapter); |
| * } |
| * } |
| * |
| * class UserAdapter extends ListAdapter<User, UserViewHolder> { |
| * public UserAdapter() { |
| * super(User.DIFF_CALLBACK); |
| * } |
| * {@literal @}Override |
| * public void onBindViewHolder(UserViewHolder holder, int position) { |
| * holder.bindTo(getItem(position)); |
| * } |
| * public static final DiffUtil.ItemCallback<User> DIFF_CALLBACK = |
| * new DiffUtil.ItemCallback<User>() { |
| * {@literal @}Override |
| * public boolean areItemsTheSame( |
| * {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) { |
| * // User properties may have changed if reloaded from the DB, but ID is fixed |
| * return oldUser.getId() == newUser.getId(); |
| * } |
| * {@literal @}Override |
| * public boolean areContentsTheSame( |
| * {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) { |
| * // NOTE: if you use equals, your object must properly override Object#equals() |
| * // Incorrectly returning false here will result in too many animations. |
| * return oldUser.equals(newUser); |
| * } |
| * } |
| * }</pre> |
| * |
| * Advanced users that wish for more control over adapter behavior, or to provide a specific base |
| * class should refer to {@link AsyncListDiffer}, which provides custom mapping from diff events |
| * to adapter positions. |
| * |
| * @param <T> Type of the Lists this Adapter will receive. |
| * @param <VH> A class that extends ViewHolder that will be used by the adapter. |
| */ |
| public abstract class ListAdapter<T, VH extends RecyclerView.ViewHolder> |
| extends RecyclerView.Adapter<VH> { |
| final AsyncListDiffer<T> mDiffer; |
| private final AsyncListDiffer.ListListener<T> mListener = |
| new AsyncListDiffer.ListListener<T>() { |
| @Override |
| public void onCurrentListChanged( |
| @NonNull List<T> previousList, @NonNull List<T> currentList) { |
| ListAdapter.this.onCurrentListChanged(previousList, currentList); |
| } |
| }; |
| |
| @SuppressWarnings("unused") |
| protected ListAdapter(@NonNull DiffUtil.ItemCallback<T> diffCallback) { |
| mDiffer = new AsyncListDiffer<>(new AdapterListUpdateCallback(this), |
| new AsyncDifferConfig.Builder<>(diffCallback).build()); |
| mDiffer.addListListener(mListener); |
| } |
| |
| @SuppressWarnings("unused") |
| protected ListAdapter(@NonNull AsyncDifferConfig<T> config) { |
| mDiffer = new AsyncListDiffer<>(new AdapterListUpdateCallback(this), config); |
| mDiffer.addListListener(mListener); |
| } |
| |
| /** |
| * Submits a new list to be diffed, and displayed. |
| * <p> |
| * If a list is already being displayed, a diff will be computed on a background thread, which |
| * will dispatch Adapter.notifyItem events on the main thread. |
| * |
| * @param list The new list to be displayed. |
| */ |
| public void submitList(@Nullable List<T> list) { |
| mDiffer.submitList(list); |
| } |
| |
| /** |
| * Set the new list to be displayed. |
| * <p> |
| * If a List is already being displayed, a diff will be computed on a background thread, which |
| * will dispatch Adapter.notifyItem events on the main thread. |
| * <p> |
| * The commit callback can be used to know when the List is committed, but note that it |
| * may not be executed. If List B is submitted immediately after List A, and is |
| * committed directly, the callback associated with List A will not be run. |
| * |
| * @param list The new list to be displayed. |
| * @param commitCallback Optional runnable that is executed when the List is committed, if |
| * it is committed. |
| */ |
| public void submitList(@Nullable List<T> list, @Nullable final Runnable commitCallback) { |
| mDiffer.submitList(list, commitCallback); |
| } |
| |
| protected T getItem(int position) { |
| return mDiffer.getCurrentList().get(position); |
| } |
| |
| @Override |
| public int getItemCount() { |
| return mDiffer.getCurrentList().size(); |
| } |
| |
| /** |
| * Get the current List - any diffing to present this list has already been computed and |
| * dispatched via the ListUpdateCallback. |
| * <p> |
| * If a <code>null</code> List, or no List has been submitted, an empty list will be returned. |
| * <p> |
| * The returned list may not be mutated - mutations to content must be done through |
| * {@link #submitList(List)}. |
| * |
| * @return The list currently being displayed. |
| * |
| * @see #onCurrentListChanged(List, List) |
| */ |
| @NonNull |
| public List<T> getCurrentList() { |
| return mDiffer.getCurrentList(); |
| } |
| |
| /** |
| * Called when the current List is updated. |
| * <p> |
| * If a <code>null</code> List is passed to {@link #submitList(List)}, or no List has been |
| * submitted, the current List is represented as an empty List. |
| * |
| * @param previousList List that was displayed previously. |
| * @param currentList new List being displayed, will be empty if {@code null} was passed to |
| * {@link #submitList(List)}. |
| * |
| * @see #getCurrentList() |
| */ |
| public void onCurrentListChanged(@NonNull List<T> previousList, @NonNull List<T> currentList) { |
| } |
| } |