package me.tatarka.bindingcollectionadapter2; import android.databinding.DataBindingUtil; import android.databinding.ObservableList; import android.databinding.OnRebindCallback; import android.databinding.ViewDataBinding; import android.support.annotation.LayoutRes; import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.ViewHolder; import android.view.LayoutInflater; import android.view.ViewGroup; import java.lang.ref.WeakReference; import java.util.List; /** * A {@link RecyclerView.Adapter} that binds items to layouts using the given {@link ItemBinding}. * If you give it an {@link ObservableList} it will also updated itself based on changes to that * list. */ public class BindingRecyclerViewAdapter<T> extends RecyclerView.Adapter<ViewHolder> implements BindingCollectionAdapter<T> { private static final Object DATA_INVALIDATION = new Object(); private ItemBinding<T> itemBinding; private final WeakReferenceOnListChangedCallback<T> callback = new WeakReferenceOnListChangedCallback<>(this); private List<T> items; private LayoutInflater inflater; private ItemIds<? super T> itemIds; private ViewHolderFactory viewHolderFactory; // Currently attached recyclerview, we don't have to listen to notifications if null. @Nullable private RecyclerView recyclerView; @Override public void setItemBinding(ItemBinding<T> itemBinding) { this.itemBinding = itemBinding; } @Override public ItemBinding<T> getItemBinding() { return itemBinding; } @Override public void setItems(@Nullable List<T> items) { if (this.items == items) { return; } // If a recyclerview is listening, set up listeners. Otherwise wait until one is attached. // No need to make a sound if nobody is listening right? if (recyclerView != null) { if (this.items instanceof ObservableList) { ((ObservableList<T>) this.items).removeOnListChangedCallback(callback); } if (items instanceof ObservableList) { ((ObservableList<T>) items).addOnListChangedCallback(callback); } } this.items = items; notifyDataSetChanged(); } @Override public T getAdapterItem(int position) { return items.get(position); } @Override public ViewDataBinding onCreateBinding(LayoutInflater inflater, @LayoutRes int layoutId, ViewGroup viewGroup) { return DataBindingUtil.inflate(inflater, layoutId, viewGroup, false); } @Override public void onBindBinding(ViewDataBinding binding, int variableId, @LayoutRes int layoutRes, int position, T item) { if (itemBinding.bind(binding, item)) { binding.executePendingBindings(); } } @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { if (this.recyclerView == null && items != null && items instanceof ObservableList) { ((ObservableList<T>) items).addOnListChangedCallback(callback); } this.recyclerView = recyclerView; } @Override public void onDetachedFromRecyclerView(RecyclerView recyclerView) { if (this.recyclerView != null && items != null && items instanceof ObservableList) { ((ObservableList<T>) items).removeOnListChangedCallback(callback); } this.recyclerView = null; } @Override public final ViewHolder onCreateViewHolder(ViewGroup viewGroup, int layoutId) { if (inflater == null) { inflater = LayoutInflater.from(viewGroup.getContext()); } ViewDataBinding binding = onCreateBinding(inflater, layoutId, viewGroup); final ViewHolder holder = onCreateViewHolder(binding); binding.addOnRebindCallback(new OnRebindCallback() { @Override public boolean onPreBind(ViewDataBinding binding) { return recyclerView != null && recyclerView.isComputingLayout(); } @Override public void onCanceled(ViewDataBinding binding) { if (recyclerView == null || recyclerView.isComputingLayout()) { return; } int position = holder.getAdapterPosition(); if (position != RecyclerView.NO_POSITION) { notifyItemChanged(position, DATA_INVALIDATION); } } }); return holder; } /** * Constructs a view holder for the given databinding. The default implementation is to use * {@link ViewHolderFactory} if provided, otherwise use a default view holder. */ public ViewHolder onCreateViewHolder(ViewDataBinding binding) { if (viewHolderFactory != null) { return viewHolderFactory.createViewHolder(binding); } else { return new BindingViewHolder(binding); } } private static class BindingViewHolder extends ViewHolder { public BindingViewHolder(ViewDataBinding binding) { super(binding.getRoot()); } } @Override public final void onBindViewHolder(ViewHolder viewHolder, int position) { T item = items.get(position); ViewDataBinding binding = DataBindingUtil.getBinding(viewHolder.itemView); onBindBinding(binding, itemBinding.variableId(), itemBinding.layoutRes(), position, item); } @Override public void onBindViewHolder(ViewHolder holder, int position, List<Object> payloads) { if (isForDataBinding(payloads)) { ViewDataBinding binding = DataBindingUtil.getBinding(holder.itemView); binding.executePendingBindings(); } else { super.onBindViewHolder(holder, position, payloads); } } private boolean isForDataBinding(List<Object> payloads) { if (payloads == null || payloads.size() == 0) { return false; } for (int i = 0; i < payloads.size(); i++) { Object obj = payloads.get(i); if (obj != DATA_INVALIDATION) { return false; } } return true; } @Override public int getItemViewType(int position) { itemBinding.onItemBind(position, items.get(position)); return itemBinding.layoutRes(); } /** * Set the item id's for the items. If not null, this will set {@link * android.support.v7.widget.RecyclerView.Adapter#setHasStableIds(boolean)} to true. */ public void setItemIds(@Nullable ItemIds<? super T> itemIds) { if (this.itemIds != itemIds) { this.itemIds = itemIds; setHasStableIds(itemIds != null); } } /** * Set the factory for creating view holders. If null, a default view holder will be used. This * is useful for holding custom state in the view holder or other more complex customization. */ public void setViewHolderFactory(@Nullable ViewHolderFactory factory) { viewHolderFactory = factory; } @Override public int getItemCount() { return items == null ? 0 : items.size(); } @Override public long getItemId(int position) { return itemIds == null ? position : itemIds.getItemId(position, items.get(position)); } private static class WeakReferenceOnListChangedCallback<T> extends ObservableList.OnListChangedCallback<ObservableList<T>> { final WeakReference<BindingRecyclerViewAdapter<T>> adapterRef; WeakReferenceOnListChangedCallback(BindingRecyclerViewAdapter<T> adapter) { this.adapterRef = new WeakReference<>(adapter); } @Override public void onChanged(ObservableList sender) { BindingRecyclerViewAdapter<T> adapter = adapterRef.get(); if (adapter == null) { return; } Utils.ensureChangeOnMainThread(); adapter.notifyDataSetChanged(); } @Override public void onItemRangeChanged(ObservableList sender, final int positionStart, final int itemCount) { BindingRecyclerViewAdapter<T> adapter = adapterRef.get(); if (adapter == null) { return; } Utils.ensureChangeOnMainThread(); adapter.notifyItemRangeChanged(positionStart, itemCount); } @Override public void onItemRangeInserted(ObservableList sender, final int positionStart, final int itemCount) { BindingRecyclerViewAdapter<T> adapter = adapterRef.get(); if (adapter == null) { return; } Utils.ensureChangeOnMainThread(); adapter.notifyItemRangeInserted(positionStart, itemCount); } @Override public void onItemRangeMoved(ObservableList sender, final int fromPosition, final int toPosition, final int itemCount) { BindingRecyclerViewAdapter<T> adapter = adapterRef.get(); if (adapter == null) { return; } Utils.ensureChangeOnMainThread(); for (int i = 0; i < itemCount; i++) { adapter.notifyItemMoved(fromPosition + i, toPosition + i); } } @Override public void onItemRangeRemoved(ObservableList sender, final int positionStart, final int itemCount) { BindingRecyclerViewAdapter<T> adapter = adapterRef.get(); if (adapter == null) { return; } Utils.ensureChangeOnMainThread(); adapter.notifyItemRangeRemoved(positionStart, itemCount); } } public interface ItemIds<T> { long getItemId(int position, T item); } public interface ViewHolderFactory { RecyclerView.ViewHolder createViewHolder(ViewDataBinding binding); } }