看過很多的下拉刷新框架,但感覺大多是基于ListView或RecyclerView捍岳。
個人覺得富寿,下拉上拉做為一個通用操作,最好是做為一個專門的容器锣夹,和視圖展示分離開來页徐,這樣就算內(nèi)容展示視圖要從ListView變成RecyclerViewl了,下拉上拉這一層银萍,也無需做任何改動变勇!
1.整體思路
- 自定義測量以及布局的方法
- 攔截掉子控件的一些手勢
- 處理手勢,刷新狀態(tài)
2.自定義測量和布局
繼承ViewGroup,重寫onMeasure與onLayout方法贴唇,這里要注意當(dāng)子控件GONE的情況
public abstract class DrawLayout extends ViewGroup {
public View header;
public View footer;
public PullHeader pullHeader;
public PullFooter pullFooter;
public int bottomScroll;// 當(dāng)滾動到內(nèi)容最底部時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) {
// 重置(避免重復(fù)累加)
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所有內(nèi)容視圖之后
else if (child == footer) {
child.layout(0, contentHeight, child.getMeasuredWidth(), contentHeight + child.getMeasuredHeight());
}
// 內(nèi)容視圖根據(jù)定義(插入)順序,按由上到下的順序在垂直方向進行排列
else {
child.layout(0, contentHeight, child.getMeasuredWidth(), contentHeight + child.getMeasuredHeight());
if (index <= lastChildIndex) {
if (child instanceof ScrollView) {
contentHeight += getMeasuredHeight();
continue;
}
contentHeight += child.getMeasuredHeight();
}
}
}
// 計算到達內(nèi)容最底部時ViewGroup的滑動距離
bottomScroll = contentHeight - getMeasuredHeight();
}
}
這里搀绣,我定義了兩個接口:
下拉接口:
public interface PullHeader {
//下拉刷新(下拉中,到達有效刷新距離前)
void onDownBefore(int scrollY);
//松開刷新(下拉中戳气,到達有效刷新距離后)
void onDownAfter(int scrollY);
//準(zhǔn)備刷新(從松手后的位置滾動到刷新的位置)
void onRefreshScrolling(int scrollY);
//正在刷新……
void onRefreshDoing(int scrollY);
//刷新完成后链患,回到默認(rèn)狀態(tài)中
void onRefreshCompleteScrolling(int scrollY, boolean isRefreshSuccess);
//刷新取消后,回到默認(rèn)狀態(tài)中(沒有超過有效的下拉距離)
void onRefreshCancelScrolling(int scrollY);
}
上拉接口:
public interface PullFooter {
//上拉加載
void onUpBefore(int scrollY);
//松開加載
void onUpAfter(int scrollY);
//準(zhǔn)備加載
void onLoadScrolling(int scrollY);
//正在加載……
void onLoadDoing(int scrollY);
//加載完成后瓶您,回到默認(rèn)狀態(tài)中
void onLoadCompleteScrolling(int scrollY, boolean isLoadSuccess);
//加載取消后麻捻,回到默認(rèn)狀態(tài)中
void onLoadCancelScrolling(int scrollY);
}
大家可以看到,我這里每一個回調(diào)中呀袱,都返回了scrollY贸毕,方便我們根據(jù)該值做一些自定義的動畫效果。
3.自定義攔截手勢
繼承剛才的DrawLayout夜赵,重寫onInterceptTouchEvent方法,這里主要是要解決當(dāng)子控件也可以滑動時的一些沖突問題明棍。
比如當(dāng)子控件是ScrollView時,只有當(dāng)ScrollView滑動到頂部或底部寇僧,不能再滑動時击蹲,才可以觸發(fā)下拉或上拉事件署拟。
public abstract class InterceptLauyout extends DrawLayout {
// 用于計算滑動距離的Y坐標(biāo)中介
public int lastYMove;
// 用于判斷是否攔截觸摸事件的Y坐標(biāo)中介
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坐標(biāo)
int y = (int) event.getY();
// 判斷觸摸事件類型
switch (event.getAction()) {
// Down事件
case MotionEvent.ACTION_DOWN: {
// 記錄下本次系列觸摸事件的起始點Y坐標(biāo)
lastYMove = y;
// 不攔截ACTION_DOWN,因為當(dāng)ACTION_DOWN被攔截歌豺,后續(xù)所有觸摸事件都會被攔截
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是否已經(jīng)到達內(nèi)容最頂部
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是否已經(jīng)到達內(nèi)容最底部
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.自定義處理手勢类咧,刷新狀態(tài)
首先馒铃,我定義了下拉的所有狀態(tài),基本上這里的每一種狀態(tài)都對應(yīng)著上面的一種接口回調(diào)痕惋。
public enum PullStatus {
DEFAULT,//默認(rèn)狀態(tài)
DOWN_BEFORE,//下拉中区宇,到達有效刷新距離前
DOWN_AFTER,//下拉中,到達有效刷新距離后
REFRESH_SCROLLING,//放手后值戳,開始刷新前议谷,回到刷新的位置中
REFRESH_DOING,//正在刷新中
REFRESH_COMPLETE_SCROLLING,//刷新完成后,回到默認(rèn)狀態(tài)中
REFRESH_CANCEL_SCROLLING,//刷新取消后堕虹,回到默認(rèn)狀態(tài)中
UP_BEFORE,//上拉中卧晓,到達有效刷新距離前
UP_AFTER,//上拉中,到達有效刷新距離后
LOADMORE_SCROLLING,//放手后赴捞,開始加載前逼裆,從手勢位置回到加載的位置中
LOADMORE_DOING,//正在加載中
LOADMORE_COMPLETE_SCROLLING,//加載完成后,回到默認(rèn)狀態(tài)中
LOADMORE_CANCEL_SCROLLING,//加載取消后赦政,回到默認(rèn)狀態(tài)中
}
接著胜宇,繼承剛才的InterceptLauyout,重寫onTouchEvent方法恢着,刷新狀態(tài)桐愉。這里我主要是通過屬性動畫+scrollTo/scrollBy來實現(xiàn)彈性滑動的。當(dāng)然你也可以用scroller來實現(xiàn)掰派。
需要注意scrollY和我們的視圖坐標(biāo)系方向相反仅财!
public class PullLayout extends InterceptLauyout {
// 事件監(jiān)聽接口
private OnPullListener listener;
// Layout狀態(tài)
private PullStatus status = PullStatus.DEFAULT;
//阻尼系數(shù)
private float damp = 0.5f;
//恢復(fù)動畫的執(zhí)行時間
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坐標(biāo)
lastYMove = y;
break;
}
case MotionEvent.ACTION_UP: {
// 判斷本次觸摸系列事件結(jié)束時,Layout的狀態(tài)
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("松手時是其他狀態(tài):" + status);
break;
}
}
}
lastYIntercept = 0;
postInvalidate();
return true;
}
//刷新狀態(tài)
private void updateStatus(PullStatus status) {
this.status = status;
int scrollY = getScrollY();
LogUtil.print("status=" + status);
// 判斷本次觸摸系列事件結(jié)束時,Layout的狀態(tài)
switch (status) {
//默認(rèn)狀態(tài)
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;
}
}
//默認(rèn)狀態(tài)
private void onDefault() {
isRefreshSuccess = false;
isLoadSuccess = false;
}
//滾動到加載狀態(tài)
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);
}
});
}
//滾動到刷新狀態(tài)
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);
}
});
}
//滾動到默認(rèn)狀態(tài)
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);
}
//執(zhí)行滑動
public void performScroll(int dy) {
scrollBy(0, (int) (-dy * damp));
}
//執(zhí)行動畫
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();
}
}
這里有一個供外部調(diào)用的監(jiān)聽器:
public interface OnPullListener {
//執(zhí)行刷新
void onRefresh();
//執(zhí)行加載更多
void onLoadMore();
}
5.在項目中使用時的基本配置
通過上面的代碼大家可以看到盏求,我的下拉刷新框架,沒有依賴任何res資源亿眠,很方便大家copy碎罚,或者直接依賴jar。
一般整個應(yīng)用會有一個統(tǒng)一的下拉刷新效果和上拉加載效果纳像,所以荆烈,我們可以再自定義一個應(yīng)用的刷新布局,繼承自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分別實現(xiàn)了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("準(zhǔn)備刷新");
}
@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("準(zhǔn)備加載");
}
@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點玫鸟,其實就不用寫了导绷,不過,考慮到有時候屎飘,一個應(yīng)用中多個界面都有下拉刷新時妥曲,經(jīng)常會有許多類似的代碼塊,所以钦购,我又定義了一個輔助類檐盟。
這里的SingleAdapter、SuperViewHolder是使用的我的另一個庫:fast-adapter:Adapter的封裝之路押桃,
而IWebLoading葵萎、WebTransformer、WebSubscriber等呢唱凯,則是與我的另一個庫:fast-http有關(guān)羡忘,這是一個Retrofit+OkHttp+RxJava的封裝庫,暫時沒寫博客仔細(xì)整理波丰,以后再分享,大家可以不用管它舶得,那個與本節(jié)無關(guān)掰烟,IWebLoading是為了讓初始數(shù)據(jù)時有l(wèi)oading,刷新或加載更多時沒有l(wèi)oading。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;//當(dāng)前頁碼
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("沒有更多數(shù)據(jù)了");
}
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(當(dāng)然,你要是其它的躺彬,也可以煤墙,改輔助類吧,我這個輔助類只針對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實現(xiàn)RefreshInterface接口,實現(xiàn)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);
}
});
}