看过很多的下拉刷新框架,但感觉大多是基于ListView或RecyclerView。
个人觉得,下拉上拉做为一个通用操作,最好是做为一个专门的容器,和视图展示分离开来,这样就算内容展示视图要从ListView变成RecyclerViewl了,下拉上拉这一层,也无需做任何改动!
1.整体思路
- 自定义测量以及布局的方法
- 拦截掉子控件的一些手势
- 处理手势,刷新状态
2.自定义测量和布局
继承ViewGroup,重写onMeasure与onLayout方法,这里要注意当子控件GONE的情况
public abstract class DrawLayout extends ViewGroup {
public View header;
public View footer;
public PullHeader pullHeader;
public PullFooter pullFooter;
public int bottomScroll;// 当滚动到内容最底部时Y轴所需要的滑动值
public int lastChildIndex;// 最后一个childview的index
public DrawLayout(Context context) {
super(context);
}
public DrawLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setHeader(PullHeader pullHeader) {
this.pullHeader = pullHeader;
}
public void setFooter(PullFooter pullFooter) {
this.pullFooter = pullFooter;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
lastChildIndex = getChildCount() - 1;
}
/**
* 添加上拉刷新布局作为header
*/
public void addHeader(View header) {
this.header = header;
ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
addView(header, layoutParams);
}
/**
* 添加下拉加载布局作为footer
*/
public void addFooter(View footer) {
this.footer = footer;
ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
addView(footer, layoutParams);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 遍历进行子视图的测量工作
for (int i = 0; i < getChildCount(); i++) {
// 通知子视图进行测量
View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// 重置(避免重复累加)
int contentHeight = 0;
// 遍历进行子视图的置位工作
for (int index = 0; index < getChildCount(); index++) {
View child = getChildAt(index);
if (child.getVisibility() == GONE) {
continue;
}
// 头视图隐藏在ViewGroup的顶端
if (child == header) {
child.layout(0, 0 - child.getMeasuredHeight(), child.getMeasuredWidth(), 0);
}
// 尾视图隐藏在ViewGroup所有内容视图之后
else if (child == footer) {
child.layout(0, contentHeight, child.getMeasuredWidth(), contentHeight + child.getMeasuredHeight());
}
// 内容视图根据定义(插入)顺序,按由上到下的顺序在垂直方向进行排列
else {
child.layout(0, contentHeight, child.getMeasuredWidth(), contentHeight + child.getMeasuredHeight());
if (index <= lastChildIndex) {
if (child instanceof ScrollView) {
contentHeight += getMeasuredHeight();
continue;
}
contentHeight += child.getMeasuredHeight();
}
}
}
// 计算到达内容最底部时ViewGroup的滑动距离
bottomScroll = contentHeight - getMeasuredHeight();
}
}
这里,我定义了两个接口:
下拉接口:
public interface PullHeader {
//下拉刷新(下拉中,到达有效刷新距离前)
void onDownBefore(int scrollY);
//松开刷新(下拉中,到达有效刷新距离后)
void onDownAfter(int scrollY);
//准备刷新(从松手后的位置滚动到刷新的位置)
void onRefreshScrolling(int scrollY);
//正在刷新……
void onRefreshDoing(int scrollY);
//刷新完成后,回到默认状态中
void onRefreshCompleteScrolling(int scrollY, boolean isRefreshSuccess);
//刷新取消后,回到默认状态中(没有超过有效的下拉距离)
void onRefreshCancelScrolling(int scrollY);
}
上拉接口:
public interface PullFooter {
//上拉加载
void onUpBefore(int scrollY);
//松开加载
void onUpAfter(int scrollY);
//准备加载
void onLoadScrolling(int scrollY);
//正在加载……
void onLoadDoing(int scrollY);
//加载完成后,回到默认状态中
void onLoadCompleteScrolling(int scrollY, boolean isLoadSuccess);
//加载取消后,回到默认状态中
void onLoadCancelScrolling(int scrollY);
}
大家可以看到,我这里每一个回调中,都返回了scrollY,方便我们根据该值做一些自定义的动画效果。
3.自定义拦截手势
继承刚才的DrawLayout,重写onInterceptTouchEvent方法,这里主要是要解决当子控件也可以滑动时的一些冲突问题。
比如当子控件是ScrollView时,只有当ScrollView滑动到顶部或底部,不能再滑动时,才可以触发下拉或上拉事件。
public abstract class InterceptLauyout extends DrawLayout {
// 用于计算滑动距离的Y坐标中介
public int lastYMove;
// 用于判断是否拦截触摸事件的Y坐标中介
public int lastYIntercept;
public InterceptLauyout(Context context) {
super(context);
}
public InterceptLauyout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercept = false;
// 记录此次触摸事件的y坐标
int y = (int) event.getY();
// 判断触摸事件类型
switch (event.getAction()) {
// Down事件
case MotionEvent.ACTION_DOWN: {
// 记录下本次系列触摸事件的起始点Y坐标
lastYMove = y;
// 不拦截ACTION_DOWN,因为当ACTION_DOWN被拦截,后续所有触摸事件都会被拦截
intercept = false;
break;
}
// Move事件
case MotionEvent.ACTION_MOVE: {
if (y > lastYIntercept) { // 下滑操作
// 获取最顶部的子视图
View child = getFirstVisiableChild();
if (child == null) {
intercept = false;
} else if (child instanceof AdapterView) {
intercept = avPullDownIntercept(child);
} else if (child instanceof ScrollView) {
intercept = svPullDownIntercept(child);
} else if (child instanceof RecyclerView) {
intercept = rvPullDownIntercept(child);
}
} else if (y < lastYIntercept) { // 上拉操作
// 获取最底部的子视图
View child = getLastVisiableChild();
if (child == null) {
intercept = false;
} else if (child instanceof AdapterView) {
intercept = avPullUpIntercept(child);
} else if (child instanceof ScrollView) {
intercept = svPullUpIntercept(child);
} else if (child instanceof RecyclerView) {
intercept = rvPullUpIntercept(child);
}
} else {
intercept = false;
}
break;
}
// Up事件
case MotionEvent.ACTION_UP: {
intercept = false;
break;
}
}
lastYIntercept = y;
return intercept;
}
private View getLastVisiableChild() {
for (int i = lastChildIndex; i >= 0; i--) {
View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
} else {
return child;
}
}
return null;
}
private View getFirstVisiableChild() {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
} else {
return child;
}
}
return null;
}
public boolean avPullDownIntercept(View child) {
boolean intercept = true;
AdapterView adapterChild = (AdapterView) child;
// 判断AbsListView是否已经到达内容最顶部
if (adapterChild.getFirstVisiblePosition() != 0
|| adapterChild.getChildAt(0).getTop() != 0) {
// 如果没有达到最顶端,则仍然将事件下放
intercept = false;
}
return intercept;
}
public boolean avPullUpIntercept(View child) {
boolean intercept = false;
AdapterView adapterChild = (AdapterView) child;
// 判断AbsListView是否已经到达内容最底部
if (adapterChild.getLastVisiblePosition() == adapterChild.getCount() - 1
&& (adapterChild.getChildAt(adapterChild.getChildCount() - 1).getBottom() == getMeasuredHeight())) {
// 如果到达底部,则拦截事件
intercept = true;
}
return intercept;
}
public boolean svPullDownIntercept(View child) {
boolean intercept = false;
if (child.getScrollY() <= 0) {
intercept = true;
}
return intercept;
}
public boolean svPullUpIntercept(View child) {
boolean intercept = false;
ScrollView scrollView = (ScrollView) child;
View scrollChild = scrollView.getChildAt(0);
if (scrollView.getScrollY() >= (scrollChild.getHeight() - scrollView.getHeight())) {
intercept = true;
}
return intercept;
}
public boolean rvPullDownIntercept(View child) {
boolean intercept = false;
RecyclerView recyclerChild = (RecyclerView) child;
if (recyclerChild.computeVerticalScrollOffset() <= 0)
intercept = true;
return intercept;
}
public boolean rvPullUpIntercept(View child) {
boolean intercept = false;
RecyclerView recyclerChild = (RecyclerView) child;
if (recyclerChild.computeVerticalScrollExtent() + recyclerChild.computeVerticalScrollOffset()
>= recyclerChild.computeVerticalScrollRange())
intercept = true;
return intercept;
}
}
4.自定义处理手势,刷新状态
首先,我定义了下拉的所有状态,基本上这里的每一种状态都对应着上面的一种接口回调。
public enum PullStatus {
DEFAULT,//默认状态
DOWN_BEFORE,//下拉中,到达有效刷新距离前
DOWN_AFTER,//下拉中,到达有效刷新距离后
REFRESH_SCROLLING,//放手后,开始刷新前,回到刷新的位置中
REFRESH_DOING,//正在刷新中
REFRESH_COMPLETE_SCROLLING,//刷新完成后,回到默认状态中
REFRESH_CANCEL_SCROLLING,//刷新取消后,回到默认状态中
UP_BEFORE,//上拉中,到达有效刷新距离前
UP_AFTER,//上拉中,到达有效刷新距离后
LOADMORE_SCROLLING,//放手后,开始加载前,从手势位置回到加载的位置中
LOADMORE_DOING,//正在加载中
LOADMORE_COMPLETE_SCROLLING,//加载完成后,回到默认状态中
LOADMORE_CANCEL_SCROLLING,//加载取消后,回到默认状态中
}
接着,继承刚才的InterceptLauyout,重写onTouchEvent方法,刷新状态。这里我主要是通过属性动画+scrollTo/scrollBy来实现弹性滑动的。当然你也可以用scroller来实现。
需要注意scrollY和我们的视图坐标系方向相反!
public class PullLayout extends InterceptLauyout {
// 事件监听接口
private OnPullListener listener;
// Layout状态
private PullStatus status = PullStatus.DEFAULT;
//阻尼系数
private float damp = 0.5f;
//恢复动画的执行时间
public int SCROLL_TIME = 300;
//是否刷新完成
private boolean isRefreshSuccess = false;
//是否加载完成
private boolean isLoadSuccess = false;
public void setOnPullListener(OnPullListener listener) {
this.listener = listener;
}
public PullLayout(Context context) {
super(context);
}
public PullLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE: {
// 计算本次滑动的Y轴增量(距离)
int dy = y - lastYMove;
LogUtil.print("dy=" + dy + "\tgetScrollY=" + getScrollY());
// 如果getScrollY<0,即下拉操作
if (getScrollY() < 0) {
if (header != null) {
// 进行Y轴上的滑动
performScroll(dy);
if (Math.abs(getScrollY()) > header.getMeasuredHeight()) {
updateStatus(DOWN_AFTER);
} else {
updateStatus(DOWN_BEFORE);
}
}
}
// 如果getScrollY>=0,即上拉操作
else {
if (footer != null) {
// 进行Y轴上的滑动
performScroll(dy);
if (getScrollY() >= bottomScroll + footer.getMeasuredHeight()) {
updateStatus(UP_AFTER);
} else {
updateStatus(UP_BEFORE);
}
}
}
// 记录y坐标
lastYMove = y;
break;
}
case MotionEvent.ACTION_UP: {
// 判断本次触摸系列事件结束时,Layout的状态
switch (status) {
//下拉刷新
case DOWN_BEFORE:
scrolltoDefaultStatus(REFRESH_CANCEL_SCROLLING);
break;
case DOWN_AFTER:
scrolltoRefreshStatus();
break;
//上拉加载更多
case UP_BEFORE:
scrolltoDefaultStatus(LOADMORE_CANCEL_SCROLLING);
break;
case UP_AFTER:
scrolltoLoadStatus();
break;
default:
LogUtil.print("松手时是其他状态:" + status);
break;
}
}
}
lastYIntercept = 0;
postInvalidate();
return true;
}
//刷新状态
private void updateStatus(PullStatus status) {
this.status = status;
int scrollY = getScrollY();
LogUtil.print("status=" + status);
// 判断本次触摸系列事件结束时,Layout的状态
switch (status) {
//默认状态
case DEFAULT:
onDefault();
break;
//下拉刷新
case DOWN_BEFORE:
pullHeader.onDownBefore(scrollY);
break;
case DOWN_AFTER:
pullHeader.onDownAfter(scrollY);
break;
case REFRESH_SCROLLING:
pullHeader.onRefreshScrolling(scrollY);
break;
case REFRESH_DOING:
pullHeader.onRefreshDoing(scrollY);
listener.onRefresh();
break;
case REFRESH_COMPLETE_SCROLLING:
pullHeader.onRefreshCompleteScrolling(scrollY, isRefreshSuccess);
break;
case REFRESH_CANCEL_SCROLLING:
pullHeader.onRefreshCancelScrolling(scrollY);
break;
//上拉加载更多
case UP_BEFORE:
pullFooter.onUpBefore(scrollY);
break;
case UP_AFTER:
pullFooter.onUpAfter(scrollY);
break;
case LOADMORE_SCROLLING:
pullFooter.onLoadScrolling(scrollY);
break;
case LOADMORE_DOING:
pullFooter.onLoadDoing(scrollY);
listener.onLoadMore();
break;
case LOADMORE_COMPLETE_SCROLLING:
pullFooter.onLoadCompleteScrolling(scrollY, isLoadSuccess);
break;
case LOADMORE_CANCEL_SCROLLING:
pullFooter.onLoadCancelScrolling(scrollY);
break;
}
}
//默认状态
private void onDefault() {
isRefreshSuccess = false;
isLoadSuccess = false;
}
//滚动到加载状态
private void scrolltoLoadStatus() {
int start = getScrollY();
int end = footer.getMeasuredHeight() + bottomScroll;
performAnim(start, end, new AnimListener() {
@Override
public void onDoing() {
updateStatus(LOADMORE_SCROLLING);
}
@Override
public void onEnd() {
updateStatus(LOADMORE_DOING);
}
});
}
//滚动到刷新状态
private void scrolltoRefreshStatus() {
int start = getScrollY();
int end = -header.getMeasuredHeight();
performAnim(start, end, new AnimListener() {
@Override
public void onDoing() {
updateStatus(REFRESH_SCROLLING);
}
@Override
public void onEnd() {
updateStatus(REFRESH_DOING);
}
});
}
//滚动到默认状态
private void scrolltoDefaultStatus(final PullStatus startStatus) {
int start = getScrollY();
int end = 0;
performAnim(start, end, new AnimListener() {
@Override
public void onDoing() {
updateStatus(startStatus);
}
@Override
public void onEnd() {
updateStatus(DEFAULT);
}
});
}
//停止刷新
public void stopRefresh(boolean isSuccess) {
isRefreshSuccess = isSuccess;
scrolltoDefaultStatus(PullStatus.REFRESH_COMPLETE_SCROLLING);
}
//停止加载更多
public void stopLoadMore(boolean isSuccess) {
isLoadSuccess = isSuccess;
scrolltoDefaultStatus(PullStatus.LOADMORE_COMPLETE_SCROLLING);
}
//执行滑动
public void performScroll(int dy) {
scrollBy(0, (int) (-dy * damp));
}
//执行动画
private void performAnim(int start, int end, final AnimListener listener) {
ValueAnimator animator = ValueAnimator.ofInt(start, end);
animator.setDuration(SCROLL_TIME).start();
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (int) animation.getAnimatedValue();
scrollTo(0, value);
postInvalidate();
listener.onDoing();
}
});
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
listener.onEnd();
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
}
interface AnimListener {
void onDoing();
void onEnd();
}
}
这里有一个供外部调用的监听器:
public interface OnPullListener {
//执行刷新
void onRefresh();
//执行加载更多
void onLoadMore();
}
5.在项目中使用时的基本配置
通过上面的代码大家可以看到,我的下拉刷新框架,没有依赖任何res资源,很方便大家copy,或者直接依赖jar。
一般整个应用会有一个统一的下拉刷新效果和上拉加载效果,所以,我们可以再自定义一个应用的刷新布局,继承自PullLayout。
public class RefreshLayout extends PullLayout {
public RefreshLayout(Context context) {
super(context);
}
public RefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
init();
}
public void init() {
HeaderView header = new HeaderView(getContext());
FooterView footer = new FooterView(getContext());
addHeader(header);
addFooter(footer);
setHeader(header);
setFooter(footer);
}
}
这里的HeaderView和FooterView分别实现了PullHeader和PullFooter接口。
public class HeaderView extends FrameLayout implements PullHeader {
public TextView tvPullDown;
public HeaderView(Context context) {
super(context);
LayoutInflater.from(context).inflate(R.layout.layout_header, this, true);
tvPullDown = (TextView) findViewById(R.id.tv);
}
@Override
public void onDownBefore(int scrollY) {
tvPullDown.setText("下拉刷新");
}
@Override
public void onDownAfter(int scrollY) {
tvPullDown.setText("松开刷新");
}
@Override
public void onRefreshScrolling(int scrollY) {
tvPullDown.setText("准备刷新");
}
@Override
public void onRefreshDoing(int scrollY) {
tvPullDown.setText("正在刷新……");
}
@Override
public void onRefreshCompleteScrolling(int scrollY, boolean isLoadSuccess) {
tvPullDown.setText(isLoadSuccess ? "刷新成功" : "刷新失败");
}
@Override
public void onRefreshCancelScrolling(int scrollY) {
tvPullDown.setText("取消刷新");
}
}
public class FooterView extends FrameLayout implements PullFooter {
public TextView tvPullUp;
public FooterView(Context context) {
super(context);
LayoutInflater.from(context).inflate(R.layout.layout_footer, this, true);
tvPullUp = (TextView) findViewById(R.id.tv);
}
@Override
public void onUpBefore(int scrollY) {
tvPullUp.setText("上拉加载更多");
}
@Override
public void onUpAfter(int scrollY) {
tvPullUp.setText("松开加载更多");
}
@Override
public void onLoadScrolling(int scrollY) {
tvPullUp.setText("准备加载");
}
@Override
public void onLoadDoing(int scrollY) {
tvPullUp.setText("正在加载……");
}
@Override
public void onLoadCompleteScrolling(int scrollY, boolean isLoadSuccess) {
tvPullUp.setText(isLoadSuccess ? "加载成功" : "加载失败");
}
@Override
public void onLoadCancelScrolling(int scrollY) {
tvPullUp.setText("加载取消");
}
}
它们的布局资源也是很简单的:
layout_header:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#f5f5f5">
<TextView
android:id="@+id/tv"
android:layout_width="match_parent"
android:layout_height="@dimen/header_height"
android:gravity="center"
android:text="下拉刷新"
android:textColor="#000000"
android:textSize="16sp"/>
</RelativeLayout>
layout_footer:
<?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="wrap_content"
android:background="#f5f5f5">
<TextView
android:id="@+id/tv"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="@dimen/header_height"
android:text="上拉加载更多"
android:textColor="#000000"
android:textSize="16sp"/>
</RelativeLayout>
6.加个辅助类,让集成变得更加简单
本来呢,写到第5点,其实就不用写了,不过,考虑到有时候,一个应用中多个界面都有下拉刷新时,经常会有许多类似的代码块,所以,我又定义了一个辅助类。
这里的SingleAdapter、SuperViewHolder是使用的我的另一个库:fast-adapter:Adapter的封装之路,
而IWebLoading、WebTransformer、WebSubscriber等呢,则是与我的另一个库:fast-http有关,这是一个Retrofit+OkHttp+RxJava的封装库,暂时没写博客仔细整理,以后再分享,大家可以不用管它,那个与本节无关,IWebLoading是为了让初始数据时有loading,刷新或加载更多时没有loading。WebTransformer是为了通用的线程切换。WebSubscriber是为了通用的错误处理。大家可以先看看我的这两篇:Retrofit基本用法和流程分析、Okhttp基本用法和流程分析
public class RefreshHelper<T> {
public RefreshLayout viewRefresh;
public RefreshInterface<T> refreshInterface;
public int layoutId;
public Context context;
public IWebLoading webLoading;
public View viewEmpty;
public RecyclerView rv;
public List<T> data;
public SingleAdapter<T> adapter;
public int curPage = 1;//当前页码
public boolean isRefresh = false;//是否正在刷新
public boolean isLoadMore = false;//是否正在加载更多
public RefreshHelper(RefreshLayout viewRefresh,RefreshInterface<T> refreshInterface,int layoutId) {
this.viewRefresh=viewRefresh;
this.refreshInterface=refreshInterface;
this.layoutId=layoutId;
context=viewRefresh.getContext();
webLoading=new LoadingDialog(context);
viewEmpty=viewRefresh.getChildAt(0);
rv= (RecyclerView) viewRefresh.getChildAt(1);
data = new ArrayList<>();
initRv();
initRefresh();
loadData();
}
private void initRv() {
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
rv.setLayoutManager(layoutManager);
adapter = new SingleAdapter<T>(context, layoutId) {
@Override
protected void bindData(SuperViewHolder holder, T item) {
refreshInterface.bindData(holder, item);
}
};
rv.setAdapter(adapter);
}
private void initRefresh() {
viewRefresh.setOnPullListener(new OnPullListener() {
@Override
public void onRefresh() {
LogUtil.print("");
isRefresh = true;
curPage = 1;
data = new ArrayList<>();
loadData();
}
@Override
public void onLoadMore() {
LogUtil.print("");
isLoadMore = true;
curPage++;
loadData();
}
});
}
private void loadData() {
if (isRefresh || isLoadMore) {
webLoading = null;
}
refreshInterface.getData(curPage)
.compose(new WebTransformer<>(webLoading))
.subscribe(new WebSubscriber<List<T>>(webLoading) {
@Override
public void onSuccess(List<T> list) {
LogUtil.print("list.size="+list.size());
if (isRefresh) {
isRefresh = false;
viewRefresh.stopRefresh(true);
}
if (isLoadMore) {
if (list.isEmpty()) {
AppHelper.show("没有更多数据了");
}
isLoadMore = false;
viewRefresh.stopLoadMore(true);
}
data.addAll(list);
if (data.isEmpty()) {
rv.setVisibility(View.GONE);
viewEmpty.setVisibility(View.VISIBLE);
} else {
rv.setVisibility(View.VISIBLE);
viewEmpty.setVisibility(View.GONE);
adapter.setData(data);
}
}
@Override
public void onFailure(WebException exception) {
super.onFailure(exception);
if (isRefresh) {
isRefresh = false;
viewRefresh.stopRefresh(false);
}
if (isLoadMore) {
isLoadMore = false;
viewRefresh.stopLoadMore(false);
}
}
});
}
public interface RefreshInterface<T> {
void bindData(SuperViewHolder holder, T item);
Observable<List<T>> getData(int curPage);
}
public List<T> getData() {
return data;
}
public SingleAdapter<T> getAdapter() {
return adapter;
}
}
这里注意,RefreshLayout里面的子控件必须是这样的,第一个是viewEmpty,第二个是recyclerView(当然,你要是其它的,也可以,改辅助类吧,我这个辅助类只针对recyclerView):
<com.che.lovecar.support.view.pull.RefreshLayout
android:id="@+id/view_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/view_empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone">
<ImageView
android:layout_width="150dp"
android:layout_height="150dp"
android:scaleType="centerInside"
android:src="@drawable/icon_nomessage"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="暂无消息"
android:textColor="@color/text_d"
android:textSize="20sp"/>
</LinearLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_msg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never"
android:visibility="visible"/>
</com.che.lovecar.support.view.pull.RefreshLayout>
然后,在Activity或Fragment中使用时,我只需要这样:
1.添加一个RefreshHelper
private RefreshHelper<Message> refreshHelper;
refreshHelper = new RefreshHelper<Message>(viewRefresh, this, R.layout.item_msg);
2.Activity实现RefreshInterface接口,实现getData和bindData方法
public class MsgListActivity extends BaseActivity implements RefreshHelper.RefreshInterface<Message>
@Override
public void bindData(SuperViewHolder holder, Message item) {
View rootView = holder.getRootView();
View dot = holder.getView(R.id.dot_msg);
TextView tvTime = holder.getView(R.id.tv_time);
TextView tvMsg = holder.getView(R.id.tv_msg);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
dot.setVisibility(item.getRead() == 0 ? View.VISIBLE : View.GONE);
tvTime.setText(dateFormat.format(item.getCreate_time()));
tvMsg.setText(item.getContent());
rootView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
readMsg(item, dot);
}
});
}
@Override
public Observable<List<Message>> getData(int curPage) {
return Observable.create(subscriber -> {
if (subscriber.isUnsubscribed()) return;
try {
Thread.sleep(3000);
List<Message> list = new ArrayList<>();
if (!isEmpty) {
LogUtil.print("加载本地的json");
String json = FileUtil.getFromAssets(getActivity(), "json/list.json");
MessageListResponse response = JSON.parseObject(json, MessageListResponse.class);
list = response.getMessageListPojoList();
}
subscriber.onNext(list);
subscriber.onCompleted();
} catch (Exception e) {
subscriber.onError(e);
}
});
}