1 /*
2  * Copyright 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *       http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.example.android.swiperefreshlistfragment;
18 
19 import android.content.Context;
20 import android.os.Bundle;
21 import android.support.v4.app.ListFragment;
22 import android.support.v4.view.ViewCompat;
23 import android.support.v4.widget.SwipeRefreshLayout;
24 import android.view.LayoutInflater;
25 import android.view.View;
26 import android.view.ViewGroup;
27 import android.widget.ListView;
28 
29 /**
30  * Subclass of {@link android.support.v4.app.ListFragment} which provides automatic support for
31  * providing the 'swipe-to-refresh' UX gesture by wrapping the the content view in a
32  * {@link android.support.v4.widget.SwipeRefreshLayout}.
33  */
34 public class SwipeRefreshListFragment extends ListFragment {
35 
36     private SwipeRefreshLayout mSwipeRefreshLayout;
37 
38     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)39     public View onCreateView(LayoutInflater inflater, ViewGroup container,
40             Bundle savedInstanceState) {
41 
42         // Create the list fragment's content view by calling the super method
43         final View listFragmentView = super.onCreateView(inflater, container, savedInstanceState);
44 
45         // Now create a SwipeRefreshLayout to wrap the fragment's content view
46         mSwipeRefreshLayout = new ListFragmentSwipeRefreshLayout(container.getContext());
47 
48         // Add the list fragment's content view to the SwipeRefreshLayout, making sure that it fills
49         // the SwipeRefreshLayout
50         mSwipeRefreshLayout.addView(listFragmentView,
51                 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
52 
53         // Make sure that the SwipeRefreshLayout will fill the fragment
54         mSwipeRefreshLayout.setLayoutParams(
55                 new ViewGroup.LayoutParams(
56                         ViewGroup.LayoutParams.MATCH_PARENT,
57                         ViewGroup.LayoutParams.MATCH_PARENT));
58 
59         // Now return the SwipeRefreshLayout as this fragment's content view
60         return mSwipeRefreshLayout;
61     }
62 
63     /**
64      * Set the {@link android.support.v4.widget.SwipeRefreshLayout.OnRefreshListener} to listen for
65      * initiated refreshes.
66      *
67      * @see android.support.v4.widget.SwipeRefreshLayout#setOnRefreshListener(android.support.v4.widget.SwipeRefreshLayout.OnRefreshListener)
68      */
setOnRefreshListener(SwipeRefreshLayout.OnRefreshListener listener)69     public void setOnRefreshListener(SwipeRefreshLayout.OnRefreshListener listener) {
70         mSwipeRefreshLayout.setOnRefreshListener(listener);
71     }
72 
73     /**
74      * Returns whether the {@link android.support.v4.widget.SwipeRefreshLayout} is currently
75      * refreshing or not.
76      *
77      * @see android.support.v4.widget.SwipeRefreshLayout#isRefreshing()
78      */
isRefreshing()79     public boolean isRefreshing() {
80         return mSwipeRefreshLayout.isRefreshing();
81     }
82 
83     /**
84      * Set whether the {@link android.support.v4.widget.SwipeRefreshLayout} should be displaying
85      * that it is refreshing or not.
86      *
87      * @see android.support.v4.widget.SwipeRefreshLayout#setRefreshing(boolean)
88      */
setRefreshing(boolean refreshing)89     public void setRefreshing(boolean refreshing) {
90         mSwipeRefreshLayout.setRefreshing(refreshing);
91     }
92 
93     /**
94      * Set the color scheme for the {@link android.support.v4.widget.SwipeRefreshLayout}.
95      *
96      * @see android.support.v4.widget.SwipeRefreshLayout#setColorScheme(int, int, int, int)
97      */
setColorScheme(int colorRes1, int colorRes2, int colorRes3, int colorRes4)98     public void setColorScheme(int colorRes1, int colorRes2, int colorRes3, int colorRes4) {
99         mSwipeRefreshLayout.setColorScheme(colorRes1, colorRes2, colorRes3, colorRes4);
100     }
101 
102     /**
103      * @return the fragment's {@link android.support.v4.widget.SwipeRefreshLayout} widget.
104      */
getSwipeRefreshLayout()105     public SwipeRefreshLayout getSwipeRefreshLayout() {
106         return mSwipeRefreshLayout;
107     }
108 
109     /**
110      * Sub-class of {@link android.support.v4.widget.SwipeRefreshLayout} for use in this
111      * {@link android.support.v4.app.ListFragment}. The reason that this is needed is because
112      * {@link android.support.v4.widget.SwipeRefreshLayout} only supports a single child, which it
113      * expects to be the one which triggers refreshes. In our case the layout's child is the content
114      * view returned from
115      * {@link android.support.v4.app.ListFragment#onCreateView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle)}
116      * which is a {@link android.view.ViewGroup}.
117      *
118      * <p>To enable 'swipe-to-refresh' support via the {@link android.widget.ListView} we need to
119      * override the default behavior and properly signal when a gesture is possible. This is done by
120      * overriding {@link #canChildScrollUp()}.
121      */
122     private class ListFragmentSwipeRefreshLayout extends SwipeRefreshLayout {
123 
ListFragmentSwipeRefreshLayout(Context context)124         public ListFragmentSwipeRefreshLayout(Context context) {
125             super(context);
126         }
127 
128         /**
129          * As mentioned above, we need to override this method to properly signal when a
130          * 'swipe-to-refresh' is possible.
131          *
132          * @return true if the {@link android.widget.ListView} is visible and can scroll up.
133          */
134         @Override
canChildScrollUp()135         public boolean canChildScrollUp() {
136             final ListView listView = getListView();
137             if (listView.getVisibility() == View.VISIBLE) {
138                 return canListViewScrollUp(listView);
139             } else {
140                 return false;
141             }
142         }
143 
144     }
145 
146     // BEGIN_INCLUDE (check_list_can_scroll)
147     /**
148      * Utility method to check whether a {@link ListView} can scroll up from it's current position.
149      * Handles platform version differences, providing backwards compatible functionality where
150      * needed.
151      */
canListViewScrollUp(ListView listView)152     private static boolean canListViewScrollUp(ListView listView) {
153         if (android.os.Build.VERSION.SDK_INT >= 14) {
154             // For ICS and above we can call canScrollVertically() to determine this
155             return ViewCompat.canScrollVertically(listView, -1);
156         } else {
157             // Pre-ICS we need to manually check the first visible item and the child view's top
158             // value
159             return listView.getChildCount() > 0 &&
160                     (listView.getFirstVisiblePosition() > 0
161                             || listView.getChildAt(0).getTop() < listView.getPaddingTop());
162         }
163     }
164     // END_INCLUDE (check_list_can_scroll)
165 
166 }
167