/* * Copyright (C) 2014 Simon Vig Therkildsen * * 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 net.simonvt.cathode.ui.fragment; import android.content.Context; import android.os.Bundle; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.TextView; import butterknife.BindView; import net.simonvt.cathode.R; public abstract class RecyclerViewFragment<T extends RecyclerView.ViewHolder> extends BaseFragment { private static final String SAVED_EMPTY_TEXT = "savedEmptyText"; private static final int STATE_NONE = -1; private static final int STATE_PROGRESS_VISIBLE = 0; private static final int STATE_CONTENT_VISIBLE = 1; private RecyclerView.Adapter<T> adapter; @BindView(R.id.progressContainer) View progressContainer; @BindView(R.id.listContainer) View listContainer; @BindView(android.R.id.list) RecyclerView recyclerView; @BindView(android.R.id.empty) TextView empty; private Context appContext; private String emptyText; private boolean animating; private int currentState = STATE_PROGRESS_VISIBLE; private int pendingStateChange = STATE_NONE; private boolean forceDisplayProgress; @Override public void onCreate(Bundle inState) { super.onCreate(inState); appContext = getActivity().getApplicationContext(); if (inState != null) { emptyText = inState.getString(SAVED_EMPTY_TEXT); } } @Override public void onSaveInstanceState(Bundle outState) { outState.putString(SAVED_EMPTY_TEXT, emptyText); super.onSaveInstanceState(outState); } protected RecyclerView.ItemAnimator getItemAnimator() { return null; } protected void addItemDecorations(RecyclerView recyclerView) { } protected abstract RecyclerView.LayoutManager getLayoutManager(); @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle inState) { return inflater.inflate(R.layout.fragment_recyclerview, container, false); } public void onViewCreated(View view, Bundle inState) { super.onViewCreated(view, inState); recyclerView.setLayoutManager(getLayoutManager()); RecyclerView.ItemAnimator itemAnimator = getItemAnimator(); if (itemAnimator != null) { recyclerView.setItemAnimator(itemAnimator); } else { ((DefaultItemAnimator) recyclerView.getItemAnimator()).setSupportsChangeAnimations(false); } addItemDecorations(recyclerView); if (empty != null) { if (emptyText != null) { empty.setText(emptyText); } if (adapter != null && adapter.getItemCount() > 0) { empty.setVisibility(View.GONE); } else { empty.setVisibility(View.VISIBLE); } } if (adapter != null) { recyclerView.setAdapter(adapter); } if (adapter == null) { listContainer.setVisibility(View.GONE); progressContainer.setVisibility(View.VISIBLE); currentState = STATE_PROGRESS_VISIBLE; } else { currentState = STATE_CONTENT_VISIBLE; listContainer.setVisibility(View.VISIBLE); progressContainer.setVisibility(View.GONE); } } @Override public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) { Animation animation = null; if (nextAnim != 0) { animation = AnimationUtils.loadAnimation(getActivity(), nextAnim); animation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { animating = true; } @Override public void onAnimationEnd(Animation animation) { animating = false; if (pendingStateChange != STATE_NONE) { changeState(pendingStateChange, true); pendingStateChange = STATE_NONE; } } @Override public void onAnimationRepeat(Animation animation) { } }); } return animation; } private void changeState(final int newState, final boolean animate) { if (newState == currentState) { return; } if (animating) { pendingStateChange = newState; return; } currentState = newState; updateViewVisiblity(animate); } private void updateViewVisiblity(boolean animate) { if (currentState == STATE_NONE) { return; } if (forceDisplayProgress || currentState == STATE_PROGRESS_VISIBLE) { if (listContainer.getVisibility() != View.GONE) { hideContent(animate); } displayProgress(animate); } else { if (progressContainer.getVisibility() != View.GONE) { hideProgress(animate); } displayContent(animate); } } private void displayProgress(boolean animate) { final boolean wasGone = progressContainer.getVisibility() == View.GONE; progressContainer.setVisibility(View.VISIBLE); if (animate) { if (wasGone) { progressContainer.setAlpha(0.0f); } progressContainer.animate().alpha(1.0f); } } private void hideProgress(boolean animate) { final View progressContainer = this.progressContainer; if (!animate) { progressContainer.setVisibility(View.GONE); } else { if (progressContainer.getVisibility() != View.GONE) { progressContainer.animate().alpha(0.0f).withEndAction(new Runnable() { @Override public void run() { progressContainer.setVisibility(View.GONE); } }); } } } private void displayContent(boolean animate) { final boolean wasGone = listContainer.getVisibility() == View.GONE; listContainer.setVisibility(View.VISIBLE); if (animate) { if (wasGone) { listContainer.setAlpha(0.0f); } listContainer.animate().alpha(1.0f); } } private void hideContent(boolean animate) { final View listContainer = this.listContainer; if (!animate) { listContainer.setVisibility(View.GONE); } else { if (listContainer.getVisibility() != View.GONE) { listContainer.animate().alpha(0.0f).withEndAction(new Runnable() { @Override public void run() { listContainer.setVisibility(View.GONE); } }); } } } public void setForceDisplayProgress(boolean forceDisplayProgress) { if (forceDisplayProgress == this.forceDisplayProgress) { return; } this.forceDisplayProgress = forceDisplayProgress; updateViewVisiblity(true); } public RecyclerView getRecyclerView() { return recyclerView; } public void setAdapter(RecyclerView.Adapter<T> adapter) { if (adapter != this.adapter) { if (this.adapter != null) { this.adapter.unregisterAdapterDataObserver(adapterObserver); } this.adapter = adapter; if (recyclerView != null) { recyclerView.setAdapter(this.adapter); } if (this.adapter != null) { if (recyclerView != null) { changeState(STATE_CONTENT_VISIBLE, true); } adapter.registerAdapterDataObserver(adapterObserver); if (empty != null) { if (adapter.getItemCount() > 0) { empty.setVisibility(View.GONE); } else { empty.setVisibility(View.VISIBLE); } } } else if (recyclerView != null) { recyclerView.setAdapter(null); changeState(STATE_PROGRESS_VISIBLE, true); } } } private void showEmptyView(boolean show) { final View empty = this.empty; if (empty == null) { return; } if (show && empty.getVisibility() == View.GONE) { empty.setAlpha(0.0f); empty.animate().alpha(1.0f).withStartAction(new Runnable() { @Override public void run() { empty.setVisibility(View.VISIBLE); } }); } else if (!show && empty.getVisibility() == View.VISIBLE) { empty.animate().alpha(0.0f).withEndAction(new Runnable() { @Override public void run() { empty.setAlpha(1.0f); empty.setVisibility(View.GONE); } }); } } private RecyclerView.AdapterDataObserver adapterObserver = new RecyclerView.AdapterDataObserver() { @Override public void onChanged() { final int adapterItemCount = adapter.getItemCount(); showEmptyView(adapterItemCount == 0); } @Override public void onItemRangeChanged(int positionStart, int itemCount) { final int adapterItemCount = adapter.getItemCount(); showEmptyView(adapterItemCount == 0); } @Override public void onItemRangeInserted(int positionStart, int itemCount) { final int adapterItemCount = adapter.getItemCount(); showEmptyView(adapterItemCount == 0); } @Override public void onItemRangeRemoved(int positionStart, int itemCount) { final int adapterItemCount = adapter.getItemCount(); showEmptyView(adapterItemCount == 0); } }; public RecyclerView.Adapter<T> getAdapter() { return adapter; } public void clearEmptyText() { emptyText = ""; if (empty != null) { empty.setText(emptyText); } } public final void setEmptyText(String text) { if (text == null) { text = ""; } emptyText = text; if (empty != null) { empty.setText(emptyText); } } public final void setEmptyText(int resId) { String text = appContext.getString(resId); if (text != null) { emptyText = text; if (empty != null) { empty.setText(emptyText); } } } public final void setEmptyText(int resId, Object... formatArgs) { String text = appContext.getString(resId, formatArgs); if (text != null) { emptyText = text; if (empty != null) { empty.setText(emptyText); } } } }