SwiperefreshLayout in Android

2019-01-17 16:31发布

问题:

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.

回答1:

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;
  }
});


回答2:

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;
}


回答3:

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);
    }
}


回答4:

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.



回答5:

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)



回答6:

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



回答7:

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;
    }

}