实现Material风格的滑动刷新Swipe to Refresh

Material Design用户界面指南中非常棒的一个设计是Swipe to Refresh UI pattern。实际上你可能已经看到或者用过这种效果了。在很多热门的app中都有这种效果,比facebok、 Google Newsstand, Trello, Gmail等等。

类似于如下效果:

cat names gif

Swipe to Refresh UI非常适合于基于adapter的控件(如RecyclerView and ListView),一般它们都需要支持用户的刷新请求。关于Swipe to Refresh的实现,在KitKat版本中就有了SwipeRefreshLayout,Lollipop中对SwipeRefreshLayout做了改进,v4包含了SwipeRefreshLayout控件,我们只需做一些设置就可以了。为了方便读者,我把demo放在了github 上 下载地址

我们是在一个包含最新版本Support库的Android Studio项目中实现Swipe to Refresh,我们要做的第一件事就是将support library添加进build.gradle

compile 'com.android.support:support-v4:21.0.+'

在新建工程向导的时候自动创建了res/layouts/activity_main.xml文件,我们将一个ListView添加进SwipeRefreshLayout控件

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

注意ListView被包含在了SwipeRefreshLayout的里面。每次我们滑动ListView到SwipeRefreshLayout边缘的时候,SwipeRefreshLayout都会显示一个正在加载的图标,同时触发一个onRefresh事件。onRefresh是我们为自己list刷新数据添加的一个回调方法。

设置adapter

布局我们已经搞定,现在为列表添加数据,我们用一个简单的adapter来显示数据,数据来自于res/strings.xml文件:

<string-array name="cat_names">
    <item>George</item>
    <item>Zubin</item>
    <item>Carlos</item>
    <item>Frank</item>
    <item>Charles</item>
    <item>Simon</item>
    <item>Fezra</item>
    <item>Henry</item>
    <item>Schuster</item>
</string-array>

设置adapter:

class MainActivity extends Activity {
  ListView mListView;
  SwipeRefreshLayout mSwipeRefreshLayout;
  Adapter mAdapter;
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.acivity_main);
    SwipeRefreshLayout mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.activity_main_swipe_refresh_layout);
    mListView = findViewById(R.id.activity_main_list_view);
 mListView.setAdapter(new ArrayAdapter<String>(){
    String\[\] fakeTweets = getResources().getStringArray(R.array.fake_tweets);
    mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, fakeTweets)
    listView.setAdapter(mAdapter);
  });
  }
}

处理数据刷新

adapter已经设置,现在我们添加下拉刷新事件。我们会免费获得一个动画效果的加载图标,我们只需要决定ListView该做什么,这取决于SwipeRefreshLayout的OnRefreshListener 接口是如何实现的。我们用getNewCatNames模拟从webservice 中得到新数据。

@Override
  public void onCreate(Bundle savedInstanceState) {
  ...
    listView.setAdapter();
    mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
      @Override
      public void onRefresh() {
            refreshContent();
          ...
  }
  // fake a network operation's delayed response
  // this is just for demonstration, not real code!
  private void refreshContent(){
  new Handler().postDelayed(new Runnable() {
          @Override
          public void run() {
    mAdapter = new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_list_item_1, getNewCatNames());
    mListView.setAdapter(mAdapter);
    mSwipeRefreshLayout.setRefreshing(false);
  });
  }
  // get new cat names.
  // Normally this would be a call to a webservice using async task,
  // or a database operation
 private List<String> getNewCatNames() {
        List<String> newCatNames = new ArrayList<String>();
        for (int i = 0; i < mCatNames.size(); i++) {
          int randomCatNameIndex = new Random().nextInt(mCatNames.size() - 1);
          newCatNames.add(mCatNames.get(randomCatNameIndex));
        }
        return newCatNames;
    }

注意上面的代码中refreshContent()方法最后一行代码setRefreshing(false);`setRefreshing`的作用是设置刷新加载效果的icon是否继续显示,这里使用handler做了个延时,模拟实际加载数据需要的时间,当handler的post开始执行的时候`setRefreshing(false)`表示加载结束,停止播放加载动画。

自定义

你可以自定义SwipeRefreshLayout的外观。setColorSchemeResources()可以改变加载图标的颜色。

先在资源文件中定义几个颜色值:

<resources>
    <color name="orange">#FF9900</color>
  <color name="green">#009900</color>
    <color name="blue">#000099</color>
</resources>

然后调用setColorSchemeResources(R.color.orange, R.color.green, R.color.blue);

cat names color

SwipeRefreshLayout旋转的时候将会在这三种颜色间切换。

就如你所看到的Swipe to Refresh简化了用户请求更新数据的操作,关于SwipeRefreshLayout的更多api请查看官方文档

注:本篇文章提供的demo编译的时候虽然用的是21版本的appcompat,但是运行还是需要在5.0的设备上,因为demo的Material主题和颜色appcompat无能为力。当然你可以如下更改一下主题,这样在4.x版本上也能运行:

demo本来的主题:

styles.xml

<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="android:ThemeOverlay.Material.Light">
        <!-- Customize your theme here. -->
        <item name="android:colorPrimary">@android:color/holo_blue_dark</item>
    </style>
</resources>

改成:

<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light">
        <!-- Customize your theme here. -->
    </style>
</resources>

本文由 泡在网上的日子 翻译,英文出处:Implementing Swipe to Refresh, an Android Material Design UI Pattern