package be.digitalia.fosdem.fragments; import android.content.Context; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.TextView; import be.digitalia.fosdem.R; import be.digitalia.fosdem.widgets.ContentLoadingProgressBar; /** * Fragment providing a RecyclerView, an empty view and a progress bar. * * @author Christophe Beyls */ public class RecyclerViewFragment extends Fragment { private static final float DEFAULT_EMPTY_VIEW_PADDING_DIPS = 16f; static class ViewHolder { FrameLayout container; RecyclerView recyclerView; View emptyView; ContentLoadingProgressBar progress; } private class EmptyViewAwareRecyclerView extends RecyclerView { private final AdapterDataObserver mEmptyObserver = new AdapterDataObserver() { @Override public void onChanged() { updateEmptyViewVisibility(); } @Override public void onItemRangeInserted(int positionStart, int itemCount) { updateEmptyViewVisibility(); } @Override public void onItemRangeRemoved(int positionStart, int itemCount) { updateEmptyViewVisibility(); } }; public EmptyViewAwareRecyclerView(Context context) { super(context); } public EmptyViewAwareRecyclerView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public EmptyViewAwareRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public void setAdapter(Adapter adapter) { final Adapter oldAdapter = getAdapter(); if (oldAdapter != null) { oldAdapter.unregisterAdapterDataObserver(mEmptyObserver); } super.setAdapter(adapter); if (adapter != null) { adapter.registerAdapterDataObserver(mEmptyObserver); } updateEmptyViewVisibility(); } } private ViewHolder mHolder; private boolean mIsProgressBarVisible; /** * Override this method to provide a custom Empty View. * The default one is a TextView with some padding. */ @NonNull protected View onCreateEmptyView(LayoutInflater inflater, ViewGroup container, @Nullable Bundle savedInstanceState) { TextView textView = new TextView(inflater.getContext()); textView.setGravity(Gravity.CENTER); int textPadding = (int) (getResources().getDisplayMetrics().density * DEFAULT_EMPTY_VIEW_PADDING_DIPS + 0.5f); textView.setPadding(textPadding, textPadding, textPadding, textPadding); return textView; } /** * Override this method to setup the RecyclerView (LayoutManager, ItemDecoration, Adapter) */ protected void onRecyclerViewCreated(RecyclerView recyclerView, @Nullable Bundle savedInstanceState) { } @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { final Context context = inflater.getContext(); mHolder = new ViewHolder(); mHolder.container = new FrameLayout(context); mHolder.recyclerView = new EmptyViewAwareRecyclerView(context, null, R.attr.recyclerViewStyle); mHolder.recyclerView.setId(android.R.id.list); mHolder.recyclerView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); mHolder.recyclerView.setHasFixedSize(true); mHolder.container.addView(mHolder.recyclerView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); mHolder.emptyView = onCreateEmptyView(inflater, mHolder.container, savedInstanceState); mHolder.emptyView.setId(android.R.id.empty); mHolder.emptyView.setVisibility(View.GONE); mHolder.container.addView(mHolder.emptyView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); mHolder.progress = new ContentLoadingProgressBar(context, null, android.R.attr.progressBarStyleLarge); mHolder.progress.setId(android.R.id.progress); mHolder.progress.hide(); mHolder.container.addView(mHolder.progress, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER)); mHolder.container.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); onRecyclerViewCreated(mHolder.recyclerView, savedInstanceState); return mHolder.container; } @Override public void onDestroyView() { // Ensure the RecyclerView is properly unregistered as an observer of the adapter mHolder.recyclerView.setAdapter(null); mHolder = null; mIsProgressBarVisible = false; super.onDestroyView(); } /** * Get the fragments's RecyclerView widget. */ public RecyclerView getRecyclerView() { return mHolder.recyclerView; } /** * The default content for a RecyclerViewFragment has a TextView that can be shown when the list is empty. * Call this method to supply the text it should use. */ public void setEmptyText(CharSequence text) { ((TextView) mHolder.emptyView).setText(text); } void updateEmptyViewVisibility() { if (!mIsProgressBarVisible) { RecyclerView.Adapter adapter = mHolder.recyclerView.getAdapter(); final boolean isEmptyViewVisible = (adapter != null) && (adapter.getItemCount() == 0); mHolder.recyclerView.setVisibility(isEmptyViewVisible ? View.GONE : View.VISIBLE); mHolder.emptyView.setVisibility(isEmptyViewVisible ? View.VISIBLE : View.GONE); } } /** * Call this method to show or hide the indeterminate progress bar. * When shown, the RecyclerView will be hidden. * * @param visible true to show the progress bar, false to hide it. The initial value is false. */ public void setProgressBarVisible(boolean visible) { if (mIsProgressBarVisible != visible) { mIsProgressBarVisible = visible; if (visible) { mHolder.recyclerView.setVisibility(View.GONE); mHolder.emptyView.setVisibility(View.GONE); mHolder.progress.show(); } else { updateEmptyViewVisibility(); mHolder.progress.hide(); } } } }