Google官方下拉刷新控件
在App中由于屏幕的限制,需要使用类似ListView等控件,容纳并显示更多的数据给用户。但是数据肯定不是一层不变的,需要时时更新,这就有了下拉刷新的操作。
SwipeRefreshLayout
SwipeRefreshLayout是Google自家生产的下拉刷新的控件,效果大致是下拉到了一定的高度会进行刷新,若高度不够会回收上去。正在刷新过程中,继续下拉无反应。
官方文档中有一个重要提示,SwipeRefreshLayout的子视图必须是ListView,RecyleView,ScrollView,GridView等可滑动的控件。而且只能有一个控件。
基础用法
常用Api介绍
- setColorSchemaResources(int...colorResIds)
- 设置下拉进度条的颜色主题,参数为可变参数,必须是资源id。可以设置多种不同的颜色,每转一圈就显示一种颜色。
- setRefreshing(boolean)
- 设置刷新状态,true启动刷新动画,false关闭刷新动画
- setProgressBackgroundColorSchemaResources(int colorRes)
- 设置下拉圆圈背景颜色
- setOnRefreshListener(OnResfreshListener)
- 设置监听器,监听下拉操作,并实现回调方法做一些刷新操作
- setEnabled(boolean)
- 参数为false时禁用下拉刷新
- setSize(int)
- 设置下拉圆圈大小,有SwipeRefreshLayout.LARGE和SwipeRefreshLayout.DEFAULT两种
实践
布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/id_swipe_refresh_first"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/id_list_view_first"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.v4.widget.SwipeRefreshLayout>
</LinearLayout>
Java代码
private void initView() {
mListView = (ListView) findViewById(R.id.id_list_view_first);
mData = new ArrayList<>();
for (int i = 1; i < 20; i++) {
mData.add("This is item " + i);
}
mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mData);
mListView.setAdapter(mAdapter);
mRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.id_swipe_refresh_first);
mRefreshLayout.setColorSchemeResources(
android.R.color.holo_blue_light,
android.R.color.holo_green_light,
android.R.color.holo_orange_light,
android.R.color.holo_red_light
);
mRefreshLayout.setProgressBackgroundColorSchemeColor(Color.RED);
mRefreshLayout.setSize(SwipeRefreshLayout.DEFAULT);
mRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
new Thread(new Runnable() {
@Override
public void run() {
Log.d(TAG, Thread.currentThread().getName());
double k = Math.random();
int index = (int) (k * 100);
mData.add(0, "This is item " + index);
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
Log.d(TAG, Thread.currentThread().getName());
mAdapter.notifyDataSetChanged();
mRefreshLayout.setRefreshing(false);
}
}, 3000);
}
}).start();
}
});
}
代码分析,给SwipeRefreshLayout设置了监听器后,可以去实现onRefresh(),在里面做一些数据更新操作。一些耗时的操作,可以开启一个新线程来执行。最后记得使用Handler来切回主线程,更新UI。当数据更新完毕后一定要调用setRefreshing(false)来关闭SwipeRefreshLayout的刷新动画,否则刷新动画一直持续。
由于本次实践没有任何耗时的操作,所以采用postDelayed()方法延时,可以方便的观察到圆圈内部颜色的变换。在实践的过程中,即便开启新线程更新mData,并在该线程调用notifyDataSetChanged(),可以实现ListView的item更新。调用notifyDataSetChanged()实质上是调用适配器的geiView()重新绘制所有item视图。这里不太理解为何可以在子线程更新,觉得应该是ListView封装好了线程切回到UI线程操作。
效果
问题
当在SwipeRefreshLayout标签下,嵌套一个ViewGroup(FrameLayout或者LinearLayout或者其他View),那么下拉刷新的效果还会有吗?
mTarget
上面官方文档已经提示,SwipeRefreshLayout只能有一个childView,必须是ListView等可滑动控件。SwipeRefreshLayout在初始化时,会将这个childView赋予mTarget变量。来看源码:
private void ensureTarget() {
// Don't bother getting the parent height if the parent hasn't been laid
// out yet.
if (mTarget == null) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (!child.equals(mCircleView)) {
mTarget = child;
break;
}
}
}
}
发现目标View就是SwipeRefreshLayout的直接第一个子View。
ChildView不是可滑动控件
当第一个直接子View不是可滑动控件,SwipeRefreshLayout的效果很差。大致可以理解为当直接子View不是ListView等可滑动控件,不会影响可滑动控件的上拉(也就是查看下面被遮挡的item)。原因是上滑事件会最终传递给可滑动控件,并且消费该事件。相反如果想下拉刷新,就会出现问题。由于此事SwipeRefreshLayout监听的是第一个子View(非可滑动控件),下拉事件无法被第一个子View消费导致SwipeRefreshLayout直接拦截事件,自己处理(此时的效果就像ListVIew还没有下拉到最顶部,就出发了SwipeRefreshLayout下拉刷新的动画)。效果如下:
同时还发现,设置的进度变换颜色(setColorSchemaRespurces)无效果。
目标
- 如何自定义ProgressView,参考SwipeRefreshLayout,SuperSwipeRefreshLayout
- 如何实现上拉加载更多,参考SwipeRefreshLayout详解和自定义上拉加载更多,SwipeRefreshLayout + headerView,打造下拉刷新,上拉加载(滑到底部自动加载)