Created
October 9, 2017 16:17
-
-
Save amaksoft/7737efb44bbcecc8cc262eb3874f16c5 to your computer and use it in GitHub Desktop.
Missing emptyVIew for RecyclerVIew
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.amaksoft.recyclerviewtools.adapter; | |
import android.support.annotation.IdRes; | |
import android.support.annotation.LayoutRes; | |
import android.support.annotation.Nullable; | |
import android.support.v7.widget.GridLayoutManager; | |
import android.support.v7.widget.LinearLayoutManager; | |
import android.support.v7.widget.RecyclerView; | |
import android.view.LayoutInflater; | |
import android.view.View; | |
import android.view.ViewGroup; | |
import android.widget.LinearLayout; | |
import android.widget.TextView; | |
import ru.altarix.mpgu3.R; | |
/** | |
* Адаптер {@link RecyclerView} для отображения плейсхолдера при пустом списке. | |
* Является оберткой для обычного адаптера. | |
* Если список в {@link #originalAdapter} пуст, то показывается плейсхолдер, иначе сам список. | |
* Плейсхолдер отобразится только после первого вызова одного из notify- методов, если данных нет. | |
* Работа проверена на {@link GridLayoutManager} и {@link LinearLayoutManager} | |
* | |
* Created by gars and amak on 4/13/17. | |
*/ | |
@SuppressWarnings({"unused", "WeakerAccess"}) | |
public class PlaceholderRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { | |
private int placeholderViewType = Integer.MAX_VALUE; | |
private String message; | |
protected RecyclerView.Adapter originalAdapter; | |
@SuppressWarnings("WeakerAccess") | |
protected RecyclerView recyclerView; | |
@LayoutRes | |
final int placeholderViewId; | |
@IdRes | |
@SuppressWarnings("WeakerAccess") | |
final int errorTextViewId; | |
protected boolean notified; | |
private boolean prevEmpty; | |
@Nullable | |
private PlaceholderToggleListener placeholderToggleListener; | |
/** | |
* Создает плейсхолдер-адаптер с вьюхой по умолчанию {@link R.layout#empty_list_placeholder} | |
* @param originalAdapter адаптер, отображающий сам список | |
*/ | |
public PlaceholderRecyclerViewAdapter(RecyclerView.Adapter originalAdapter) { | |
this(originalAdapter, R.layout.empty_list_placeholder, R.id.tvEmptyListMessage, false); | |
} | |
/** | |
* Создает плейсхолдер-адаптер с вьюхой по умолчанию {@link R.layout#empty_list_placeholder} | |
* @param originalAdapter адаптер, отображающий сам список | |
* @param showImmediately - отображать ли плейсхолдер сразу при подключении адаптера к | |
* {@link RecyclerView} (если false - покажет после первого вызова одного notify* методов) | |
*/ | |
public PlaceholderRecyclerViewAdapter(RecyclerView.Adapter originalAdapter, boolean showImmediately) { | |
this(originalAdapter, R.layout.empty_list_placeholder, R.id.tvEmptyListMessage, showImmediately); | |
} | |
/** | |
* Создает плейсхолдер-адаптер с заданной вьюхой и стандартным id {@link TextView}, отображающего ошибку {@link R.id#tvEmptyListMessage} | |
* @param originalAdapter адаптер, отображающий сам список | |
* @param placeholderViewId - id лейаута плейсхолдера | |
*/ | |
public PlaceholderRecyclerViewAdapter(RecyclerView.Adapter originalAdapter, @LayoutRes int placeholderViewId) { | |
this(originalAdapter, placeholderViewId, R.id.tvEmptyListMessage, false); | |
} | |
/** | |
* Создает плейсхолдер-адаптер с заданной вьюхой и стандартным id {@link TextView}, отображающего ошибку {@link R.id#tvEmptyListMessage} | |
* @param originalAdapter адаптер, отображающий сам список | |
* @param placeholderViewId - id лейаута плейсхолдера | |
* @param showImmediately - отображать ли плейсхолдер сразу при подключении адаптера к | |
* {@link RecyclerView} (если false - покажет после первого вызова одного notify* методов) | |
*/ | |
public PlaceholderRecyclerViewAdapter(RecyclerView.Adapter originalAdapter, @LayoutRes int placeholderViewId, boolean showImmediately) { | |
this(originalAdapter, placeholderViewId, R.id.tvEmptyListMessage, showImmediately); | |
} | |
/** | |
* Создает плейсхолдер-адаптер с вьюхой по умолчанию | |
* @param originalAdapter адаптер, отображающий сам список | |
* @param placeholderViewId - id лейаута плейсхолдера | |
* @param errorTextViewId - id {@link TextView}, в котором будет отображено сообщение об ошибке. | |
* @param showImmediately - отображать ли плейсхолдер сразу при подключении адаптера к | |
* {@link RecyclerView} (если false - покажет после первого вызова одного notify* методов) | |
*/ | |
public PlaceholderRecyclerViewAdapter(RecyclerView.Adapter originalAdapter, @LayoutRes int placeholderViewId, @IdRes int errorTextViewId, boolean showImmediately) { | |
this.originalAdapter = originalAdapter; | |
this.placeholderViewId = placeholderViewId; | |
this.errorTextViewId = errorTextViewId; | |
this.originalAdapter.registerAdapterDataObserver(originalAdapterObserver); | |
this.registerAdapterDataObserver(placeholderAdapterObserver); | |
this.notified = showImmediately; | |
} | |
/** | |
* Задает сообщение, отображаемое в плейсхолдере | |
* | |
* @param mMessage сообщение | |
*/ | |
public void setMessage(String mMessage) { | |
this.message = mMessage; | |
} | |
/** | |
* Позволяет узнать viewType, соответствующий плейсхолдеру. | |
* @return viewType плейсхолдера | |
*/ | |
public int getPlaceholderViewType() { | |
return placeholderViewType; | |
} | |
/** | |
* Позволяет при необходимости изменить {@link Integer}, соответствующий viewType. По умолчанию - {@link Integer#MAX_VALUE} | |
* @param placeholderViewType желаемый viewType плейсхолдера. | |
*/ | |
public void setPlaceholderViewType(int placeholderViewType) { | |
this.placeholderViewType = placeholderViewType; | |
} | |
/** | |
* Задает модуль для подготовки {@link RecyclerView} к отображению плейсхолдера | |
* @param placeholderToggleListener имплементация интерфейса | |
*/ | |
public void setPlaceholderToggleListener(@Nullable PlaceholderToggleListener placeholderToggleListener) { | |
this.placeholderToggleListener = placeholderToggleListener; | |
} | |
/** | |
* Возвращает текущий модуль для подготовки {@link RecyclerView} к отображению плейсхолдера | |
*/ | |
@Nullable | |
public PlaceholderToggleListener getPlaceholderToggleListener() { | |
return placeholderToggleListener; | |
} | |
@Override | |
public int getItemViewType(int position) { | |
return isEmpty() ? placeholderViewType : originalAdapter.getItemViewType(position); | |
} | |
/** | |
* Обновляет состояние {@link RecyclerView}. | |
* Работает аналогично {@link #notifyDataSetChanged()}, вынесено в отдельный метод, т.к. | |
* {@link #notifyDataSetChanged()} является final и не может быть переопределен в субклассах. | |
* @deprecated используйте обычные методы адаптера: {@link #notifyDataSetChanged()}, {@link #notifyItemInserted(int)} и прочие. | |
* Этот метод скоро будет удален. | |
*/ | |
@Deprecated | |
public void notifyDataChanged() { | |
prepareRecyclerView(recyclerView); | |
notifyDataSetChanged(); | |
} | |
/** | |
* Подготавливает {@link RecyclerView} к отображению плейсхолдера (переформатирует {@link android.support.v7.widget.RecyclerView.LayoutManager}) | |
* @param recyclerView - {@link RecyclerView}, к которому применяем изменения | |
*/ | |
@SuppressWarnings("WeakerAccess") | |
void prepareRecyclerView(RecyclerView recyclerView) { | |
notified = true; | |
if (isEmpty() ^ prevEmpty) { // Делаем перестройки RecyclerView только при смене состояния с пустого на заполненный и назад | |
if (placeholderToggleListener != null) { | |
placeholderToggleListener.onPlaceholderToggle(recyclerView, isEmpty()); | |
} | |
} | |
prevEmpty = isEmpty(); | |
} | |
@Override | |
public void onAttachedToRecyclerView(RecyclerView recyclerView) { | |
super.onAttachedToRecyclerView(recyclerView); | |
this.recyclerView = recyclerView; | |
if (message == null) { | |
message = recyclerView.getContext().getString(R.string.no_data); | |
} | |
if (placeholderToggleListener == null && recyclerView.getLayoutManager() instanceof GridLayoutManager) { | |
placeholderToggleListener = GRID_LAYOUT_MANAGER_PREPARATION; | |
} | |
// если адаптер создается уже со списком то грид остается и количество колонок не меняется | |
boolean prevNotify = notified; | |
// поэтому дергаем это | |
prepareRecyclerView(recyclerView); | |
notified = prevNotify; | |
} | |
@Override | |
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { | |
if (viewType == placeholderViewType) { | |
return onCreateEmptyViewHolder(parent, viewType); | |
} else { | |
return originalAdapter.createViewHolder(parent, viewType); | |
} | |
} | |
@Override | |
public long getItemId(int position) { | |
if (isEmpty()) | |
return super.getItemId(position); | |
return originalAdapter.getItemId(position); | |
} | |
/** | |
* Метод создает {@link RecyclerView.ViewHolder} с вьюхой плейсхолдера. | |
* При наследовании от этого класса можно передать свой (с обработкой кликов и пр. дополнениями) | |
*/ | |
public RecyclerView.ViewHolder onCreateEmptyViewHolder(ViewGroup parent, int viewType) { | |
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); | |
View v = layoutInflater.inflate(placeholderViewId, parent, false); | |
RecyclerView.ViewHolder holder = new ViewHolder(v); | |
v.setLayoutParams(new LinearLayout.LayoutParams( | |
LinearLayout.LayoutParams.MATCH_PARENT, | |
LinearLayout.LayoutParams.MATCH_PARENT | |
)); | |
return holder; | |
} | |
@Override | |
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { | |
if (getItemViewType(position) == placeholderViewType) { | |
onBindEmptyViewHolder(holder, position); | |
} else { | |
//noinspection unchecked | |
originalAdapter.bindViewHolder(holder, position); | |
} | |
} | |
/** | |
* Метод подготавливает {@link RecyclerView.ViewHolder} с вьюхой плейсхолдера к отображению | |
* При наследовании от этого класса можно дополнить чем нужно | |
*/ | |
@SuppressWarnings("WeakerAccess") | |
public void onBindEmptyViewHolder(RecyclerView.ViewHolder holder, int position) { | |
holder.itemView.setVisibility(notified ? View.VISIBLE : View.GONE); | |
((ViewHolder) holder).setMessage(message); | |
} | |
@Override | |
public int getItemCount() { | |
return isEmpty() ? 1 : originalAdapter.getItemCount(); | |
} | |
class ViewHolder extends RecyclerView.ViewHolder { | |
private TextView tvMessage; | |
ViewHolder(View itemView) { | |
super(itemView); | |
View vMessage = itemView.findViewById(errorTextViewId); | |
if (vMessage instanceof TextView) tvMessage = (TextView) vMessage; | |
} | |
public void setMessage(String message) { | |
if (tvMessage != null) tvMessage.setText(message); | |
} | |
} | |
private boolean isEmpty() { | |
return originalAdapter.getItemCount() == 0; | |
} | |
/** | |
* Этот {@link RecyclerView.AdapterDataObserver} обрабатывает вызовы notify* методов, вызванных из | |
* оригинального адаптера и обновляет обертку (и тем самым {@link RecyclerView} т.к. он подписан на нее) | |
*/ | |
@SuppressWarnings("FieldCanBeLocal") | |
protected RecyclerView.AdapterDataObserver originalAdapterObserver = new RecyclerView.AdapterDataObserver() { | |
@Override | |
public void onChanged() { | |
PlaceholderRecyclerViewAdapter.this.notifyDataSetChanged(); | |
} | |
@Override | |
public void onItemRangeChanged(int positionStart, int itemCount) { | |
PlaceholderRecyclerViewAdapter.this.notifyItemRangeChanged(positionStart, itemCount); | |
} | |
@Override | |
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { | |
PlaceholderRecyclerViewAdapter.this.notifyItemRangeChanged(positionStart, itemCount, payload); | |
} | |
@Override | |
public void onItemRangeInserted(int positionStart, int itemCount) { | |
PlaceholderRecyclerViewAdapter.this.notifyItemRangeInserted(positionStart, itemCount); | |
} | |
@Override | |
public void onItemRangeRemoved(int positionStart, int itemCount) { | |
PlaceholderRecyclerViewAdapter.this.notifyItemRangeRemoved(positionStart, itemCount); | |
} | |
@Override | |
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { | |
PlaceholderRecyclerViewAdapter.this.notifyDataSetChanged(); | |
} | |
}; | |
/** | |
* Этот {@link RecyclerView.AdapterDataObserver} обрабатывает вызовы notify* методов перед {@link RecyclerView} | |
* и выполняет все подготовительные операции. | |
*/ | |
@SuppressWarnings("FieldCanBeLocal") | |
private RecyclerView.AdapterDataObserver placeholderAdapterObserver = new RecyclerView.AdapterDataObserver() { | |
@Override | |
public void onChanged() { | |
prepareRecyclerView(recyclerView); | |
} | |
@Override | |
public void onItemRangeChanged(int positionStart, int itemCount) { | |
prepareRecyclerView(recyclerView); | |
} | |
@Override | |
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { | |
prepareRecyclerView(recyclerView); | |
} | |
@Override | |
public void onItemRangeInserted(int positionStart, int itemCount) { | |
prepareRecyclerView(recyclerView); | |
} | |
@Override | |
public void onItemRangeRemoved(int positionStart, int itemCount) { | |
prepareRecyclerView(recyclerView); | |
} | |
@Override | |
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { | |
prepareRecyclerView(recyclerView); | |
} | |
}; | |
/** | |
* Интерфейс для кастомизации подготовки {@link RecyclerView} к отображению плейсхолдера на весь размер. | |
* Например для {@link GridLayoutManager} необходимо изменить SpanCount на 1, а затем вернуть назад при отображении списка. | |
* Или спрятать конфликтующие с плейсхолдером {@link android.support.v7.widget.RecyclerView.ItemDecoration}. | |
* Или выполнить любые другие операции, изначально не предусмотренные данным адаптером. | |
* @see #GRID_LAYOUT_MANAGER_PREPARATION пример использования в случае {@link GridLayoutManager} | |
*/ | |
interface PlaceholderToggleListener { | |
/** | |
* Метод вызывается перед оповещением {@link RecyclerView} о смене состояния списка. | |
* Вызывается <b>только при смене состояния<b/> с заполненного (в оригинальном адаптере есть | |
* элементы) на пустое (элементов нет, нужно показать плейсхолдер) и обратно. | |
* @param recyclerView {@link RecyclerView}, который необходимо подготовить | |
* @param empty состояние адаптера: true - пустой, false - в списке есть элементы. | |
*/ | |
void onPlaceholderToggle(RecyclerView recyclerView, boolean empty); | |
} | |
/** | |
* Подготавливает {@link RecyclerView} с {@link GridLayoutManager} для отображения плейсхолдера (меняет колчество столбцов на 1 и обратно) | |
*/ | |
public static final PlaceholderToggleListener GRID_LAYOUT_MANAGER_PREPARATION = new PlaceholderToggleListener() { | |
private int originalGridSpanCount; | |
@Override | |
public void onPlaceholderToggle(RecyclerView recyclerView, boolean empty) { | |
if (recyclerView.getLayoutManager() instanceof GridLayoutManager) { | |
GridLayoutManager gridLayoutManager = (GridLayoutManager) recyclerView.getLayoutManager(); | |
if (empty) { | |
originalGridSpanCount = gridLayoutManager.getSpanCount(); | |
gridLayoutManager.setSpanCount(1); | |
} else { | |
gridLayoutManager.setSpanCount(originalGridSpanCount); | |
} | |
} | |
} | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment