如何实现Android下拉刷新

482
在 Android 应用程序中(如 Twitter 官方应用程序),当您遇到 ListView 时,您可以向下拉它(并在释放时反弹)以刷新内容。在您看来,实现这一功能的最佳方法是什么?以下是我想到的一些可能性:
  1. 在 ListView 顶部的一个元素——但我认为使用 ListView 上的动画将滚动回项目位置1(基于0)并不容易。
  2. 在 ListView 外部的另一个视图——但我需要注意当拉动时移动 ListView 的位置,并且我不确定我们是否能够检测到拖动触摸仍然真正滚动 ListView 上的项目。
您有什么建议吗?
附言:我想知道官方 Twitter 应用程序源代码何时发布。它已经被提到过,但自那时以来已经过去了6个月,我们没有再听到相关消息。

这个功能能否在动态创建的TableLayout中实现?请帮忙…… - Arun Badole
https://github.com/fruitranger/PulltorefreshListView 是我另一个实现。具有流畅的多点触控支持。 - Changwei Yao
6
相关文章:“Pull-to-refresh”:Android上的反UI模式,这篇文章认为这个UI不适合在Android上使用,并引发了很多关于它是否合适的讨论。请注意,本文旨在翻译内容,不包括任何解释。 - blahdiblah
33
有人应该通知谷歌,因为Android版的最新Gmail使用了这种“反模式”。 - Nick
Gmail中使用的下拉刷新与Twitter等应用程序中使用的不同。我认为Google将其添加到那里是为了让习惯于下拉刷新模式并期望看到它的人们能够使用。同时,Google在操作栏中添加了一个“刷新”按钮,以符合Android指南。Google在其他应用程序中也采用了相同的方式:Google+、Google Play Newsstand,可能还有其他应用程序,因此保持了一致性。请参阅http://www.androiduipatterns.com/2013/06/googles-first-pull-to-refresh-good.html。 - Stan
4
下拉刷新是(并且已经成为)iOS和Android中采用的标准模式,这是非常自然的,因此反模式讨论已经过时了。用户会期望看到它,并按照这种方式行事。 - Martin Marconcini
18个回答

383

最后,谷歌发布了一个官方版本的下拉刷新库!

它叫做SwipeRefreshLayout,在支持库中,文档在这里:

  1. SwipeRefreshLayout添加为要处理为下拉刷新布局的视图的父视图。 (我以ListView为例,可以是任何像LinearLayoutScrollView等的View

 <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/pullToRefresh"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
     <ListView
         android:id="@+id/listView"
         android:layout_width="match_parent"
         android:layout_height="match_parent"/>
 </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
  • 向你的类添加一个监听器

  •  protected void onCreate(Bundle savedInstanceState) {
         final SwipeRefreshLayout pullToRefresh = findViewById(R.id.pullToRefresh);
         pullToRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
             @Override
             public void onRefresh() {
                 refreshData(); // your code
                 pullToRefresh.setRefreshing(false);
             }
         });
     }
    

    根据您的要求,您还可以调用pullToRefresh.setRefreshing(true/false);

    更新

    Android支持库已被弃用,并已被AndroidX取代。新库的链接可以在这里找到。

    此外,您需要将以下依赖项添加到项目中:

    implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
    

    您可以前往重构>>迁移到AndroidX,Android Studio将为您处理依赖关系。


    3
    我能为您翻译。这句话的意思是:我可以在SwipeRefreshLayout上使用ProgressBar代替Color Scheme吗? - pablobaldez
    62
    2014年4月1日 - 那不是一个笑话。 - aeracode
    3
    示例代码 https://guides.codepath.com/android/Implementing-Pull-to-Refresh-Guide - david72

    81

    我尝试实现了一个下拉刷新组件,但是它远未完成,但展示了一种可能的实现方式,https://github.com/johannilsson/android-pulltorefresh

    主要逻辑通过扩展ListView实现在PullToRefreshListView中。 内部使用smoothScrollBy(API Level 8)控制页眉视图的滚动。该小部件已更新为支持1.5及更高版本,请阅读README以获取1.5支持。

    在您的布局中,您只需像这样添加即可。

    <com.markupartist.android.widget.PullToRefreshListView
        android:id="@+id/android:list"
        android:layout_height="fill_parent"
        android:layout_width="fill_parent"
        />
    

    3
    你好,Johan:我已经下载了你的示例代码,在Android 2.2设备上可以运行,但在2.1设备上无法运行。我认为这是因为它使用了smoothScrollBy方法,该方法仅适用于2.2或更高版本,这正确吗?您是否有任何想法将此效果实现到2.1或更早的版本中?谢谢 - user577394
    id为@+id/android:list而不是@android:id/list,这是故意的吗?看起来很奇怪。我的项目在这里抛出了一个膨胀错误,我正在检查这个问题... - Mathias Conradt
    12
    这是我发现的最好的下拉刷新库:https://github.com/chrisbanes/Android-PullToRefresh。它适用于ListView、GridView和WebView,并且还实现了上拉刷新模式。 - rOrlig
    1
    我在使用Intellij Idea时,如何将项目添加到库文件夹中?有人能帮帮我吗? - Mustafa Güven
    你好@JohanNilsson,当列表中的所有数据同时可见时,下拉刷新视图不会完全隐藏。你能提供一个解决方法吗? - seema
    显示剩余7条评论

    55

    我还实现了一个稳定的、开源的、易于使用和高度可定制的PullToRefresh库,适用于Android。你可以根据项目页面上的文档将你的ListView替换为PullToRefreshListView。

    https://github.com/erikwt/PullToRefresh-ListView


    1
    这能替换标准的ListView,与ListActivity、ListFragment等一起使用吗? - davidcesarino
    1
    这太棒了!是谷歌把我带到这里的,看了一些例子后,你的代码看起来最容易实现。太棒了,非常感谢! - Evan R.
    看起来很不错,但是能否用它替换普通的ListView使用ExpandableListView呢? - AlvaroSantisteban
    @Erik,我在使用你的库时遇到了问题,你能在http://stackoverflow.com/questions/19820329/why-does-pull-to-refresh-fail-once-you-have-navigated-to-the-bottom-of-the-listv帮我吗? - Akshat Agarwal
    @Erik,我发现你的库非常有用且简单易懂。但是我遇到了一个问题,当屏幕正在刷新时,我无法获取项目点击事件。你有什么建议吗? - Abx
    显示剩余8条评论

    44

    我认为最简单的方法是使用Android支持库提供的:

    android.support.v4.widget.SwipeRefreshLayout;

    导入该库后,您可以按照以下方式定义布局:

      <android.support.v4.widget.SwipeRefreshLayout
            android:id="@+id/refresh"
            android:layout_height="match_parent"
            android:layout_width="match_parent">
        <android.support.v7.widget.RecyclerView
            xmlns:recycler_view="http://schemas.android.com/apk/res-auto"
            android:id="@android:id/list"
            android:theme="@style/Theme.AppCompat.Light"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/button_material_light"
            >
    
        </android.support.v7.widget.RecyclerView>
    </android.support.v4.widget.SwipeRefreshLayout>
    

    我假设你正在使用RecyclerView而不是ListView。然而,ListView仍然可以使用,所以你只需要将RecyclerView替换为ListView,并在Java代码(Fragment)中更新引用。

    在你的活动片段中,你首先要实现接口SwipeRefreshLayout.OnRefreshListener

    public class MySwipeFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener{
    private SwipeRefreshLayout swipeRefreshLayout;
    
    @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
            View view = inflater.inflate(R.layout.fragment_item, container, false);
            swipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.refresh);
            swipeRefreshLayout.setOnRefreshListener(this);
    }
    
    
     @Override
      public void onRefresh(){
         swipeRefreshLayout.setRefreshing(true);
         refreshList();
      }
      refreshList(){
        //do processing to get new data and set your listview's adapter, maybe  reinitialise the loaders you may be using or so
       //when your data has finished loading, cset the refresh state of the view to false
       swipeRefreshLayout.setRefreshing(false);
    
       }
    }
    

    希望这能帮助到大众


    它工作得很好。不过,有一件事情需要注意关于 setRefreshing:在 http://developer.android.com/reference/android/support/v4/widget/SwipeRefreshLayout.html#setRefreshing%28boolean%29 中有说明,“当刷新由滑动手势触发时,请勿调用此函数”。 - gabriel14

    23
    在这个链接中,你可以找到著名的PullToRefresh视图的一个分支,它有新的有趣实现,如PullTorRefreshWebViewPullToRefreshGridView,或者在列表底部添加PullToRefresh的可能性。 https://github.com/chrisbanes/Android-PullToRefresh 最好的是它在Android 4.1上运行完美(正常的PullToRefresh不能工作)。

    3
    自述文件顶部的大评论表明:该项目不再得到维护。无论如何,这个库是我目前见过的最好的!干得好! - Milton
    3
    即使它已经不再维护,也并不意味着它不好用。我仍然会用它来完成所有下拉刷新的需求 :) - Muz

    19

    为了实现 Android 的下拉刷新,可以尝试使用以下代码:

    <android.support.v4.widget.SwipeRefreshLayout
            android:id="@+id/pullToRefresh"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
    
        <ListView
            android:id="@+id/lv"
            android:layout_width="match_parent"
            android:layout_height="match_parent" >
        </ListView>
    
    </android.support.v4.widget.SwipeRefreshLayout>
    

    活动类:

    ListView lv = (ListView) findViewById(R.id.lv);
    SwipeRefreshLayout pullToRefresh = (SwipeRefreshLayout) findViewById(R.id.pullToRefresh);
    
    
    lv.setAdapter(mAdapter);
    
    pullToRefresh.setOnRefreshListener(new OnRefreshListener() {
    
            @Override
            public void onRefresh() {
                // TODO Auto-generated method stub
    
                refreshContent();
    
            }
        });
    
    
    
    private void refreshContent(){ 
    
         new Handler().postDelayed(new Runnable() {
                @Override public void run() {
                    pullToRefresh.setRefreshing(false);
                }
            }, 5000);
    
     }
    

    18

    我有一种非常简单的方法来做这件事,但不确定它是否是绝对可靠的方法 以下是我的代码 PullDownListView.java

    package com.myproject.widgets;
    
    import android.content.Context;
    import android.util.AttributeSet;
    import android.view.MotionEvent;
    import android.widget.AbsListView;
    import android.widget.AbsListView.OnScrollListener;
    import android.widget.ListView;
    
    /**
     * @author Pushpan
     * @date Nov 27, 2012
     **/
    public class PullDownListView extends ListView implements OnScrollListener {
    
        private ListViewTouchEventListener mTouchListener;
        private boolean pulledDown;
    
        public PullDownListView(Context context) {
            super(context);
            init();
        }
    
        public PullDownListView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        public PullDownListView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            init();
        }
    
        private void init() {
            setOnScrollListener(this);
        }
    
        private float lastY;
    
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
                lastY = ev.getRawY();
            } else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
                float newY = ev.getRawY();
                setPulledDown((newY - lastY) > 0);
                postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        if (isPulledDown()) {
                            if (mTouchListener != null) {
                                mTouchListener.onListViewPulledDown();
                                setPulledDown(false);
                            }
                        }
                    }
                }, 400);
                lastY = newY;
            } else if (ev.getAction() == MotionEvent.ACTION_UP) {
                lastY = 0;
            }
            return super.dispatchTouchEvent(ev);
        }
    
        @Override
        public void onScroll(AbsListView view, int firstVisibleItem,
                int visibleItemCount, int totalItemCount) {
            setPulledDown(false);
        }
    
        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
        }
    
        public interface ListViewTouchEventListener {
            public void onListViewPulledDown();
        }
    
        public void setListViewTouchListener(
                ListViewTouchEventListener touchListener) {
            this.mTouchListener = touchListener;
        }
    
        public ListViewTouchEventListener getListViewTouchListener() {
            return mTouchListener;
        }
    
        public boolean isPulledDown() {
            return pulledDown;
        }
    
        public void setPulledDown(boolean pulledDown) {
            this.pulledDown = pulledDown;
        }
    }
    

    您只需要在希望使用此ListView的活动上实现ListViewTouchEventListener并设置侦听器。

    我已经在PullDownListViewActivity中实现了它。

    package com.myproject.activities;
    
    import android.app.Activity;
    import android.os.Bundle;
    
    /**
     * @author Pushpan
     *
     */
    public class PullDownListViewActivity extends Activity implements ListViewTouchEventListener {
    
        private PullDownListView listView;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            listView = new PullDownListView(this);
            setContentView(listView);
            listView.setListViewTouchListener(this);
    
            //setItems in listview
        }
    
        public void onListViewPulledDown(){
            Log.("PullDownListViewActivity", "ListView pulled down");
        }
    }
    

    对我来说有效:)


    10

    有一种新型的“下拉刷新”方式,类似于Google Now或Gmail应用程序中显示在操作栏顶部的方式,但目前还没有人提到。

    现在有一个名为ActionBar-PullToRefresh的库,可以实现完全相同的效果。


    7
    请注意,在Android和WP上实现时需要解决UX问题。
    “一个很好的指标,说明为什么设计师/开发人员不应该像iOS应用程序那样实现下拉刷新的方式,是因为Google及其团队从未在Android上使用下拉刷新,而他们在iOS上使用它。”

    https://plus.google.com/109453683460749241197/posts/eqYxXR8L4eb


    3
    我知道这已经过去一年了,但 Gmail 应用确实使用下拉刷新。然而,从 UI 的角度来看,它与其他应用略有不同,列表并不会向下滚动,相反,在屏幕顶部会显示一个新的视图。 - ashishduh

    4
    如果你不想让你的程序看起来像是被强行适配到安卓中的iPhone程序,那么就要追求更本地化的外观和感觉,并且做类似于Gingerbread的东西。

    2
    @Rob:我的意思是,在你拉过头时,在列表顶部有橙色的阴影,而不是像iPhone一样让列表反弹回来。这是作为评论(不是答案)的意见,但评论不能有图片。 - Lie Ryan
    啊,抱歉,好主意。我还没有尝试过Gingerbread,所以还没有看到效果。仍在等待谷歌将其推向Nexus One。 - Rob
    9
    我有姜饼和橙色光芒,当一个列表是静态的时候非常好用。但下拉刷新是一种很棒的界面机制,可以刷新动态列表。虽然它在iOS世界很流行,但在Android生态系统中也不会让人感到奇怪。我强烈建议你在官方的Twitter应用程序中查看它。 :) - Thierry Roy
    这里的本地行为是表明您已到达列表结尾。根据这一点,执行刷新与本地操作不同,因为您没有做平台所做的事情。这意味着您更有可能让用户感到惊讶。我肯定不会期望也不想要只是因为我到达了列表末尾就进行刷新。下拉刷新更直观和清晰。由于原生的 Twitter 应用程序使用它,我认为可以说这是一个大量人们熟悉的 UI 概念。 - steprobe

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