注:本文demo已经提交github,地址完整代码如下,demo工程已经上传至GitHub,
github地址https://github.com/wsclwps123/UpLoadSwipeRefreshLayout
感谢大家支持!
在Android开发中,我们经常会用到列表下拉刷新和上拉加载的功能。
Google在support.v4包中提供了一个组件可以用来进行下来刷新,这个组件是SwipeRefreshLayout。
下面我们来看一下这个组件的使用:
在布局文件中加上xml代码
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"></ListView>
</android.support.v4.widget.SwipeRefreshLayout>
在MainActivity.java代码中添加代码:
/**
* 添加变量
* /
private SwipeRefreshLayout swipeRefreshLayout;
private ListView listView;
private ArrayList<String> list = new ArrayList<>();
private ArrayAdapter<String> adapter;
在Activity的onCreate方法中初始化实例
/** * 实例化变量,此处省略initList()方法 * 此方法内容为添加了N个字符串"item" */
initList();
swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout);
listView = (ListView) findViewById(R.id.list_view);
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, list);
listView.setAdapter(adapter);
接下来给SwipeRefreshLayout添加OnRefreshListener监听
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
Toast.makeText(MainActivity.this, "is refreshing!", Toast.LENGTH_SHORT).show();
}
});
运行效果如图所示
但是在日常开发中,我们不仅需要下拉刷新的功能,还应该有上拉继续加载的功能,但是Google并没有给我们提供上拉加载的方法,那怎么办呢?我们可以自定义一个View,自己添加上拉加载方法。
我们模仿Swiperefreshlayout的使用下拉刷新的方法,去设计上拉加载的方法。添加了上拉加载的代码形式大约为
view.setOnLoadListener(new OnLoadListener() {
@Override
public void onLoad() {
//在此执行上拉加载的逻辑
view.setloading(false); //逻辑执行完毕后停止上拉加载
}
});
所以,我们在重写下拉刷新组件前先写一个接口OnLoadListener
public interface OnLoadListener {
public void onLoad(); //需要用户重写的方法
}
我们写一个UpLoadSwipeRefreshLayout类,继承SwipeRefershLayout类。我们应该在什么时候开始上拉加载呢?当然是在子ListView滑动到最底部的时候,所以,我们可以开始进行上拉加载的时候应该满足一下条件:
① 判断listview已经滑动到了最底部
② 判断手指是在向上滑动
③ 当前上拉加载不正在进行
编写类
代码如下:
public class UpLoadSwipeRefreshLayout extends SwipeRefreshLayout {
private ListView mListView; //子view
private boolean isCanLoading = false; //是否正可以加载,默认不可用
private OnLoadListener mOnLoadListener;
private View mFooterView; //上拉加载的视图
private int firstY;
private int lastY;
private int mTouchSlop; //最短的滑动距离
private int footerResource; //加载视图的资源ID
public UpLoadSwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); //获取系统默认的最短滑动距离
}
}
因为ListView是此组件的子view,那么我们应该在开始的时候去检测,子view是否是listview。
重写onLayout()方法,获取子类
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (mListView == null) {
getListView(); //获取子view
}
}
构造一个getListView方法
private void getListView() {
int childs = getChildCount();
if (childs > 0) {
View childView = getChildAt(0); //获取第一个子view
if (childView instanceof ListView) {
/** * 如果子view的类型是ListView */
mListView = (ListView) childView;
//给子listview设置滑动监听
mListView.setOnScrollListener(this);
}
}
}
重写事件拦截事件,防止滑动事件发生冲突
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
/** * 获得触摸事件刚刚开始的时候,手指触摸的坐标 */
firstY = (int) ev.getRawY();
break;
case MotionEvent.ACTION_MOVE:
/** * 获得触摸事件结束的时候,手指离开时候的坐标 */
lastY = (int) ev.getRawY();
break;
case MotionEvent.ACTION_UP:
if (isCanLoad()) {
loadData();
}
break;
default:
break;
}
return super.onInterceptTouchEvent(ev);
}
为了获取子listview的滑动情况,使用我们应该让组件实现OnScrollListener接口。
即
class UpLoadSwipeRefreshLayout extends SwipeRefreshLayout implements AbsListView.OnScrollListener {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {}
/** * 重写滑动时间 * / @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (isCanLoad()) { /** * 滑动过程中如果可以加载数据 * 就执行加载数据的函数 */
loadData();
}
}
}
编写isCanLoad()方法,这里就用到了上方我们需要满足的三个条件
private boolean isCanLoad() {
return isBottom() && isPushUp() && !isCanLoading;
}
依次编写2个方法
/** * 判断listview是否到了底部 */
private boolean isBottom() {
if (mListView != null && mListView.getAdapter() != null) {
/** * 当前可加的最后一项就是listview的末尾项 * 说明滑动到了最底部 */
return mListView.getLastVisiblePosition() == (mListView.getAdapter().getCount() - 1);
}
return false;
}
/** * 判断是否正在上拉 */
private boolean isPushUp() {
/** * 往上滑动的距离大于系统默认的滑动距离 */
return firstY - lastY >= mTouchSlop;
}
这时候我们还需要编写loadData()方法
/** * 加载数据 */
private void loadData() {
if (mOnLoadListener != null) {
setLoading(true);
/** * 处理加载的逻辑 * 具体逻辑在回调中完成 */
mOnLoadListener.onLoad();
}
}
编写设置可加载的方法setLoading()
public void setLoading(boolean isCanLoad) {
this.isCanLoading = isCanLoad;
if (isCanLoad == true) {
/** * 加载,添加底部的正在加载视图 */
mListView.addFooterView(mFooterView);
} else {
/** * 不在加载中,将底部的加载中视图移除 */
mListView.removeFooterView(mFooterView);
firstY = 0;
lastY = 0;
}
}
对了,还没添加设置上拉加载监听的方法!
编写setOnUpLoadListener()方法
public void setOnLoadListener(OnLoadListener onLoadListener) {
this.mOnLoadListener = onLoadListener;
}
这个类我对外提供了一个变量,代表listview的底视图的资源id
可以通过setFooterResource方法进行添加并实例化此底部view
编写此方法
public void setFooterResource(int footerResource) {
this.footerResource = footerResource;
/** * 实例化布局view */
this.mFooterView = LayoutInflater.from(getContext()).inflate(footerResource, null, false);
}
这时候这个类我们就编写好了,完整的代码我会在博客的结尾放出。
我们先来看看这段代码的使用
将刚才的布局文件修改成如下:
<com.example.chenglei.myapplication.UpLoadSwipeRefreshLayout
android:id="@+id/swipe_refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"</ListView>
</com.example.chenglei.myapplication.UpLoadSwipeRefreshLayout>
添加尾视图的资源id文件,layout_up_load_view.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="80dp" android:background="@android:color/white">
<LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:orientation="horizontal">
<ProgressBar android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_margin="5dp" android:indeterminateDuration="1500" />
<TextView android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="center" android:layout_margin="5dp" android:gravity="center" android:text="正在加载中..." />
</LinearLayout>
</RelativeLayout>
修改MainActivity.java的代码
swipeRefreshLayout.setFooterResource(R.layout.layout_up_load_view); //添加正在加载中的视图
swipeRefreshLayout.setOnLoadListener(new OnLoadListener() {
@Override
public void onLoad() {
//上拉加载的操作
}
});
完整代码如下,demo工程已经上传至GitHub
github地址https://github.com/wsclwps123/UpLoadSwipeRefreshLayout
package com.example.chenglei.myapplication;
import android.content.Context;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.AbsListView;
import android.widget.ListView;
/** * Created by chenglei on 2016/4/20. * 重写下拉刷新组件,添加上拉加载的功能 */
public class UpLoadSwipeRefreshLayout extends SwipeRefreshLayout implements AbsListView.OnScrollListener {
private ListView mListView; //子view
private boolean isCanLoading = false; //是否正可以加载,默认不可用
private OnLoadListener mOnLoadListener;
private View mFooterView; //上拉加载的视图
private int firstY;
private int lastY;
private int mTouchSlop; //最短的滑动距离
private int footerResource;
public UpLoadSwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); //获取系统默认的最短滑动距离
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (mListView == null) {
getListView();
}
}
/** * 获取子view */
private void getListView() {
int childs = getChildCount();
if (childs > 0) {
View childView = getChildAt(0); //获取第一个子view
if (childView instanceof ListView) {
/** * 如果子view的类型是ListView */
mListView = (ListView) childView;
//给子listview设置滑动监听
mListView.setOnScrollListener(this);
}
}
}
/** * 触摸事件拦截 */
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
/** * 获得触摸事件刚刚开始的时候,手指触摸的坐标 */
firstY = (int) ev.getRawY();
break;
case MotionEvent.ACTION_MOVE:
/** * 获得触摸事件结束的时候,手指离开时候的坐标 */
lastY = (int) ev.getRawY();
break;
case MotionEvent.ACTION_UP:
if (isCanLoad()) {
loadData();
}
break;
default:
break;
}
return super.onInterceptTouchEvent(ev);
}
/** * 判断是否可以进行加载 */
private boolean isCanLoad() {
return isBottom() && isPushUp() && !isCanLoading;
}
/** * 判断listview是否到了底部 */
private boolean isBottom() {
if (mListView != null && mListView.getAdapter() != null) {
/** * 当前可加的最后一项就是listview的末尾项 * 说明滑动到了最底部 */
return mListView.getLastVisiblePosition() == (mListView.getAdapter().getCount() - 1);
}
return false;
}
/** * 判断是否正在上拉 */
private boolean isPushUp() {
/** * 往上滑动的距离大于系统默认的滑动距离 */
return firstY - lastY >= mTouchSlop;
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
if (isCanLoad()) {
/** * 滑动过程中如果可以加载数据 * 就执行加载数据的函数 */
loadData();
}
}
/** * 加载数据 */
private void loadData() {
if (mOnLoadListener != null) {
setLoading(true);
/** * 处理加载的逻辑 * 具体逻辑在回调中完成 */
mOnLoadListener.onLoad();
}
}
/** * 设置可加载 */
public void setLoading(boolean isCanLoad) {
this.isCanLoading = isCanLoad;
if (isCanLoad == true) {
/** * 加载,添加底部的正在加载视图 */
mListView.addFooterView(mFooterView);
} else {
/** * 不在加载中,将底部的加载中视图移除 */
mListView.removeFooterView(mFooterView);
firstY = 0;
lastY = 0;
}
}
/** * 设置上拉加载监听 */
public void setOnLoadListener(OnLoadListener onLoadListener) {
this.mOnLoadListener = onLoadListener;
}
/** * 设置上拉加载的布局 */
public void setFooterResource(int footerResource) {
this.footerResource = footerResource;
/** * 实例化布局view */
this.mFooterView = LayoutInflater.from(getContext()).inflate(footerResource, null, false);
}
}