RecyclerView更全解析之 - 打造通用的下拉刷新上拉加载

1.概述


这期我们在上一期的RecyclerView更全解析之 - 为它优雅的添加头部和底部的基础上再去增加功能,我相信我们在真正的实践开发过程中肯定少不了下拉刷新和上拉加载。
  我们需要思考一个问题上拉刷新下拉加载风格各式各样,淘宝和京东的列表刷新样式就肯定不一样,我们怎么样做到版本迭代的时候可以快速的更改样式。有时还需要显示正在加载数据或者无数据,比如筛选的时候有可能会出现没有数据的情况会显示无数据页面,怎么快速做到?当然如果你对系统架构比较了解那就非常简单了,又或者是你对面向对象的六大基本原则比较熟悉也行。

相关文章:
  
  RecyclerView更全解析之 - 基本使用和分割线解析
  
  RecyclerView更全解析之 - 打造通用的万能Adapter
  
  RecyclerView更全解析之 - 为它优雅的添加头部和底部
  
  RecyclerView更全解析之 - 打造通用的下拉刷新上拉加载
  
  RecyclerView更全解析之 - 仿支付宝侧滑删除和拖动排序
  
  
  

这里写图片描述

2.基本思路


我们在写项目或是搭建架构的时候需要考虑最多的是扩展,而不是先把所有的功能写在一起或是全部写好,或者说代码过度设计本来很简单的东西你非得跟人解释这怎么怎么的,很忌讳。
  肯定是希望目前写好的东西,以后如果出现什么问题或者添加新的功能都不需要去修改我们已经写好的代码,而是在原来的基础上利用面向对象的思想去扩展无论你是继承也好还是实现也好都行,就不会出现需求改变的时候我们的代码就改成了别人口中说的改成了......
  
  本着这个原则我们大致的思想就是:

  • 先处理下拉刷新,同时考虑刷新列表的不同风格样式,确保这个项目还是下一个项目都能用
  • 再处理上拉加载更多,只需去继承写好的下拉刷新控件即可
  • 可以适当的增加一些基本功能,如正在加载列表样式或者说是无页面数据样式
  • 封装通用默认的样式,封装好整个项目的通用样式,如果下次需要修改扩展即可
  • 最后思考一下我们这样去写合不合理,给自己的同事用用自己和他们都做一下测评和修改

3.基本实现


3.1 下拉刷新
  先处理下拉刷新,同时考虑刷新列表的不同风格样式,确保这个项目还是下一个项目都能用。这里我们肯定是继承上一期的可以直接添加头部和底部的WrapRecyclerView,为了确保实现不同的样式,需要一个额外的辅助类:

1/** 2 * Created by Darren on 2017/1/3. 3 * Email: 240336124@qq.com 4 * Description: 下拉刷新的辅助类为了匹配所有效果 5 */ 6 7public abstract class RefreshViewCreator { 8 9 /** 10 * 获取下拉刷新的View 11 * 12 * @param context 上下文 13 * @param parent RecyclerView 14 */ 15 public abstract View getRefreshView(Context context, ViewGroup parent); 16 17 /** 18 * 正在下拉 19 * @param currentDragHeight 当前拖动的高度 20 * @param refreshViewHeight 总的刷新高度 21 * @param currentRefreshStatus 当前状态 22 */ 23 public abstract void onPull(int currentDragHeight, int refreshViewHeight, int currentRefreshStatus); 24 25 /** 26 * 正在刷新中 27 */ 28 public abstract void onRefreshing(); 29 30 /** 31 * 停止刷新 32 */ 33 public abstract void onStopRefresh(); 34}

