/* * Copyright 2014 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.gdg.frisbee.android.fragment; import android.content.Context; import android.os.Bundle; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.StaggeredGridLayoutManager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import java.lang.ref.WeakReference; import timber.log.Timber; /** * Subclass of {@link android.support.v4.app.ListFragment} which provides automatic support for * providing the 'swipe-to-refresh' UX gesture by wrapping the the content view in a * {@link android.support.v4.widget.SwipeRefreshLayout}. */ public class SwipeRefreshRecyclerViewFragment extends GdgRecyclerFragment { private ListFragmentSwipeRefreshLayout mSwipeRefreshLayout; public View createSwipeRefresh(final View listFragmentView) { // Now create a SwipeRefreshLayout to wrap the fragment's content view mSwipeRefreshLayout = new ListFragmentSwipeRefreshLayout(getActivity()); // Add the list fragment's content view to the SwipeRefreshLayout, making sure that it fills // the SwipeRefreshLayout mSwipeRefreshLayout.addView(listFragmentView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); // Make sure that the SwipeRefreshLayout will fill the fragment mSwipeRefreshLayout.setLayoutParams( new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); mSwipeRefreshLayout.getViewTreeObserver().addOnGlobalLayoutListener( new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { mSwipeRefreshLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this); mSwipeRefreshLayout.setRecyclerView(getListView()); } }); // Now return the SwipeRefreshLayout as this fragment's content view return mSwipeRefreshLayout; } /** * Set the {@link android.support.v4.widget.SwipeRefreshLayout.OnRefreshListener} to listen for * initiated refreshes. * * @see android.support.v4.widget.SwipeRefreshLayout#setOnRefreshListener(SwipeRefreshLayout.OnRefreshListener) */ public void setOnRefreshListener(SwipeRefreshLayout.OnRefreshListener listener) { mSwipeRefreshLayout.setOnRefreshListener(listener); } /** * Returns whether the {@link android.support.v4.widget.SwipeRefreshLayout} is currently * refreshing or not. * * @see android.support.v4.widget.SwipeRefreshLayout#isRefreshing() */ public boolean isRefreshing() { return mSwipeRefreshLayout.isRefreshing(); } /** * Set whether the {@link android.support.v4.widget.SwipeRefreshLayout} should be displaying * that it is refreshing or not. * * @see android.support.v4.widget.SwipeRefreshLayout#setRefreshing(boolean) */ public void setRefreshing(boolean refreshing) { mSwipeRefreshLayout.setRefreshing(refreshing); } /** * Set the color scheme for the {@link android.support.v4.widget.SwipeRefreshLayout}. * */ public void setColorSchemeResources(int colorRes1, int colorRes2, int colorRes3, int colorRes4) { mSwipeRefreshLayout.setColorSchemeResources(colorRes1, colorRes2, colorRes3, colorRes4); } /** * @return the fragment's {@link android.support.v4.widget.SwipeRefreshLayout} widget. */ public SwipeRefreshLayout getSwipeRefreshLayout() { return mSwipeRefreshLayout; } /** * Sub-class of {@link android.support.v4.widget.SwipeRefreshLayout} for use in this * {@link android.support.v4.app.ListFragment}. The reason that this is needed is because * {@link android.support.v4.widget.SwipeRefreshLayout} only supports a single child, which it * expects to be the one which triggers refreshes. In our case the layout's child is the content * view returned from * {@link android.support.v4.app.ListFragment#onCreateView(LayoutInflater, ViewGroup, Bundle)} * which is a {@link android.view.ViewGroup}. * * <p>To enable 'swipe-to-refresh' support via the {@link android.widget.ListView} we need to * override the default behavior and properly signal when a gesture is possible. This is done by * overriding {@link #canChildScrollUp()}. */ private static class ListFragmentSwipeRefreshLayout extends SwipeRefreshLayout { private WeakReference<RecyclerView> recyclerView; public ListFragmentSwipeRefreshLayout(Context context) { super(context); } public void setRecyclerView(RecyclerView recyclerView) { this.recyclerView = new WeakReference<>(recyclerView); } /** * As mentioned above, we need to override this method to properly signal when a * 'swipe-to-refresh' is possible. * * @return true if the {@link android.widget.ListView} is visible and can scroll up. */ @Override public boolean canChildScrollUp() { RecyclerView view = recyclerView != null ? recyclerView.get() : null; return view != null && view.getVisibility() == View.VISIBLE && canListViewScrollUp(view); } /** * Utility method to check whether a {@link android.widget.ListView} can scroll up from it's current position. * Handles platform version differences, providing backwards compatible functionality where * needed. */ private boolean canListViewScrollUp(RecyclerView recyclerView) { try { RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); if (layoutManager instanceof LinearLayoutManager) { int position = ((LinearLayoutManager) layoutManager).findFirstCompletelyVisibleItemPosition(); return position != 0; } else if (layoutManager instanceof StaggeredGridLayoutManager) { int[] positions = ((StaggeredGridLayoutManager) layoutManager) .findFirstCompletelyVisibleItemPositions(null); for (int position : positions) { if (position == 0) { return false; } } } } catch (NullPointerException exception) { Timber.e(exception, "Exception in RecyclerView canListViewScrollUp."); } return true; } } }