Android: CollapsingToolbarLayout and SwipeRefreshLayout get stuck
Asked Answered
V

6

68

I use CollapsingToolbarLayout, RecyclerView and SwipeRefreshLayout together:

Xml:

<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

<android.support.design.widget.CoordinatorLayout
    android:id="@+id/coordinator_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

<android.support.design.widget.AppBarLayout
    android:id="@+id/appbar"
    android:layout_width="match_parent"
    android:layout_height="@dimen/collapse_toolbar_height"
    android:fitsSystemWindows="true"
    android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

    <android.support.design.widget.CollapsingToolbarLayout
        android:id="@+id/collapsing_toolbar"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:contentScrim="?attr/colorPrimary"
        android:fitsSystemWindows="true"
        app:expandedTitleMarginStart="48dp"
        app:expandedTitleMarginEnd="64dp"
        app:layout_scrollFlags="scroll|exitUntilCollapsed">

        <ImageView
            android:id="@+id/toolbar_image"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="centerCrop"
            android:fitsSystemWindows="true"
            app:layout_collapseMode="parallax" />

        <include
            layout="@layout/activity_main_toolbar"/>

    </android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swipe_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <cz.yetanotherview.webcamviewer.app.helper.EmptyRecyclerView
            android:id="@+id/mainList"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scrollbars="vertical" />
    </android.support.v4.widget.SwipeRefreshLayout>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/floating_action_button"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        app:layout_anchor="@id/appbar"
        app:layout_anchorGravity="bottom|right|end"
        android:layout_margin="16dp"
        app:fabSize="mini"
        android:src="@drawable/ic_action_edit"
        android:onClick="assignSelectedWebCamsToCategory"/>

    <com.github.clans.fab.FloatingActionMenu
        android:id="@+id/floating_action_menu"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="bottom|end"
        android:paddingRight="10dp"
        android:paddingBottom="8dp"
        android:paddingLeft="10dp"
        fab:menu_shadowColor="#37000000"
        fab:menu_colorNormal="#DA4336"
        fab:menu_colorPressed="#E75043"
        fab:menu_colorRipple="#99FFFFFF"
        fab:menu_icon="@drawable/fab_add"
        fab:menu_buttonSpacing="10dp"
        fab:menu_labels_textColor="@color/very_dark_grey"
        fab:menu_labels_textSize="14sp"
        fab:menu_labels_colorNormal="@color/white"
        fab:menu_labels_colorPressed="@color/next_grey"
        fab:menu_labels_colorRipple="#99FFFFFF"
        fab:menu_labels_margin="8dp"
        fab:menu_backgroundColor="@color/black_transparent">

        <com.github.clans.fab.FloatingActionButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_action_content_import"
            fab:fab_size="mini"
            fab:fab_label="@string/pref_import_from_server"
            fab:fab_colorNormal="@color/white"
            app:fab_colorPressed="@color/next_grey"
            app:fab_colorRipple="#99FFFFFF"
            android:onClick="showSelectionDialog"/>

        <com.github.clans.fab.FloatingActionButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_action_content_manually"
            fab:fab_size="mini"
            fab:fab_label="@string/create_manually"
            fab:fab_colorNormal="@color/white"
            app:fab_colorPressed="@color/next_grey"
            app:fab_colorRipple="#99FFFFFF"
            android:onClick="showAddDialog"/>

        <com.github.clans.fab.FloatingActionButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_action_content_suggestion"
            fab:fab_size="mini"
            fab:fab_label="@string/submit_suggestion"
            fab:fab_colorNormal="@color/white"
            app:fab_colorPressed="@color/next_grey"
            app:fab_colorRipple="#99FFFFFF"
            android:onClick="showSuggestionDialog"/>

    </com.github.clans.fab.FloatingActionMenu>
</android.support.design.widget.CoordinatorLayout>

<include
    layout="@layout/activity_main_drawer"/>
</android.support.v4.widget.DrawerLayout>

Code:

    swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_container);
    swipeRefreshLayout.setOnRefreshListener(this);

How to allow swipe refresh action only when collapsing toolbar layout is fully expanded and scrollview (recyclerview) on top? The similar behavior like in Google+ or Inbox application.

Wrong:

enter image description here

Good:

enter image description here

Valuation answered 11/6, 2015 at 11:35 Comment(0)
V
28

Finally,

I found that SwipeRefreshLayout works without any "hacks" from Support Library version 23.1.1.

Simply use in your layout:

 <android.support.v4.widget.SwipeRefreshLayout
    android:id="@+id/swipe_refresh_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scrollbars="vertical" />
</android.support.v4.widget.SwipeRefreshLayout>

and in code:

SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout);
swipeRefreshLayout.setColorSchemeResources(R.color.green, R.color.red, R.color.yellow);
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
    @Override
    public void onRefresh() {
       //Your refresh code here
    }
});

And don't forget to use:

swipeRefreshLayout.setRefreshing(false);

after using your code logic ;)

Valuation answered 18/11, 2015 at 9:45 Comment(6)
With the new Support Library 23.2.0 is the problem again: code.google.com/p/android/issues/detail?id=201775Valuation
23.2 Workaround: #35642130Valuation
What if the SwipeRefreshLayout is not directly inside the CoordinatorLayout? I've a ViewPager with fragments in it using the SwipeRefreshLayoutMultipara
Ok, with the Support Library 23.3.0 is again everything fine and working well ;)Valuation
For anybody struggling with this, I just wanted to point out that the app:layout_behavior="@string/appbar_scrolling_view_behavior" HAS be on the SwipeRefreshLayout. I had this on my scrollable view, not the SwipeRefreshLayout and nothing was working.Accomplished
Also for case nested RecyclerView, check here https://mcmap.net/q/282111/-how-to-disable-swiperefreshlayout-when-swiping-nested-recyclerview-39-s-cardview – user1221256Damiendamietta
W
115

Update: This issue has been resolved in the latest version of the support library (23.1.1+). If you are using an older version of the support library either upgrade or continue reading.

If you're using an older version of the support library, add an offset change listener to your AppBarLayout to enable or disable your swipe to refresh layout accordingly. Additional code available here:

https://gist.github.com/blackcj/001a90c7775765ad5212

Relevant changes:

public class MainActivity extends AppCompatActivity implements AppBarLayout.OnOffsetChangedListener {
    ...

    private AppBarLayout appBarLayout;
    private SwipeRefreshLayout mSwipeRefreshLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ...
        mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.contentView);
        appBarLayout = (AppBarLayout) findViewById(R.id.appBarLayout);

    }

    @Override
    public void onOffsetChanged(AppBarLayout appBarLayout, int i) {
        //The Refresh must be only active when the offset is zero :
        mSwipeRefreshLayout.setEnabled(i == 0);
    }

    @Override
    protected void onResume() {
        super.onResume();
        appBarLayout.addOnOffsetChangedListener(this);
    }

    @Override
    protected void onPause() {
        super.onPause();
        appBarLayout.removeOnOffsetChangedListener(this);
    }
}
Whoso answered 11/6, 2015 at 15:59 Comment(12)
What if the SwipeRefreshLayout was inside a Fragment which would be inside a ViewPager?Fork
@Fork I've updated my design support example on GitHub to include code for this. You need to override dispatchTouchEvent for the Activity in addition to listening for onOffsetChanged. Then include a public function in the Fragment to enable / disable the SwipeRefreshLayout. Full code available here: github.com/blackcj/DesignSupportExampleWhoso
Nice job blackcj I'll check it out and see if it all looks ok! Edit: Looks clear so far based on the example I saw in your Repo, haven't tested it on a device yet. Will keep you posted.Fork
This thing you have implemented better be in android itself.Ampoule
Note that this doesn't work properly with support library v23.0.0 and v23.0.1 as the nested scrolling functionality ignores the enabled state if the nested scrolling already started. If you scroll down and up in one motion, it will trigger the refresh anyways and the AppBarLayout stays collapsed.Seabury
This is a good idea but need some fix. If it starts full expanded (SwipeRefresh enabled) and I touch beggining scroll, it'll keep enabled until I release the touch. So if I scroll down a little and scroll up again, it'll try to refresh before full expanded.Perlis
Hey, I've created an RxObservable for observing AppBarLayout state (visibility), feel free to use: gist.github.com/scana/a5c0abc452abf202c9cfSpradling
I am stuck with the SwipeRefreshLayout was inside a Fragment of MainFragment. What can i do if i have fragments like ViewPager => Fragment =>Three Sub Fragment => SwipeRefreshLayout and have problem to scroll. Is there any solution?Mani
In my case I have the recyclerview inside a cardview, and when I swipe upward / downward it doesn't collapse / expand the toolbar, I use support library 23.2.1 and I added app:layout_behavior="@string/appbar_scrolling_view_behavior" to the recyclerview but in vain, I added it to the cardview but in vain, I added it to both but still in vain, why the movements is not sent to the CollapsingToolbarLayout? Note, I'm using CoordinatorLayout for my activity.Hillhouse
@Whoso knowing that at runtime I set my recyclerview to horizonal.Hillhouse
You don't want to disable it if it is in the refreshing process. So I did this: if(!this.isRefreshing()){ this.setEnabled(i == 0); }Bourassa
I was having an issue with this where a half swipe disabled the swipe permanently. To fix I just changed i == 0 to i <= 0. i.e. mSwipeRefreshLayout.setEnabled(i <= 0);.Urfa
V
28

Finally,

I found that SwipeRefreshLayout works without any "hacks" from Support Library version 23.1.1.

Simply use in your layout:

 <android.support.v4.widget.SwipeRefreshLayout
    android:id="@+id/swipe_refresh_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scrollbars="vertical" />
</android.support.v4.widget.SwipeRefreshLayout>

and in code:

SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout);
swipeRefreshLayout.setColorSchemeResources(R.color.green, R.color.red, R.color.yellow);
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
    @Override
    public void onRefresh() {
       //Your refresh code here
    }
});

And don't forget to use:

swipeRefreshLayout.setRefreshing(false);

after using your code logic ;)

Valuation answered 18/11, 2015 at 9:45 Comment(6)
With the new Support Library 23.2.0 is the problem again: code.google.com/p/android/issues/detail?id=201775Valuation
23.2 Workaround: #35642130Valuation
What if the SwipeRefreshLayout is not directly inside the CoordinatorLayout? I've a ViewPager with fragments in it using the SwipeRefreshLayoutMultipara
Ok, with the Support Library 23.3.0 is again everything fine and working well ;)Valuation
For anybody struggling with this, I just wanted to point out that the app:layout_behavior="@string/appbar_scrolling_view_behavior" HAS be on the SwipeRefreshLayout. I had this on my scrollable view, not the SwipeRefreshLayout and nothing was working.Accomplished
Also for case nested RecyclerView, check here https://mcmap.net/q/282111/-how-to-disable-swiperefreshlayout-when-swiping-nested-recyclerview-39-s-cardview – user1221256Damiendamietta
A
15

If I understand you correctly, you want to start refreshing only after toolbar is expanded, right? So first CollapsingToolbarLayout needs to be opened and then start refreshing. I managed it by the following code:

     <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/coordinator_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true">

        <android.support.design.widget.AppBarLayout
            android:id="@+id/app_bar_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:fitsSystemWindows="true"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

            <android.support.design.widget.CollapsingToolbarLayout
                android:id="@+id/collapsing_toolbar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:fitsSystemWindows="true"
                app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed">

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical">
                      <!--PUT HERE WHAT EVER YOU WANT TO COLLAPSE, A TOOLBAR, ETC...-->
                </LinearLayout>
            </android.support.design.widget.CollapsingToolbarLayout>
        </android.support.design.widget.AppBarLayout>
     <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swipe_refresh_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

            <android.support.v7.widget.RecyclerView
                android:id="@+id/recycler_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:clipToPadding="false"
                android:fadeScrollbars="false"
                android:scrollbars="vertical"
                app:layout_behavior="@string/appbar_scrolling_view_behavior" />
    </android.support.v4.widget.SwipeRefreshLayout>
</android.support.design.widget.CoordinatorLayout>   

And then, in your fragment/activity make it implement AppBarLayout.OnOffsetChangedListener (Now the refreshing is enabled when toolbar is fully expanded):

    @Override
    public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
        if (collapsingToolbarLayout.getHeight() + verticalOffset < 2 * ViewCompat.getMinimumHeight(collapsingToolbarLayout)) {
            swipeRefreshLayout.setEnabled(false);
        } else {
            swipeRefreshLayout.setEnabled(true);
        }
    }

Override onPause() & onResume() as in @blackcj answer:

  @Override
    public void onResume() {
        super.onResume();
        appBarLayout.addOnOffsetChangedListener(this);
    }

    @Override
    public void onPause() {
        super.onPause();
        appBarLayout.removeOnOffsetChangedListener(this);
    }

Then set LinearLayoutManager to your recyclerView:

    LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
    layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
    recyclerView.setLayoutManager(layoutManager);

For me this worked as a charm, first appBarlayout gets expanded and only then swipeRefreshLayout triggers refreshing.

Antisocial answered 13/11, 2015 at 14:13 Comment(5)
need to do its with a fragment added to a nestedscrollview. I am having a hard time.Welt
@user1232726 Hm.. and what's the problem? If you put FrameLayout inside a NestedScrollView and then from your code begin transaction to put your fragment into the framelayout it should work, no?Antisocial
Hi I try to put framelayout inside nestedScrollview and try replace it with fragment contain swiperefreshlayout and recycleView inside but does not work, recycleView does not show. (remove swipeRefreshlayout, recycleview show normally)Jesse
@LêKhánhVinh, try to put android:fillViewport="true" to the nestedScrollViewAntisocial
recyclerView is a nestedScrollView... you do not need both.Royo
D
1

I had to make the RecyclerView the main child of the SwipeRefreshLayout in order to remove the issue using Support Library 23.2.0. Could not fixed it having an include layout inside of the SwipeRefreshLayout

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

        <!--<include layout="@layout/my_RecyclerView_layout"/> issue for me here -->

        <android.support.v7.widget.RecyclerView
            android:id="@+id/my_recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        </android.support.v7.widget.RecyclerView>

    </android.support.v4.widget.SwipeRefreshLayout>
Daye answered 17/3, 2016 at 15:16 Comment(0)
L
0

It worked with offsetChangedListener for me, just a little better-performing kotlin onOffsetChanged() implementation

val toEnable = verticalOffset == 0
if (!swipeRefresh.isEnabled.xor(toEnable)) return
swipeRefresh.isEnabled = toEnable
Levi answered 4/3, 2021 at 19:22 Comment(0)
M
-2

The Above Answer is Perfect for AppCompatActivity but If you are used Fragment then following snippet will help you.

Just put NestedScrollView in xml of fragment

<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="fill_vertical"
    android:clipToPadding="false"
    android:isScrollContainer="false"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">

    <!-- A RecyclerView with some commonly used attributes -->
        <android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/album_timeline_swipe_refresh_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <android.support.v7.widget.RecyclerView
                android:id="@+id/album_timeline_recyclerview"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:clickable="true" />
        </android.support.v4.widget.SwipeRefreshLayout>

</android.support.v4.widget.NestedScrollView>
Mani answered 9/12, 2015 at 13:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.