/**

  • 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; // 手指拖拽的阻力指数 private float mDragIndex = 0.35f; // 当前是否正在拖动 private boolean mCurrentDrag = false; // 当前的状态 private int mCurrentRefreshStatus; // 默认状态 public int REFRESH_STATUS_NORMAL = 0x0011; // 下拉刷新状态 public int REFRESH_STATUS_PULL_DOWN_REFRESH = 0x0022; // 松开刷新状态 public int REFRESH_STATUS_LOOSEN_REFRESHING = 0x0033; // 正在刷新状态 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); }

    // 先处理下拉刷新,同时考虑刷新列表的不同风格样式,确保这个项目还是下一个项目都能用 // 所以我们不能直接添加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那是因为如果我们处理了条目点击事件, // 那么就不会进入onTouchEvent里面,所以只能在这里获取 mFingerDownY = (int) ev.getRawY(); break;

    1 case MotionEvent.ACTION_UP: 2 if (mCurrentDrag) { 3 restoreRefreshView(); 4 } 5 break; 6 } 7 return super.dispatchTouchEvent(ev);

    }

    /**

    • 重置当前刷新状态状态 */ 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) { // 如果没有到达最顶端,也就是说还可以向上滚动就什么都不处理 return super.onTouchEvent(e); }

    1 // 解决下拉刷新自动滚动问题 2 if (mCurrentDrag) { 3 scrollToPosition(0); 4 } 5 6 // 获取手指触摸拖拽的距离 7 int distanceY = (int) ((e.getRawY() - mFingerDownY) * mDragIndex); 8 // 如果是已经到达头部,并且不断的向下拉,那么不断的改变refreshView的marginTop的值 9 if (distanceY > 0) { 10 int marginTop = distanceY - mRefreshViewHeight; 11 setRefreshViewMarginTop(marginTop); 12 updateRefreshStatus(marginTop); 13 mCurrentDrag = true; 14 return false; 15 } 16 break; 17 } 18 19 return super.onTouchEvent(e);

    }

    /**

    • 更新刷新的状态 */ 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); } } } }

    /**

    • 设置刷新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(); } }

    // 处理刷新回调监听 private OnRefreshListener mListener;

    public void setOnRefreshListener(OnRefreshListener listener) { this.mListener = listener; }

    public interface OnRefreshListener { void onRefresh(); } }

1 2我们来写一个默认的下拉刷新效果测试一下,这个gif录制软件的效果不是特别给力 3

/**

  • Created by Darren on 2017/1/3.
  • Email: 240336124@qq.com
  • Description: 默认样式的头部刷新
  •          如淘宝、京东、不同的样式可以自己去实现
    

*/

