open-keychain/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/RecyclerFragment.java
2016-12-05 12:55:16 +01:00

413 lines
13 KiB
Java

/*
* Copyright (C) 2013 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 org.sufficientlysecure.keychain.ui.base;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.support.v7.widget.RecyclerView;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.util.Log;
/*
* Implementation of taken from the sourcecode of
* android.support.v4.app.ListFragment from the
* Android Open Source Project and changed to use
* RecyclerView instead of ListView.
*/
public class RecyclerFragment<A extends RecyclerView.Adapter> extends Fragment {
protected static final int INTERNAL_LIST_VIEW_ID = android.R.id.list;
protected static final int INTERNAL_EMPTY_VIEW_ID = android.R.id.empty;
protected static final int INTERNAL_LIST_CONTAINER_ID = android.R.id.widget_frame;
protected static final int INTERNAL_PROGRESS_CONTAINER_ID = android.R.id.progress;
private final Handler handler = new Handler();
private final Runnable requestFocus = new Runnable() {
@Override
public void run() {
listView.focusableViewAvailable(listView);
}
};
private boolean observerRegistered = false;
private final RecyclerView.AdapterDataObserver dataObserver = new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
super.onChanged();
checkDataSet();
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
super.onItemRangeInserted(positionStart, itemCount);
checkDataSet();
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
super.onItemRangeRemoved(positionStart, itemCount);
checkDataSet();
}
};
private final RecyclerView.OnScrollListener scrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
RecyclerFragment.this.onScrolled(dx, dy);
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
RecyclerFragment.this.onScrollStateChanged(newState);
}
};
private A adapter;
private RecyclerView.LayoutManager layoutManager;
private RecyclerView listView;
private View emptyView;
private View progressContainer;
private View listContainer;
private boolean listShown;
public RecyclerFragment() {
super();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
final Context context = getContext();
FrameLayout root = new FrameLayout(context);
LinearLayout progressContainer = new LinearLayout(context);
progressContainer.setId(INTERNAL_PROGRESS_CONTAINER_ID);
progressContainer.setOrientation(LinearLayout.VERTICAL);
progressContainer.setGravity(Gravity.CENTER);
progressContainer.setVisibility(View.GONE);
ProgressBar progressBar = new ProgressBar(context, null,
android.R.attr.progressBarStyleLarge);
progressContainer.addView(progressBar, new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
root.addView(progressContainer, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
FrameLayout listContainer = new FrameLayout(context);
listContainer.setId(INTERNAL_LIST_CONTAINER_ID);
TextView textView = new TextView(context);
textView.setId(INTERNAL_EMPTY_VIEW_ID);
textView.setGravity(Gravity.CENTER);
listContainer.addView(textView, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
RecyclerView listView = new RecyclerView(context);
listView.setId(INTERNAL_LIST_VIEW_ID);
int padding = FormattingUtils.dpToPx(context, 8);
listView.setPadding(padding, 0, padding, 0);
listView.setClipToPadding(false);
listContainer.addView(listView, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
root.addView(listContainer, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
root.setLayoutParams(new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return root;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ensureList();
}
@Override
public void onDestroyView() {
handler.removeCallbacks(requestFocus);
listView.setLayoutManager(null);
listView.removeOnScrollListener(scrollListener);
listView = null;
listShown = false;
listContainer = null;
layoutManager = null;
emptyView = null;
progressContainer = null;
super.onDestroyView();
}
@Override
public void onDestroy() {
//setAdapter(null);
super.onDestroy();
}
public Handler getHandler() {
return handler;
}
public void onScrollStateChanged(int state) {
// empty body
}
public void onScrolled(int dx, int dy) {
// empty body
}
public void setAdapter(A adapter) {
unregisterObserver();
boolean hadAdapter = this.adapter != null;
this.adapter = adapter;
registerObserver();
if (listView != null) {
listView.setAdapter(adapter);
if (!listShown && !hadAdapter) {
if (getView() != null)
setListShown(true, getView().getWindowToken() != null);
}
}
}
public void setLayoutManager(RecyclerView.LayoutManager manager) {
if (!manager.isAttachedToWindow()) {
layoutManager = manager;
if (listView != null) {
listView.setLayoutManager(manager);
}
}
}
public int getItemCount() {
if (adapter != null)
return adapter.getItemCount();
else
return 0;
}
public long getItemId(int position) {
if (adapter != null)
return adapter.getItemId(position);
else
return View.NO_ID;
}
public RecyclerView getRecyclerView() {
ensureList();
return listView;
}
public RecyclerView.LayoutManager getLayoutManager() {
ensureList();
return layoutManager;
}
private void registerObserver() {
if (!observerRegistered && adapter != null) {
adapter.registerAdapterDataObserver(dataObserver);
observerRegistered = true;
}
}
private void unregisterObserver() {
if (observerRegistered && adapter != null) {
adapter.unregisterAdapterDataObserver(dataObserver);
observerRegistered = false;
}
}
private void checkDataSet() {
boolean empty = treatAsEmpty(getItemCount());
Log.d("RecyclerFragment", "Dataset change detected! Count: "
+ getItemCount() + ", Empty: " + empty);
if (emptyView != null) {
emptyView.setVisibility(empty ? View.VISIBLE : View.GONE);
}
}
/**
* Set whether the data set of the recycler view should be treated as empty.
* This is useful e.g. if you have an empty padding row and therefore the item
* count is always greater than 0.
*
* @param itemCount the number of items in the data set.
* @return Whether to treat this as an empty set of data
*/
protected boolean treatAsEmpty(int itemCount) {
return itemCount < 1;
}
/**
* Set whether the recycler view should have a fixed size or not
*/
protected boolean isFixedSize() {
return true;
}
public void hideList(boolean animated) {
setListShown(false, animated);
}
public void showList(boolean animated) {
setListShown(true, animated);
}
public void setEmptyText(String text) {
ensureList();
if (emptyView instanceof TextView) {
((TextView) emptyView).setText(text);
} else {
Log.e(Constants.TAG, "Cannot set empty text on a view that is null" +
"or not an instance of android.view.View!");
}
}
private void setListShown(boolean shown, boolean animated) {
ensureList();
if (progressContainer == null)
throw new IllegalStateException("Can't be used with a custom content view");
if (listShown == shown)
return;
listShown = shown;
if (shown) {
if (animated) {
progressContainer.startAnimation(AnimationUtils.loadAnimation(
getActivity(), android.R.anim.fade_out));
listContainer.startAnimation(AnimationUtils.loadAnimation(
getActivity(), android.R.anim.fade_in));
} else {
progressContainer.clearAnimation();
listContainer.clearAnimation();
}
progressContainer.setVisibility(View.GONE);
listContainer.setVisibility(View.VISIBLE);
} else {
if (animated) {
progressContainer.startAnimation(AnimationUtils.loadAnimation(
getActivity(), android.R.anim.fade_in));
listContainer.startAnimation(AnimationUtils.loadAnimation(
getActivity(), android.R.anim.fade_out));
} else {
progressContainer.clearAnimation();
listContainer.clearAnimation();
}
progressContainer.setVisibility(View.VISIBLE);
listContainer.setVisibility(View.GONE);
}
}
public A getAdapter() {
return adapter;
}
@SuppressWarnings("unchecked")
private void ensureList() {
if (listView != null) {
return;
}
View root = getView();
if (root == null) {
throw new IllegalStateException("Content view not yet created");
}
if (root instanceof RecyclerView) {
listView = (RecyclerView) root;
} else {
emptyView = root.findViewById(INTERNAL_EMPTY_VIEW_ID);
if (emptyView != null) {
emptyView.setVisibility(View.GONE);
}
progressContainer = root.findViewById(INTERNAL_PROGRESS_CONTAINER_ID);
listContainer = root.findViewById(INTERNAL_LIST_CONTAINER_ID);
View rawListView = root.findViewById(INTERNAL_LIST_VIEW_ID);
if (!(rawListView instanceof RecyclerView)) {
if (rawListView == null) {
throw new RuntimeException(
"Your content must have a RecyclerView whose id attribute is " +
"'android.R.id.list'");
}
throw new RuntimeException(
"Content has view with id attribute 'android.R.id.list' "
+ "that is not a ListView class");
}
listView = (RecyclerView) rawListView;
}
if (layoutManager != null) {
RecyclerView.LayoutManager manager = layoutManager;
this.layoutManager = null;
setLayoutManager(manager);
}
listShown = true;
listView.setHasFixedSize(isFixedSize());
listView.addOnScrollListener(scrollListener);
if (this.adapter != null) {
A adapter = this.adapter;
this.adapter = null;
setAdapter(adapter);
} else {
// We are starting without an adapter, so assume we won't
// have our data right away and start with the progress indicator.
if (progressContainer != null) {
setListShown(false, false);
}
}
handler.post(requestFocus);
}
}