SwiperefreshLayout in Android
Asked Answered
H

7

18

i am using SwipeRefreshLayout in my below layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:background="@color/homePageBackground"
  android:orientation="vertical" >


<android.support.v4.widget.SwipeRefreshLayout
    android:id="@+id/swipeRefreshLayout_listView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" >
<fragment
        android:id="@+id/announcementHomefragment"
        android:name="in.test.app.AnnouncementFragment"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/homePageBackground" >

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="15dp"
                android:background="@color/homePageBackground" >

                <TextView
                    android:id="@+id/newsTitle"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:layout_marginLeft="15dp"
                    android:layout_marginTop="5dp"
                    android:gravity="center"
                    android:text="@string/new_list"
                    android:textAppearance="?android:attr/textAppearanceSmall"
                    android:textColor="@color/white"
                    android:textStyle="bold" />

                <fragment
                    android:id="@+id/newshomefragment"
                    android:name="in.test.app.NewsFragment"
                    android:layout_width="wrap_content"
                    android:layout_height="190dp"
                    android:layout_below="@id/newsTitle"
                    android:layout_marginTop="-15dp" />

                <TextView
                    android:id="@+id/productTitle"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_below="@id/newshomefragment"
                    android:layout_gravity="center"
                    android:layout_marginLeft="15dp"
                    android:layout_marginTop="5dp"
                    android:gravity="center"
                    android:text="@string/product_in_home"
                    android:textAppearance="?android:attr/textAppearanceSmall"
                    android:textColor="@color/white"
                    android:textStyle="bold" />

                <fragment
                    android:id="@+id/proCategoryhomefragment"
                    android:name="in.test.app.CategoryFragment"
                    android:layout_width="wrap_content"
                    android:layout_height="170dp"
                    android:layout_below="@id/productTitle"
                    android:layout_marginTop="-15dp" />

                <TextView
                    android:id="@+id/trainingTitle"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_below="@id/proCategoryhomefragment"
                    android:layout_gravity="center"
                    android:layout_marginLeft="15dp"
                    android:layout_marginTop="2dp"
                    android:gravity="center"
                    android:text="@string/trainings_in_home"
                    android:textAppearance="?android:attr/textAppearanceSmall"
                    android:textColor="@color/white"
                    android:textStyle="bold" />

                <fragment
                    android:id="@+id/trainingfragment"
                    android:name="in.test.app.TrainingFragment"
                    android:layout_width="match_parent"
                    android:layout_height="180dp"
                    android:layout_below="@id/trainingTitle"
                    android:layout_marginBottom="10dp"
                    android:layout_marginTop="-15dp" />
            </RelativeLayout>
        </ScrollView>
    </LinearLayout>
</android.support.v4.widget.SwipeRefreshLayout>

When I pull down my SwipeRefreshLayout it is working, but as you can see in the above code I have a scroll view inside that. So when I am pulling down my scroll view, it goes down and half the images are not showing because it came down. When I am trying to pull up again my scroll view is not going up. Instead, SwipeRefreshLayout is getting call. What should i do?

Please help me out.

Haydeehayden answered 23/4, 2014 at 6:37 Comment(0)
F
29

I would say it's better to have an extended SwipeRefreshLayout with listener to be able to add various conditions from the classes that display this layout.

Something like the following: GeneralSwipeRefreshLayout.java

public class GeneralSwipeRefreshLayout extends SwipeRefreshLayout {   
  private OnChildScrollUpListener mScrollListenerNeeded;

  public interface OnChildScrollUpListener {
    boolean canChildScrollUp();
  }

  public GeneralSwipeRefreshLayout(Context context) {
    super(context);   
  }
  public GeneralSwipeRefreshLayout(Context context, AttributeSet attrs) {
    super(context, attrs);   
  }

 /**
  * Listener that controls if scrolling up is allowed to child views or not
  */
  public void setOnChildScrollUpListener(OnChildScrollUpListener listener) {
    mScrollListenerNeeded = listener;   
  }

  @Override
  public boolean canChildScrollUp() {
    if (mScrollListenerNeeded == null) {
      Log.e(GeneralSwipeRefreshLayout.class.getSimpleName(), "listener is not defined!");
    }
    return mScrollListenerNeeded != null && mScrollListenerNeeded.canChildScrollUp();
  }
}

And then inside your class that displays SwipeRefreshLayout containing ListView or GridView layout, you can do something like this:

mSwipeLayout.setOnChildScrollUpListener(new OnChildScrollUpListener() {
  @Override
  public boolean canChildScrollUp() {
    return mListView.getFirstVisiblePosition() > 0 || 
           mListView.getChildAt(0) == null || 
           mListView.getChildAt(0).getTop() < 0;
  }
});
Fiji answered 22/10, 2014 at 22:18 Comment(2)
And don't forget to change your xml layout from android.support.v4.widget.SwipeRefreshLayout to yourpackage.GeneralSwipeRefreshLayoutBacillus
How would you change it for RecyclerView ?Unwished
H
6

Just create a class which extends SwipeRefreshLayout and override the method canChildScrollUp(). Return true when you want scroll down for your control.

For example for scrollview you may try this,

