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

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

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

1
2
3
4
5
6
7
8
9
10
11
<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文件:

1
2
3
4
5
6
7
8
9
10
11
<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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 中得到新数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@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()可以改变加载图标的颜色。

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

1
2
3
4
5
<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

1
2
3
4
5
6
7
<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>

改成:

1
2
3
4
5
6
7
<resources>
     <!-- Base application theme. -->
     <style name= "AppTheme" parent= "Theme.AppCompat.Light" >
         <!-- Customize your theme here. -->
                                                  
     </style>
</resources>




通过源码我们发现SwipeRefreshLayout中的两个重要的属性:

private MaterialProgressDrawable mProgress;

private CircleImageView mCircleView;

这两个属性正是用于实现进度动画效果的,在方法createProgressView中,我们看到mCircleView最终加入到了SwipeRefreshLayout中。

?
1
2
3
4
5
6
7
8
private void createProgressView() {
         mCircleView =  new CircleImageView(getContext(), CIRCLE_BG_LIGHT, CIRCLE_DIAMETER/ 2 );
         mProgress =  new MaterialProgressDrawable(getContext(),  this );
         mProgress.setBackgroundColor(CIRCLE_BG_LIGHT);
         mCircleView.setImageDrawable(mProgress);
         mCircleView.setVisibility(View.GONE);
         addView(mCircleView);
     }

同时我们也可以查看到CirlceImageView继承了ImageView,MaterialProgressDrawabel继承了Drawable,至此我们也就明白了下来进度动画是如何实现的了,具体的细节在不做过多赘述,可自行查看源码

class CircleImageView extends ImageView

class MaterialProgressDrawable extends Drawable implements Animatable

具体的下拉功能实现主要是在onInterceptTouchEvent、onTouchEvent方法中完成的,在此我就不在将具体代码贴出来了,大家可自行查看。


参考博客:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/1215/2166.html

源码:http://download.csdn.net/detail/iblue007/9098789
支持包:http://download.csdn.net/detail/iblue007/9098931

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值