1.概述
這期我們在上一期的RecyclerView更全解析之 - 為它優(yōu)雅的添加頭部和底部的基礎(chǔ)上再去增加功能,我相信我們在真正的實(shí)踐開發(fā)過程中肯定少不了下拉刷新和上拉加載。
我們需要思考一個問題上拉刷新下拉加載風(fēng)格各式各樣,淘寶和京東的列表刷新樣式就肯定不一樣,我們怎么樣做到版本迭代的時候可以快速的更改樣式。有時還需要顯示正在加載數(shù)據(jù)或者無數(shù)據(jù),比如篩選的時候有可能會出現(xiàn)沒有數(shù)據(jù)的情況會顯示無數(shù)據(jù)頁面,怎么快速做到?當(dāng)然如果你對系統(tǒng)架構(gòu)比較了解那就非常簡單了,又或者是你對面向?qū)ο蟮牧蠡驹瓌t比較熟悉也行。
相關(guān)文章:
RecyclerView更全解析之 - 基本使用和分割線解析
RecyclerView更全解析之 - 打造通用的萬能Adapter
RecyclerView更全解析之 - 為它優(yōu)雅的添加頭部和底部
RecyclerView更全解析之 - 打造通用的下拉刷新上拉加載
RecyclerView更全解析之 - 仿支付寶側(cè)滑刪除和拖動排序
2.基本思路
我們在寫項(xiàng)目或是搭建架構(gòu)的時候需要考慮最多的是擴(kuò)展,而不是先把所有的功能寫在一起或是全部寫好,或者說代碼過度設(shè)計(jì)本來很簡單的東西你非得跟人解釋這怎么怎么的,很忌諱。
肯定是希望目前寫好的東西,以后如果出現(xiàn)什么問題或者添加新的功能都不需要去修改我們已經(jīng)寫好的代碼,而是在原來的基礎(chǔ)上利用面向?qū)ο蟮乃枷肴U(kuò)展無論你是繼承也好還是實(shí)現(xiàn)也好都行,就不會出現(xiàn)需求改變的時候我們的代碼就改成了別人口中說的改成了......
本著這個原則我們大致的思想就是:
- 先處理下拉刷新,同時考慮刷新列表的不同風(fēng)格樣式,確保這個項(xiàng)目還是下一個項(xiàng)目都能用
- 再處理上拉加載更多,只需去繼承寫好的下拉刷新控件即可
- 可以適當(dāng)?shù)脑黾右恍┗竟δ埽缯诩虞d列表樣式或者說是無頁面數(shù)據(jù)樣式
- 封裝通用默認(rèn)的樣式,封裝好整個項(xiàng)目的通用樣式,如果下次需要修改擴(kuò)展即可
- 最后思考一下我們這樣去寫合不合理,給自己的同事用用自己和他們都做一下測評和修改
3.基本實(shí)現(xiàn)
3.1 下拉刷新
先處理下拉刷新,同時考慮刷新列表的不同風(fēng)格樣式,確保這個項(xiàng)目還是下一個項(xiàng)目都能用。這里我們肯定是繼承上一期的可以直接添加頭部和底部的WrapRecyclerView,為了確保實(shí)現(xiàn)不同的樣式,需要一個額外的輔助類:
/**
* Created by Darren on 2017/1/3.
* Email: 240336124@qq.com
* Description: 下拉刷新的輔助類為了匹配所有效果
*/
public abstract class RefreshViewCreator {
/**
* 獲取下拉刷新的View
*
* @param context 上下文
* @param parent RecyclerView
*/
public abstract View getRefreshView(Context context, ViewGroup parent);
/**
* 正在下拉
* @param currentDragHeight 當(dāng)前拖動的高度
* @param refreshViewHeight 總的刷新高度
* @param currentRefreshStatus 當(dāng)前狀態(tài)
*/
public abstract void onPull(int currentDragHeight, int refreshViewHeight, int currentRefreshStatus);
/**
* 正在刷新中
*/
public abstract void onRefreshing();
/**
* 停止刷新
*/
public abstract void onStopRefresh();
}
/**
* Created by Darren on 2017/1/3.
* Email: 240336124@qq.com
* Description: 下拉刷新的RecyclerView
*/
public class RefreshRecyclerView extends WrapRecyclerView {
// 下拉刷新的輔助類
private RefreshViewCreator mRefreshCreator;
// 下拉刷新頭部的高度
private int mRefreshViewHeight = 0;
// 下拉刷新的頭部View
private View mRefreshView;
// 手指按下的Y位置
private int mFingerDownY;
// 手指拖拽的阻力指數(shù)
private float mDragIndex = 0.35f;
// 當(dāng)前是否正在拖動
private boolean mCurrentDrag = false;
// 當(dāng)前的狀態(tài)
private int mCurrentRefreshStatus;
// 默認(rèn)狀態(tài)
public int REFRESH_STATUS_NORMAL = 0x0011;
// 下拉刷新狀態(tài)
public int REFRESH_STATUS_PULL_DOWN_REFRESH = 0x0022;
// 松開刷新狀態(tài)
public int REFRESH_STATUS_LOOSEN_REFRESHING = 0x0033;
// 正在刷新狀態(tài)
public int REFRESH_STATUS_REFRESHING = 0x0033;
public RefreshRecyclerView(Context context) {
super(context);
}
public RefreshRecyclerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public RefreshRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
// 先處理下拉刷新,同時考慮刷新列表的不同風(fēng)格樣式,確保這個項(xiàng)目還是下一個項(xiàng)目都能用
// 所以我們不能直接添加View,需要利用輔助類
public void addRefreshViewCreator(RefreshViewCreator refreshCreator) {
this.mRefreshCreator = refreshCreator;
addRefreshView();
}
@Override
public void setAdapter(Adapter adapter) {
super.setAdapter(adapter);
addRefreshView();
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
// 記錄手指按下的位置 ,之所以寫在dispatchTouchEvent那是因?yàn)槿绻覀兲幚砹藯l目點(diǎn)擊事件,
// 那么就不會進(jìn)入onTouchEvent里面,所以只能在這里獲取
mFingerDownY = (int) ev.getRawY();
break;
case MotionEvent.ACTION_UP:
if (mCurrentDrag) {
restoreRefreshView();
}
break;
}
return super.dispatchTouchEvent(ev);
}
/**
* 重置當(dāng)前刷新狀態(tài)狀態(tài)
*/
private void restoreRefreshView() {
int currentTopMargin = ((MarginLayoutParams) mRefreshView.getLayoutParams()).topMargin;
int finalTopMargin = -mRefreshViewHeight + 1;
if (mCurrentRefreshStatus == REFRESH_STATUS_LOOSEN_REFRESHING) {
finalTopMargin = 0;
mCurrentRefreshStatus = REFRESH_STATUS_REFRESHING;
if (mRefreshCreator != null) {
mRefreshCreator.onRefreshing();
}
if (mListener != null) {
mListener.onRefresh();
}
}
int distance = currentTopMargin - finalTopMargin;
// 回彈到指定位置
ValueAnimator animator = ObjectAnimator.ofFloat(currentTopMargin, finalTopMargin).setDuration(distance);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentTopMargin = (float) animation.getAnimatedValue();
setRefreshViewMarginTop((int) currentTopMargin);
}
});
animator.start();
mCurrentDrag = false;
}
@Override
public boolean onTouchEvent(MotionEvent e) {
switch (e.getAction()) {
case MotionEvent.ACTION_MOVE:
// 如果是在最頂部才處理,否則不需要處理
if (canScrollUp() || mCurrentRefreshStatus == REFRESH_STATUS_REFRESHING) {
// 如果沒有到達(dá)最頂端,也就是說還可以向上滾動就什么都不處理
return super.onTouchEvent(e);
}
// 解決下拉刷新自動滾動問題
if (mCurrentDrag) {
scrollToPosition(0);
}
// 獲取手指觸摸拖拽的距離
int distanceY = (int) ((e.getRawY() - mFingerDownY) * mDragIndex);
// 如果是已經(jīng)到達(dá)頭部,并且不斷的向下拉,那么不斷的改變r(jià)efreshView的marginTop的值
if (distanceY > 0) {
int marginTop = distanceY - mRefreshViewHeight;
setRefreshViewMarginTop(marginTop);
updateRefreshStatus(marginTop);
mCurrentDrag = true;
return false;
}
break;
}
return super.onTouchEvent(e);
}
/**
* 更新刷新的狀態(tài)
*/
private void updateRefreshStatus(int marginTop) {
if (marginTop <= -mRefreshViewHeight) {
mCurrentRefreshStatus = REFRESH_STATUS_NORMAL;
} else if (marginTop < 0) {
mCurrentRefreshStatus = REFRESH_STATUS_PULL_DOWN_REFRESH;
} else {
mCurrentRefreshStatus = REFRESH_STATUS_LOOSEN_REFRESHING;
}
if (mRefreshCreator != null) {
mRefreshCreator.onPull(marginTop, mRefreshViewHeight, mCurrentRefreshStatus);
}
}
/**
* 添加頭部的刷新View
*/
private void addRefreshView() {
RecyclerView.Adapter adapter = getAdapter();
if (adapter != null && mRefreshCreator != null) {
// 添加頭部的刷新View
View refreshView = mRefreshCreator.getRefreshView(getContext(), this);
if (refreshView != null) {
addHeaderView(refreshView);
this.mRefreshView = refreshView;
}
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (changed) {
if (mRefreshView != null && mRefreshViewHeight <= 0) {
// 獲取頭部刷新View的高度
mRefreshViewHeight = mRefreshView.getMeasuredHeight();
if (mRefreshViewHeight > 0) {
// 隱藏頭部刷新的View marginTop 多留出1px防止無法判斷是不是滾動到頭部問題
setRefreshViewMarginTop(-mRefreshViewHeight + 1);
}
}
}
}
/**
* 設(shè)置刷新View的marginTop
*/
public void setRefreshViewMarginTop(int marginTop) {
MarginLayoutParams params = (MarginLayoutParams) mRefreshView.getLayoutParams();
if (marginTop < -mRefreshViewHeight + 1) {
marginTop = -mRefreshViewHeight + 1;
}
params.topMargin = marginTop;
mRefreshView.setLayoutParams(params);
}
/**
* @return Whether it is possible for the child view of this layout to
* scroll up. Override this if the child view is a custom view.
* 判斷是不是滾動到了最頂部,這個是從SwipeRefreshLayout里面copy過來的源代碼
*/
public boolean canScrollUp() {
if (android.os.Build.VERSION.SDK_INT < 14) {
return ViewCompat.canScrollVertically(this, -1) || this.getScrollY() > 0;
} else {
return ViewCompat.canScrollVertically(this, -1);
}
}
/**
* 停止刷新
*/
public void onStopRefresh() {
mCurrentRefreshStatus = REFRESH_STATUS_NORMAL;
restoreRefreshView();
if (mRefreshCreator != null) {
mRefreshCreator.onStopRefresh();
}
}
// 處理刷新回調(diào)監(jiān)聽
private OnRefreshListener mListener;
public void setOnRefreshListener(OnRefreshListener listener) {
this.mListener = listener;
}
public interface OnRefreshListener {
void onRefresh();
}
}
我們來寫一個默認(rèn)的下拉刷新效果測試一下,這個gif錄制軟件的效果不是特別給力
/**
* Created by Darren on 2017/1/3.
* Email: 240336124@qq.com
* Description: 默認(rèn)樣式的頭部刷新
* 如淘寶、京東、不同的樣式可以自己去實(shí)現(xiàn)
*/
public class DefaultRefreshCreator extends RefreshViewCreator {
// 加載數(shù)據(jù)的ImageView
private View mRefreshIv;
@Override
public View getRefreshView(Context context, ViewGroup parent) {
View refreshView = LayoutInflater.from(context).inflate(R.layout.layout_refresh_header_view, parent, false);
mRefreshIv = refreshView.findViewById(R.id.refresh_iv);
return refreshView;
}
@Override
public void onPull(int currentDragHeight, int refreshViewHeight, int currentRefreshStatus) {
float rotate = ((float) currentDragHeight) / refreshViewHeight;
// 不斷下拉的過程中不斷的旋轉(zhuǎn)圖片
mRefreshIv.setRotation(rotate * 360);
}
@Override
public void onRefreshing() {
// 刷新的時候不斷旋轉(zhuǎn)
RotateAnimation animation = new RotateAnimation(0, 720,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
animation.setRepeatCount(-1);
animation.setDuration(1000);
mRefreshIv.startAnimation(animation);
}
@Override
public void onStopRefresh() {
// 停止加載的時候清除動畫
mRefreshIv.setRotation(0);
mRefreshIv.clearAnimation();
}
}
3.2 處理上拉加載更多
再處理上拉加載更多,只需去繼承寫好的下拉刷新控件即可。我們的確可以在原來的這個下拉刷新的控件中去寫,但是有幾個問題都寫到一堆出了問題找誰?別人怎么看代碼?如果該需求只要下拉刷新呢?說好的擴(kuò)展。所以我們新寫一個控件繼承已經(jīng)寫好的下拉刷新控件每個類負(fù)責(zé)單獨(dú)的事情
/**
* Created by Darren on 2017/1/3.
* Email: 240336124@qq.com
* Description: 上拉加載更多的輔助類為了匹配所有效果
*/
public abstract class LoadViewCreator {
/**
* 獲取上拉加載更多的View
*
* @param context 上下文
* @param parent RecyclerView
*/
public abstract View getLoadView(Context context, ViewGroup parent);
/**
* 正在上拉
*
* @param currentDragHeight 當(dāng)前拖動的高度
* @param loadViewHeight 總的加載高度
* @param currentLoadStatus 當(dāng)前狀態(tài)
*/
public abstract void onPull(int currentDragHeight, int loadViewHeight, int currentLoadStatus);
/**
* 正在加載中
*/
public abstract void onLoading();
/**
* 停止加載
*/
public abstract void onStopLoad();
}
/**
* Created by Darren on 2017/1/3.
* Email: 240336124@qq.com
* Description: 下拉刷新上拉加載更多的RecyclerView
*/
public class LoadRefreshRecyclerView extends RefreshRecyclerView {
// 上拉加載更多的輔助類
private LoadViewCreator mLoadCreator;
// 上拉加載更多頭部的高度
private int mLoadViewHeight = 0;
// 上拉加載更多的頭部View
private View mLoadView;
// 手指按下的Y位置
private int mFingerDownY;
// 當(dāng)前是否正在拖動
private boolean mCurrentDrag = false;
// 當(dāng)前的狀態(tài)
private int mCurrentLoadStatus;
// 默認(rèn)狀態(tài)
public int LOAD_STATUS_NORMAL = 0x0011;
// 上拉加載更多狀態(tài)
public static int LOAD_STATUS_PULL_DOWN_REFRESH = 0x0022;
// 松開加載更多狀態(tài)
public static int LOAD_STATUS_LOOSEN_LOADING = 0x0033;
// 正在加載更多狀態(tài)
public int LOAD_STATUS_LOADING = 0x0044;
public LoadRefreshRecyclerView(Context context) {
super(context);
}
public LoadRefreshRecyclerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public LoadRefreshRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
// 先處理上拉加載更多,同時考慮加載列表的不同風(fēng)格樣式,確保這個項(xiàng)目還是下一個項(xiàng)目都能用
// 所以我們不能直接添加View,需要利用輔助類
public void addLoadViewCreator(LoadViewCreator loadCreator) {
this.mLoadCreator = loadCreator;
addRefreshView();
}
@Override
public void setAdapter(Adapter adapter) {
super.setAdapter(adapter);
addRefreshView();
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
// 記錄手指按下的位置 ,之所以寫在dispatchTouchEvent那是因?yàn)槿绻覀兲幚砹藯l目點(diǎn)擊事件,
// 那么就不會進(jìn)入onTouchEvent里面,所以只能在這里獲取
mFingerDownY = (int) ev.getRawY();
break;
case MotionEvent.ACTION_UP:
if (mCurrentDrag) {
restoreLoadView();
}
break;
}
return super.dispatchTouchEvent(ev);
}
/**
* 重置當(dāng)前加載更多狀態(tài)
*/
private void restoreLoadView() {
int currentBottomMargin = ((MarginLayoutParams) mLoadView.getLayoutParams()).bottomMargin;
int finalBottomMargin = 0;
if (mCurrentLoadStatus == LOAD_STATUS_LOOSEN_LOADING) {
mCurrentLoadStatus = LOAD_STATUS_LOADING;
if (mLoadCreator != null) {
mLoadCreator.onLoading();
}
if (mListener != null) {
mListener.onLoad();
}
}
int distance = currentBottomMargin - finalBottomMargin;
// 回彈到指定位置
ValueAnimator animator = ObjectAnimator.ofFloat(currentBottomMargin, finalBottomMargin).setDuration(distance);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentTopMargin = (float) animation.getAnimatedValue();
setLoadViewMarginBottom((int) currentTopMargin);
}
});
animator.start();
mCurrentDrag = false;
}
@Override
public boolean onTouchEvent(MotionEvent e) {
switch (e.getAction()) {
case MotionEvent.ACTION_MOVE:
// 如果是在最底部才處理,否則不需要處理
if (canScrollDown() || mCurrentLoadStatus == LOAD_STATUS_LOADING) {
// 如果沒有到達(dá)最頂端,也就是說還可以向上滾動就什么都不處理
return super.onTouchEvent(e);
}
if (mLoadCreator != null) {
mLoadViewHeight = mLoadView.getMeasuredHeight();
}
// 解決上拉加載更多自動滾動問題
if (mCurrentDrag) {
scrollToPosition(getAdapter().getItemCount() - 1);
}
// 獲取手指觸摸拖拽的距離
int distanceY = (int) ((e.getRawY() - mFingerDownY) * mDragIndex);
// 如果是已經(jīng)到達(dá)頭部,并且不斷的向下拉,那么不斷的改變r(jià)efreshView的marginTop的值
if (distanceY < 0) {
setLoadViewMarginBottom(-distanceY);
updateLoadStatus(-distanceY);
mCurrentDrag = true;
return true;
}
break;
}
return super.onTouchEvent(e);
}
/**
* 更新加載的狀態(tài)
*/
private void updateLoadStatus(int distanceY) {
if (distanceY <= 0) {
mCurrentLoadStatus = LOAD_STATUS_NORMAL;
} else if (distanceY < mLoadViewHeight) {
mCurrentLoadStatus = LOAD_STATUS_PULL_DOWN_REFRESH;
} else {
mCurrentLoadStatus = LOAD_STATUS_LOOSEN_LOADING;
}
if (mLoadCreator != null) {
mLoadCreator.onPull(distanceY, mLoadViewHeight, mCurrentLoadStatus);
}
}
/**
* 添加底部加載更多View
*/
private void addRefreshView() {
Adapter adapter = getAdapter();
if (adapter != null && mLoadCreator != null) {
// 添加底部加載更多View
View loadView = mLoadCreator.getLoadView(getContext(), this);
if (loadView != null) {
addFooterView(loadView);
this.mLoadView = loadView;
}
}
}
/**
* 設(shè)置加載View的marginBottom
*/
public void setLoadViewMarginBottom(int marginBottom) {
MarginLayoutParams params = (MarginLayoutParams) mLoadView.getLayoutParams();
if (marginBottom < 0) {
marginBottom = 0;
}
params.bottomMargin = marginBottom;
mLoadView.setLayoutParams(params);
}
/**
* @return Whether it is possible for the child view of this layout to
* scroll up. Override this if the child view is a custom view.
* 判斷是不是滾動到了最頂部,這個是從SwipeRefreshLayout里面copy過來的源代碼
*/
public boolean canScrollDown() {
return ViewCompat.canScrollVertically(this, 1);
}
/**
* 停止加載更多
*/
public void onStopLoad() {
mCurrentLoadStatus = LOAD_STATUS_NORMAL;
restoreLoadView();
if (mLoadCreator != null) {
mLoadCreator.onStopLoad();
}
}
// 處理加載更多回調(diào)監(jiān)聽
private OnLoadMoreListener mListener;
public void setOnLoadMoreListener(OnLoadMoreListener listener) {
this.mListener = listener;
}
public interface OnLoadMoreListener {
void onLoad();
}
}
3.3 增加一些基本通用功能
最后我們在這個基礎(chǔ)在增加一些基本的功能,如正在加載數(shù)據(jù)的頁面,或者數(shù)據(jù)是空的頁面,所以決定找一層最合適的方法去改,那就是我們上一期的WrapRecyclerView的基礎(chǔ)上去改,因?yàn)槟鞘俏覀傾dapter密切聯(lián)系的一層。
/**
* Created by Darren on 2016/12/29.
* Email: 240336124@qq.com
* Description: 可以添加頭部和底部的RecyclerView
*/
public class WrapRecyclerView extends RecyclerView {
// 增加一些通用功能
// 空列表數(shù)據(jù)應(yīng)該顯示的空View
// 正在加載數(shù)據(jù)頁面,也就是正在獲取后臺接口頁面
private View mEmptyView, mLoadingView;
// 省略...上一期已有代碼
private AdapterDataObserver mDataObserver = new AdapterDataObserver() {
@Override
public void onChanged() {
if (mAdapter == null) return;
// 觀察者 列表Adapter更新 包裹的也需要更新不然列表的notifyDataSetChanged沒效果
if (mWrapRecyclerAdapter != mAdapter)
mWrapRecyclerAdapter.notifyDataSetChanged();
dataChanged();
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
if (mAdapter == null) return;
// 觀察者 列表Adapter更新 包裹的也需要更新不然列表的notifyDataSetChanged沒效果
if (mWrapRecyclerAdapter != mAdapter)
mWrapRecyclerAdapter.notifyItemRemoved(positionStart);
dataChanged();
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
if (mAdapter == null) return;
// 觀察者 列表Adapter更新 包裹的也需要更新不然列表的notifyItemMoved沒效果
if (mWrapRecyclerAdapter != mAdapter)
mWrapRecyclerAdapter.notifyItemMoved(fromPosition, toPosition);
dataChanged();
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
if (mAdapter == null) return;
// 觀察者 列表Adapter更新 包裹的也需要更新不然列表的notifyItemChanged沒效果
if (mWrapRecyclerAdapter != mAdapter)
mWrapRecyclerAdapter.notifyItemChanged(positionStart);
dataChanged();
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
if (mAdapter == null) return;
// 觀察者 列表Adapter更新 包裹的也需要更新不然列表的notifyItemChanged沒效果
if (mWrapRecyclerAdapter != mAdapter)
mWrapRecyclerAdapter.notifyItemChanged(positionStart, payload);
dataChanged();
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
if (mAdapter == null) return;
// 觀察者 列表Adapter更新 包裹的也需要更新不然列表的notifyItemInserted沒效果
if (mWrapRecyclerAdapter != mAdapter)
mWrapRecyclerAdapter.notifyItemInserted(positionStart);
dataChanged();
}
};
/**
* 添加一個空列表數(shù)據(jù)頁面
*/
public void addEmptyView(View emptyView) {
this.mEmptyView = emptyView;
}
/**
* 添加一個正在加載數(shù)據(jù)的頁面
*/
public void addLoadingView(View loadingView) {
this.mLoadingView = loadingView;
}
/**
* Adapter數(shù)據(jù)改變的方法
*/
private void dataChanged() {
if (mAdapter.getItemCount() == 0) {
// 沒有數(shù)據(jù)
if (mEmptyView != null) {
mEmptyView.setVisibility(VISIBLE);
} else {
mEmptyView.setVisibility(GONE);
}
}
}
// 省略...上一期已有代碼
}
上一期的代碼就已經(jīng)省略了RecyclerView更全解析之 - 為它優(yōu)雅的添加頭部和底部,到目前應(yīng)該所有的這些列表刷新和加載樣式都可以實(shí)現(xiàn),具體的一些要求可以自己修改修改。我這里就不在把它使用到具體的項(xiàng)目中了,我自己也用到了自己的項(xiàng)目中,之所以之前沒寫這一期的博客是因?yàn)樵谑褂玫倪^程中出現(xiàn)了一些Bug,所以才等到這個時候。
所有分享大綱:Android進(jìn)階之旅 - 自定義View篇