@override.
boolean canChildScrollUp()
{
   //your condition to check scrollview reached at top while scrolling
   if(scrollview.getScrollY() == 0.0)
      return true;
   else
      return false;
}
Hydrophyte answered 23/4, 2014 at 6:50 Comment(9)
thanks for your response. i have solved it by changing some XML. But anyway thanks for support.Haydeehayden
+1 for great answer.It works for me but what sort of condition are you talking about? @User22791Lat
If i just keep return true then refreshing doesnt work for meLat
@droid_dev yes. You can't put just true there. It will always allow your list or scrollview whatever to scroll up. You must use some sort of condition that match up your criteriaHydrophyte
@rup35h what did you change in your layout? post your answer too so that others can also get help.Hydrophyte
@User22791 I got it but thinking how to access listview in another class? How it will listen runtime?Lat
let us continue this discussion in chatHydrophyte
@droid_dev Try making your listview public static.Hydrophyte
if(scrollview.getScrollY() == 0.0) return true; else return false; - hm, why not return scrollview.getScrollY() == 0?Antimatter
I
5

As others have already stated, if you don't have your scrollable view (ie listview) as the direct child of the SwipeRefreshLayout, the stock canChildScrollUp will not work.

Has to do with the simple logic SwipeRefreshLayout uses in checking the ability of the child view to scroll.

I was using a ListView inside an ActionbarActivity, and wanted to include an empty view whenever my listview was empty. This caused problems, since the SwipeRefreshLayout class can only have a single child. Note it also checks this child's ability to scrollUp to determine if a pull down causes the child to scrollUp, or if it causes the childs content to refresh.

So if you want to use the same logic as SwipeRefreshLayout, just extend the class, and create a method to allow you to pass in the handle to your scrollable view. Note the stock implementation uses canScrollVertically() which does exactly what we want, but only appears in SDK >= 14.

Also don't forget to include the constructor that contains the param "AttributeSet", when you extend the class, otherwise you will have problems using the class in your layout files.

So, in the onCreate method of your Activity (in my case it was an ActionBarActivity) that includes the list view, just call setMyScrollableView passing in your ListView or whatever view you use that scrolls.

/*Constructor*/
public MySwipeRefreshLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    // TODO Auto-generated constructor stub
}


 View mMyScrollableView = null;  //The content that get's pulled down.
 /*Method used to pass in my scrollable view*/
 public void setMyScrollableView(View view){
    mMyScrollableView = view;
 }

/**
 * @return Whether it is possible for the child view of this layout to
 * scroll up.  This was taken for the most part directly from SwipeRefreshLayout
 */
public boolean canChildScrollUp() {
    if(mMyScrollableView == null)
      return false;
    if (android.os.Build.VERSION.SDK_INT < 14) {
        if (mMyScrollableView instanceof AbsListView) {
            final AbsListView absListView = (AbsListView) mMyScrollableView;
            return absListView.getChildCount() > 0
        && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
        .getTop() < absListView.getPaddingTop());
        } else {
            return mMyScrollableView.getScrollY() > 0;
        }
    } else {
        return ViewCompat.canScrollVertically(mMyScrollableView, -1);
    }
}
Inebriety answered 29/7, 2014 at 20:42 Comment(0)
N
3

The solution from @User22791 works perfectly and, based on that, I created a library available on github that you can use (and modify) for make the usage of swipeRefreshLayout easier for developers. It's here: https://github.com/dagova/referencedSwipeRefreshLayout

Basically you just have to reference in your layout the view to be checked in the method canChildScrollUp. I hope it will be useful.

Nucleolus answered 29/5, 2014 at 9:56 Comment(0)
L
2

I also found the other answers didn't quite work.

Took me a while of head scratching to figure out that they are using the method getScrollY() which as this answer explains, is a View method describing how far it's been scroll within a container, not a method to describe how much your Scroll container has been scrolled.

If you use the same technique as in the other answers (overriding the canChildScrollUp() method) you can easily check if the Scrollable is at it's highest point:

@Override
public boolean canChildScrollUp() {
    return !isListAtTop();
}

private boolean isListAtTop()   {   
    if(mGridView.getChildCount() == 0) return true;
    return mGridView.getChildAt(0).getTop() == 0;
}

(As you can see, I'm using a GridView, but you can use a ListView too)

Lacewing answered 3/7, 2014 at 13:46 Comment(1)
Such solution contains a bug: mGridView.getChildAt(0) will return first visible child, but not a child at absolute 0 position. That's why the user will be stuck every time the first visible view is scrolled to 0.Fiji
P
2

Easier solution is to use onScrollListener and check if user can see firstElement.

someView.setOnScrollListener(new AbsListView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(AbsListView absListView, int scrollState) {
        if (scrollState == SCROLL_STATE_IDLE) {
            if (isViewAtTop()) {
                swipeLayout.setEnabled(true);
            } else {
                swipeLayout.setEnabled(false);
            }
        }

    }

    @Override
    public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        if (firstVisibleItem == 0) {
            swipeLayout.setEnabled(true);
        } else {
            swipeLayout.setEnabled(false);
        }
    }
});

Where method isViewAtTop() is some other method, that checks this View is scrolled to the top

Pericope answered 25/7, 2014 at 9:30 Comment(0)
P
1

Ok I have got it working. If the SwipeRefreshLayout is the root of the layout and the ScrollView resides deep into the hierarchy (I had put the ScrollView inside a RelativeLayout) and not the direct child of the SwipeRefreshLayout, it won’t detect a swipe up on the ScrollView properly.

You should create a custom class that extends SwipeRefreshLayout and override canChildScrollUp() method in SwipRefreshLayout

Here is a example :

 public class CustomSwipeRefreshLayout extends SwipeRefreshLayout {

    private ScrollView scrollview;

    public CustomSwipeRefreshLayout(Context context) {
        super(context);
    }

    public CustomSwipeRefreshLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public void setView(ScrollView view) {
        this.scrollview = view;
    }

    @Override
    public boolean canChildScrollUp() {
        return scrollview.getScrollY() != 0;
    }

}
Planula answered 15/5, 2014 at 13:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.