public class DefaultRefreshCreator extends RefreshViewCreator { // 加载数据的ImageView private View mRefreshIv;

1@Override 2public View getRefreshView(Context context, ViewGroup parent) { 3 View refreshView = LayoutInflater.from(context).inflate(R.layout.layout_refresh_header_view, parent, false); 4 mRefreshIv = refreshView.findViewById(R.id.refresh_iv); 5 return refreshView; 6} 7 8@Override 9public void onPull(int currentDragHeight, int refreshViewHeight, int currentRefreshStatus) { 10 float rotate = ((float) currentDragHeight) / refreshViewHeight; 11 // 不断下拉的过程中不断的旋转图片 12 mRefreshIv.setRotation(rotate * 360); 13} 14 15@Override 16public void onRefreshing() { 17 // 刷新的时候不断旋转 18 RotateAnimation animation = new RotateAnimation(0, 720, 19 Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); 20 animation.setRepeatCount(-1); 21 animation.setDuration(1000); 22 mRefreshIv.startAnimation(animation); 23} 24 25@Override 26public void onStopRefresh() { 27 // 停止加载的时候清除动画 28 mRefreshIv.setRotation(0); 29 mRefreshIv.clearAnimation(); 30}

}

1 2![](https://img-hello-world.oss-cn-beijing.aliyuncs.com/ec7e9ec914f16117425b57c1ead44b9d.gif) 3 4这里写图片描述 5 6**32 处理上拉加载更多** 7  再处理上拉加载更多,只需去继承写好的下拉刷新控件即可。我们的确可以在原来的这个下拉刷新的控件中去写,但是有几个问题都写到一堆出了问题找谁?别人怎么看代码?如果该需求只要下拉刷新呢?说好的扩展。所以我们新写一个控件继承已经写好的下拉刷新控件每个类负责单独的事情 8

/**

  • Created by Darren on 2017/1/3.
  • Email: 240336124@qq.com
  • Description: 上拉加载更多的辅助类为了匹配所有效果 */

public abstract class LoadViewCreator {

1/** 2 * 获取上拉加载更多的View 3 * 4 * @param context 上下文 5 * @param parent RecyclerView 6 */ 7public abstract View getLoadView(Context context, ViewGroup parent); 8 9/** 10 * 正在上拉 11 * 12 * @param currentDragHeight 当前拖动的高度 13 * @param loadViewHeight 总的加载高度 14 * @param currentLoadStatus 当前状态 15 */ 16public abstract void onPull(int currentDragHeight, int loadViewHeight, int currentLoadStatus); 17 18/** 19 * 正在加载中 20 */ 21public abstract void onLoading(); 22 23/** 24 * 停止加载 25 */ 26public abstract void onStopLoad();

}

1/** 2 * Created by Darren on 2017/1/3. 3 * Email: 240336124@qq.com 4 * Description: 下拉刷新上拉加载更多的RecyclerView 5 */ 6public class LoadRefreshRecyclerView extends RefreshRecyclerView { 7 // 上拉加载更多的辅助类 8 private LoadViewCreator mLoadCreator; 9 // 上拉加载更多头部的高度 10 private int mLoadViewHeight = 0; 11 // 上拉加载更多的头部View 12 private View mLoadView; 13 // 手指按下的Y位置 14 private int mFingerDownY; 15 // 当前是否正在拖动 16 private boolean mCurrentDrag = false; 17 // 当前的状态 18 private int mCurrentLoadStatus; 19 // 默认状态 20 public int LOAD_STATUS_NORMAL = 0x0011; 21 // 上拉加载更多状态 22 public static int LOAD_STATUS_PULL_DOWN_REFRESH = 0x0022; 23 // 松开加载更多状态 24 public static int LOAD_STATUS_LOOSEN_LOADING = 0x0033; 25 // 正在加载更多状态 26 public int LOAD_STATUS_LOADING = 0x0044; 27 28 public LoadRefreshRecyclerView(Context context) { 29 super(context); 30 } 31 32 public LoadRefreshRecyclerView(Context context, @Nullable AttributeSet attrs) { 33 super(context, attrs); 34 } 35 36 public LoadRefreshRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { 37 super(context, attrs, defStyle); 38 } 39 40 // 先处理上拉加载更多,同时考虑加载列表的不同风格样式,确保这个项目还是下一个项目都能用 41 // 所以我们不能直接添加View,需要利用辅助类 42 public void addLoadViewCreator(LoadViewCreator loadCreator) { 43 this.mLoadCreator = loadCreator; 44 addRefreshView(); 45 } 46 47 @Override 48 public void setAdapter(Adapter adapter) { 49 super.setAdapter(adapter); 50 addRefreshView(); 51 } 52 53 @Override 54 public boolean dispatchTouchEvent(MotionEvent ev) { 55 switch (ev.getAction()) { 56 case MotionEvent.ACTION_DOWN: 57 // 记录手指按下的位置 ,之所以写在dispatchTouchEvent那是因为如果我们处理了条目点击事件, 58 // 那么就不会进入onTouchEvent里面,所以只能在这里获取 59 mFingerDownY = (int) ev.getRawY(); 60 break; 61 62 case MotionEvent.ACTION_UP: 63 if (mCurrentDrag) { 64 restoreLoadView(); 65 } 66 break; 67 } 68 return super.dispatchTouchEvent(ev); 69 } 70 71 /** 72 * 重置当前加载更多状态 73 */ 74 private void restoreLoadView() { 75 int currentBottomMargin = ((MarginLayoutParams) mLoadView.getLayoutParams()).bottomMargin; 76 int finalBottomMargin = 0; 77 if (mCurrentLoadStatus == LOAD_STATUS_LOOSEN_LOADING) { 78 mCurrentLoadStatus = LOAD_STATUS_LOADING; 79 if (mLoadCreator != null) { 80 mLoadCreator.onLoading(); 81 } 82 if (mListener != null) { 83 mListener.onLoad(); 84 } 85 } 86 87 int distance = currentBottomMargin - finalBottomMargin; 88 89 // 回弹到指定位置 90 ValueAnimator animator = ObjectAnimator.ofFloat(currentBottomMargin, finalBottomMargin).setDuration(distance); 91 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 92 @Override 93 public void onAnimationUpdate(ValueAnimator animation) { 94 float currentTopMargin = (float) animation.getAnimatedValue(); 95 setLoadViewMarginBottom((int) currentTopMargin); 96 } 97 }); 98 animator.start(); 99 mCurrentDrag = false; 100 } 101 102 103 @Override 104 public boolean onTouchEvent(MotionEvent e) { 105 switch (e.getAction()) { 106 case MotionEvent.ACTION_MOVE: 107 // 如果是在最底部才处理,否则不需要处理 108 if (canScrollDown() || mCurrentLoadStatus == LOAD_STATUS_LOADING) { 109 // 如果没有到达最顶端,也就是说还可以向上滚动就什么都不处理 110 return super.onTouchEvent(e); 111 } 112 113 if (mLoadCreator != null) { 114 mLoadViewHeight = mLoadView.getMeasuredHeight(); 115 } 116 // 解决上拉加载更多自动滚动问题 117 if (mCurrentDrag) { 118 scrollToPosition(getAdapter().getItemCount() - 1); 119 } 120 121 // 获取手指触摸拖拽的距离 122 int distanceY = (int) ((e.getRawY() - mFingerDownY) * mDragIndex); 123 // 如果是已经到达头部,并且不断的向下拉,那么不断的改变refreshView的marginTop的值 124 if (distanceY < 0) { 125 setLoadViewMarginBottom(-distanceY); 126 updateLoadStatus(-distanceY); 127 mCurrentDrag = true; 128 return true; 129 } 130 break; 131 } 132 133 return super.onTouchEvent(e); 134 } 135 136 /** 137 * 更新加载的状态 138 */ 139 private void updateLoadStatus(int distanceY) { 140 if (distanceY <= 0) { 141 mCurrentLoadStatus = LOAD_STATUS_NORMAL; 142 } else if (distanceY < mLoadViewHeight) { 143 mCurrentLoadStatus = LOAD_STATUS_PULL_DOWN_REFRESH; 144 } else { 145 mCurrentLoadStatus = LOAD_STATUS_LOOSEN_LOADING; 146 } 147 148 if (mLoadCreator != null) { 149 mLoadCreator.onPull(distanceY, mLoadViewHeight, mCurrentLoadStatus); 150 } 151 } 152 153 /** 154 * 添加底部加载更多View 155 */ 156 private void addRefreshView() { 157 Adapter adapter = getAdapter(); 158 if (adapter != null && mLoadCreator != null) { 159 // 添加底部加载更多View 160 View loadView = mLoadCreator.getLoadView(getContext(), this); 161 if (loadView != null) { 162 addFooterView(loadView); 163 this.mLoadView = loadView; 164 } 165 } 166 } 167 168 /** 169 * 设置加载View的marginBottom 170 */ 171 public void setLoadViewMarginBottom(int marginBottom) { 172 MarginLayoutParams params = (MarginLayoutParams) mLoadView.getLayoutParams(); 173 if (marginBottom < 0) { 174 marginBottom = 0; 175 } 176 params.bottomMargin = marginBottom; 177 mLoadView.setLayoutParams(params); 178 } 179 180 181 /** 182 * @return Whether it is possible for the child view of this layout to 183 * scroll up. Override this if the child view is a custom view. 184 * 判断是不是滚动到了最顶部,这个是从SwipeRefreshLayout里面copy过来的源代码 185 */ 186 public boolean canScrollDown() { 187 return ViewCompat.canScrollVertically(this, 1); 188 } 189 190 /** 191 * 停止加载更多 192 */ 193 public void onStopLoad() { 194 mCurrentLoadStatus = LOAD_STATUS_NORMAL; 195 restoreLoadView(); 196 if (mLoadCreator != null) { 197 mLoadCreator.onStopLoad(); 198 } 199 } 200 201 // 处理加载更多回调监听 202 private OnLoadMoreListener mListener; 203 204 public void setOnLoadMoreListener(OnLoadMoreListener listener) { 205 this.mListener = listener; 206 } 207 208 public interface OnLoadMoreListener { 209 void onLoad(); 210 } 211} 212``` 213 214![](https://img-hello-world.oss-cn-beijing.aliyuncs.com/7afe0629bf8679f2445ce3f3ee0562c1.gif) 215 216这里写图片描述 217 218**3.3 增加一些基本通用功能** 219 220最后我们在这个基础在增加一些基本的功能,如正在加载数据的页面,或者数据是空的页面,所以决定找一层最合适的方法去改,那就是我们上一期的WrapRecyclerView的基础上去改,因为那是我们Adapter密切联系的一层。 221 222``` 223/** 224 * Created by Darren on 2016/12/29. 225 * Email: 240336124@qq.com 226 * Description: 可以添加头部和底部的RecyclerView 227 */ 228public class WrapRecyclerView extends RecyclerView { 229 // 增加一些通用功能 230 // 空列表数据应该显示的空View 231 // 正在加载数据页面,也就是正在获取后台接口页面 232 private View mEmptyView, mLoadingView; 233 234 // 省略...上一期已有代码 235 236 private AdapterDataObserver mDataObserver = new AdapterDataObserver() { 237 @Override 238 public void onChanged() { 239 if (mAdapter == null) return; 240 // 观察者 列表Adapter更新 包裹的也需要更新不然列表的notifyDataSetChanged没效果 241 if (mWrapRecyclerAdapter != mAdapter) 242 mWrapRecyclerAdapter.notifyDataSetChanged(); 243 244 dataChanged(); 245 } 246 247 @Override 248 public void onItemRangeRemoved(int positionStart, int itemCount) { 249 if (mAdapter == null) return; 250 // 观察者 列表Adapter更新 包裹的也需要更新不然列表的notifyDataSetChanged没效果 251 if (mWrapRecyclerAdapter != mAdapter) 252 mWrapRecyclerAdapter.notifyItemRemoved(positionStart); 253 dataChanged(); 254 } 255 256 @Override 257 public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { 258 if (mAdapter == null) return; 259 // 观察者 列表Adapter更新 包裹的也需要更新不然列表的notifyItemMoved没效果 260 if (mWrapRecyclerAdapter != mAdapter) 261 mWrapRecyclerAdapter.notifyItemMoved(fromPosition, toPosition); 262 dataChanged(); 263 } 264 265 @Override 266 public void onItemRangeChanged(int positionStart, int itemCount) { 267 if (mAdapter == null) return; 268 // 观察者 列表Adapter更新 包裹的也需要更新不然列表的notifyItemChanged没效果 269 if (mWrapRecyclerAdapter != mAdapter) 270 mWrapRecyclerAdapter.notifyItemChanged(positionStart); 271 dataChanged(); 272 } 273 274 @Override 275 public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { 276 if (mAdapter == null) return; 277 // 观察者 列表Adapter更新 包裹的也需要更新不然列表的notifyItemChanged没效果 278 if (mWrapRecyclerAdapter != mAdapter) 279 mWrapRecyclerAdapter.notifyItemChanged(positionStart, payload); 280 dataChanged(); 281 } 282 283 @Override 284 public void onItemRangeInserted(int positionStart, int itemCount) { 285 if (mAdapter == null) return; 286 // 观察者 列表Adapter更新 包裹的也需要更新不然列表的notifyItemInserted没效果 287 if (mWrapRecyclerAdapter != mAdapter) 288 mWrapRecyclerAdapter.notifyItemInserted(positionStart); 289 dataChanged(); 290 } 291 }; 292 293 /** 294 * 添加一个空列表数据页面 295 */ 296 public void addEmptyView(View emptyView) { 297 this.mEmptyView = emptyView; 298 } 299 300 /** 301 * 添加一个正在加载数据的页面 302 */ 303 public void addLoadingView(View loadingView) { 304 this.mLoadingView = loadingView; 305 } 306 307 /** 308 * Adapter数据改变的方法 309 */ 310 private void dataChanged() { 311 if (mAdapter.getItemCount() == 0) { 312 // 没有数据 313 if (mEmptyView != null) { 314 mEmptyView.setVisibility(VISIBLE); 315 } else { 316 mEmptyView.setVisibility(GONE); 317 } 318 } 319 } 320 // 省略...上一期已有代码 321} 322``` 323 324上一期的代码就已经省略了[RecyclerView更全解析之 - 为它优雅的添加头部和底部](https://link.jianshu.com/?t=http://blog.csdn.net/z240336124/article/details/5393925081),到目前应该所有的这些列表刷新和加载样式都可以实现,具体的一些要求可以自己修改修改。我这里就不在把它使用到具体的项目中了,我自己也用到了自己的项目中,之所以之前没写这一期的博客是因为在使用的过程中出现了一些Bug,所以才等到这个时候。 325 326![](https://img-hello-world.oss-cn-beijing.aliyuncs.com/e4289e2972781ff2643f2c6c5a9d02d7.gif) 327 328这里写图片描述 329 330所有分享大纲:[Android进阶之旅 - 自定义View篇](https://www.jianshu.com/p/b272528165a2) 331 332视频讲解地址:[http://pan.baidu.com/s/1kUGvvwj](https://link.jianshu.com/?t=http://pan.baidu.com/s/1kUGvvwj) 333 334
点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

Django之Django模板

1、问:html页面从数据库中读出DateTimeField字段时,显示的时间格式和数据库中存放的格式不一致,比如数据库字段内容为2012082616:00:00,但是页面显示的却是Aug.26,2012,4p.m.答:为了页面和数据库中显示一致,需要在页面格式化时间,需要添加<td{{dayrecord.p\_time|date: