Android: CollapsingToolbarLayout和SwipeRefreshLayout卡住了

68

我使用CollapsingToolbarLayout、RecyclerView和SwipeRefreshLayout一起:

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>

代码:

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

如何在折叠工具栏布局完全展开且滚动视图(recyclerview)在顶部时,允许滑动刷新操作?类似于Google+或Inbox应用程序的行为。

错误:

enter image description here

正确:

enter image description here

6个回答

116
更新:此问题已在最新版本的支持库(23.1.1+)中得到解决。如果您正在使用旧版本的支持库,请升级或继续阅读。
如果您正在使用旧版本的支持库,请向您的AppBarLayout添加一个偏移更改侦听器,以相应地启用或禁用您的下拉刷新布局。这里提供了其他代码:https://gist.github.com/blackcj/001a90c7775765ad5212 相关更改:
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);
    }
}

17
如果SwipeRefreshLayout位于ViewPager内部的一个Fragment中,会有什么影响? - AndyRoid
2
@AndyRoid 我已经在GitHub上更新了我的设计支持示例,包括此代码。除了监听onOffsetChanged之外,您还需要覆盖Activity的dispatchTouchEvent。然后,在Fragment中包含一个公共函数以启用/禁用SwipeRefreshLayout。完整代码在此处可用:https://github.com/blackcj/DesignSupportExample - blackcj
你实现的东西最好是在Android本身中。 - guness
2
请注意,此功能在支持库v23.0.0和v23.0.1上无法正常工作,因为嵌套滚动功能会忽略启用状态,如果嵌套滚动已经开始。如果您一次滚动向下然后向上,它仍然会触发刷新,并且AppBarLayout将保持折叠状态。 - oRRs
6
这个想法不错,但需要进行一些修正。如果它开始完全展开(启用了SwipeRefresh),并且我触摸开始滚动,它会一直保持启用状态,直到我松开触摸。因此,如果我向下滚动一点,然后再向上滚动,它会在完全展开之前尝试刷新。 - Leandroid
显示剩余7条评论

27

最后,我发现在Support Library版本23.1.1中,SwipeRefreshLayout可以无需任何“黑科技”就能正常工作。

在你的布局中简单使用:

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

在代码中:

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

不要忘记使用:

swipeRefreshLayout.setRefreshing(false);

使用您的代码逻辑后 ;)


2
随着新的Support Library 23.2.0的推出,问题再次出现:https://code.google.com/p/android/issues/detail?id=201775 - Tomas
1
23.2 解决方法:https://dev59.com/8JTfa4cB1Zd3GeqPRXsi#35643170 - Tomas
2
如果SwipeRefreshLayout不直接位于CoordinatorLayout内怎么办?我有一个包含SwipeRefreshLayout的ViewPager和其内部的片段。 - Daniele Segato
好的,使用支持库23.3.0一切都正常并且工作良好 ;) - Tomas
对于任何遇到困难的人,我想指出 app:layout_behavior="@string/appbar_scrolling_view_behavior" 必须放在 SwipeRefreshLayout 上。我将其放在可滚动视图上,而不是 SwipeRefreshLayout 上,结果什么都没起作用。 - Luke Sleeman
此外,对于嵌套的RecyclerView情况,请查看此处stackoverflow.com/a/36895607/1221256 - user1221256 - user1221256

15

如果我理解你的意思正确的话,你想在工具栏展开后才开始刷新,是吗?因此,首先需要打开CollapsingToolbarLayout,然后再开始刷新。我通过以下代码实现了这一点:

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

然后,在片段/活动中使其实现AppBarLayout.OnOffsetChangedListener接口(现在刷新仅在工具栏完全展开时启用):

然后,在片段/活动中使其实现AppBarLayout.OnOffsetChangedListener接口(现在仅在工具栏完全展开时启用刷新):

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

按照@blackcj的回答重写onPause()和onResume():

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

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

然后将LinearLayoutManager设置为你的recyclerView:

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

对我来说这很有效,首先appBarlayout会被展开,然后才会触发swipeRefreshLayout进行刷新。


需要在一个NestedScrollView中添加一个Fragment来完成它。我遇到了一些困难。 - filthy_wizard
@user1232726 嗯...问题出在哪里呢?如果你将FrameLayout放入NestedScrollView中,然后从代码中开始事务将你的片段放入FrameLayout中,这样应该就可以了,不是吗? - hkop
嗨,我尝试将FrameLayout放入NestedScrollView中,并尝试用包含SwipeRefreshLayout和RecycleView的Fragment替换它,但不起作用,RecycleView不显示。(删除SwipeRefreshLayout后,RecycleView正常显示) - Lê Khánh Vinh
@LêKhánhVinh,请尝试将android:fillViewport="true"放置到nestedScrollView中。 - hkop
recyclerView 是一个嵌套的滚动视图,你不需要同时使用两者。 - Codeversed

1
我不得不将RecyclerView作为SwipeRefreshLayout的主要子项,以解决使用Support Library 23.2.0时出现的问题。不能在SwipeRefreshLayout内部使用include布局来修复它。
<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>

0

对我来说,使用offsetChangedListener可以正常工作,只是在onOffsetChanged()实现上稍微更高效的kotlin

val toEnable = verticalOffset == 0
if (!swipeRefresh.isEnabled.xor(toEnable)) return
swipeRefresh.isEnabled = toEnable

-2

上面的答案对于AppCompatActivity非常完美,但如果您使用了Fragment,那么以下代码片段将会有所帮助。

只需在fragment的xml中放置NestedScrollView即可。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接