Android高级UI开发 RecyclerView高级进阶(八)下拉刷新,上拉加载

举报
yd_57386892 发表于 2020/12/29 00:13:35 2020/12/29
【摘要】 Listview的下拉刷新,上拉加载我们常用的框架是pullToRefresh。今天我们来介绍RecyclerView如何实现下拉刷新,上拉加载。  源码下载: 源码 1. 下拉刷新 实现思路,无需我们写代码自定义下拉刷新控件,直接使用MaterialDesign5.0为我们提供的SwipeRefreshLayout控件。 布局一般为: <android.supp...

Listview的下拉刷新,上拉加载我们常用的框架是pullToRefresh。今天我们来介绍RecyclerView如何实现下拉刷新,上拉加载。

 源码下载: 源码

1. 下拉刷新

实现思路,无需我们写代码自定义下拉刷新控件,直接使用MaterialDesign5.0为我们提供的SwipeRefreshLayout控件。

布局一般为:


  
  1. <android.support.v4.widget.SwipeRefreshLayout
  2. android:id="@+id/refresh_layout"
  3. android:layout_width="match_parent"
  4. android:layout_height="wrap_content"
  5. app:layout_behavior="@string/appbar_scrolling_view_behavior">
  6. <com.yanzhenjie.recyclerview.swipe.SwipeMenuRecyclerView
  7. android:id="@+id/recycler_view"
  8. android:layout_width="match_parent"
  9. android:layout_height="wrap_content"/>
  10. </android.support.v4.widget.SwipeRefreshLayout>

 

2. 上拉加载

 

实现思路, 需要我们自己写代码来实现。在这里我们先贴出MainActivity的代码(使用RecyclerView控件的核心代码)


  
  1. /*
  2. * Copyright 2017 Yan Zhenjie
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.anyikang.volunteer.sos.recyclerview22.activity.load;
  17. import android.os.Bundle;
  18. import android.support.annotation.Nullable;
  19. import android.support.v4.content.ContextCompat;
  20. import android.support.v4.widget.SwipeRefreshLayout;
  21. import android.support.v7.app.ActionBar;
  22. import android.support.v7.app.AppCompatActivity;
  23. import android.support.v7.widget.LinearLayoutManager;
  24. import android.support.v7.widget.Toolbar;
  25. import android.view.MenuItem;
  26. import android.view.View;
  27. import android.widget.Toast;
  28. import com.anyikang.volunteer.sos.recyclerview22.R;
  29. import com.anyikang.volunteer.sos.recyclerview22.adapter.MainAdapter;
  30. import com.yanzhenjie.recyclerview.swipe.SwipeItemClickListener;
  31. import com.yanzhenjie.recyclerview.swipe.SwipeMenuRecyclerView;
  32. import com.yanzhenjie.recyclerview.swipe.widget.DefaultItemDecoration;
  33. import java.util.ArrayList;
  34. import java.util.List;
  35. /**
  36. * <p>
  37. * 默认的加载更多的View。
  38. * </p>
  39. *
  40. */
  41. public class DefaultActivity extends AppCompatActivity {
  42. private SwipeRefreshLayout mRefreshLayout;
  43. private SwipeMenuRecyclerView mRecyclerView;
  44. private MainAdapter mAdapter;
  45. private List<String> mDataList;
  46. @Override
  47. protected void onCreate(@Nullable Bundle savedInstanceState) {
  48. super.onCreate(savedInstanceState);
  49. setContentView(R.layout.activity_refresh_loadmore);
  50. Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
  51. setSupportActionBar(toolbar);
  52. ActionBar actionBar = getSupportActionBar();
  53. assert actionBar != null;
  54. actionBar.setDisplayHomeAsUpEnabled(true);
  55. mRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.refresh_layout);
  56. mRefreshLayout.setOnRefreshListener(mRefreshListener); // 刷新监听。
  57. mRecyclerView = (SwipeMenuRecyclerView) findViewById(R.id.recycler_view);
  58. // mRecyclerView.setLayoutManager(new GridLayoutManager(this, 2));
  59. mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
  60. mRecyclerView.addItemDecoration(new DefaultItemDecoration(ContextCompat.getColor(this, R.color.divider_color)));
  61. mRecyclerView.setSwipeItemClickListener(mItemClickListener); // RecyclerView Item点击监听。
  62. mRecyclerView.useDefaultLoadMore(); // 使用默认的加载更多的View。
  63. mRecyclerView.setLoadMoreListener(mLoadMoreListener); // 加载更多的监听。
  64. mAdapter = new MainAdapter(this);
  65. mRecyclerView.setAdapter(mAdapter);
  66. // 请求服务器加载数据。
  67. loadData();
  68. }
  69. /**
  70. * 刷新。
  71. */
  72. private SwipeRefreshLayout.OnRefreshListener mRefreshListener = new SwipeRefreshLayout.OnRefreshListener() {
  73. @Override
  74. public void onRefresh() {
  75. mRecyclerView.postDelayed(new Runnable() {
  76. @Override
  77. public void run() {
  78. loadData();
  79. }
  80. }, 1000); // 延时模拟请求服务器。
  81. }
  82. };
  83. /**
  84. * 加载更多。
  85. */
  86. private SwipeMenuRecyclerView.LoadMoreListener mLoadMoreListener = new SwipeMenuRecyclerView.LoadMoreListener() {
  87. @Override
  88. public void onLoadMore() {
  89. mRecyclerView.postDelayed(new Runnable() {
  90. @Override
  91. public void run() {
  92. List<String> strings = createDataList(mAdapter.getItemCount());
  93. mDataList.addAll(strings);
  94. // notifyItemRangeInserted()或者notifyDataSetChanged().
  95. mAdapter.notifyItemRangeInserted(mDataList.size() - strings.size(), strings.size());
  96. // 数据完更多数据,一定要掉用这个方法。
  97. // 第一个参数:表示此次数据是否为空。
  98. // 第二个参数:表示是否还有更多数据。
  99. mRecyclerView.loadMoreFinish(false, true);
  100. // 如果加载失败调用下面的方法,传入errorCode和errorMessage。
  101. // errorCode随便传,你自定义LoadMoreView时可以根据errorCode判断错误类型。
  102. // errorMessage是会显示到loadMoreView上的,用户可以看到。
  103. // mRecyclerView.loadMoreError(0, "请求网络失败");
  104. }
  105. }, 1000);
  106. }
  107. };
  108. /**
  109. * Item点击监听。
  110. */
  111. private SwipeItemClickListener mItemClickListener = new SwipeItemClickListener() {
  112. @Override
  113. public void onItemClick(View itemView, int position) {
  114. Toast.makeText(DefaultActivity.this, "第" + position + "个", Toast.LENGTH_SHORT).show();
  115. }
  116. };
  117. /**
  118. * 第一次加载数据。
  119. */
  120. private void loadData() {
  121. mDataList = createDataList(0);
  122. mAdapter.notifyDataSetChanged(mDataList);
  123. mRefreshLayout.setRefreshing(false);
  124. // 第一次加载数据:一定要调用这个方法,否则不会触发加载更多。
  125. // 第一个参数:表示此次数据是否为空,假如你请求到的list为空(== null || list.size == 0),那么这里就要true。
  126. // 第二个参数:表示是否还有更多数据,根据服务器返回给你的page等信息判断是否还有更多,这样可以提供性能,如果不能判断则传true。
  127. mRecyclerView.loadMoreFinish(false, true);
  128. }
  129. protected List<String> createDataList(int start) {
  130. List<String> strings = new ArrayList<>();
  131. for (int i = start; i < start + 100; i++) {
  132. strings.add("第" + i + "个Item");
  133. }
  134. return strings;
  135. }
  136. @Override
  137. public boolean onOptionsItemSelected(MenuItem item) {
  138. if (item.getItemId() == android.R.id.home) {
  139. finish();
  140. }
  141. return true;
  142. }
  143. }

2.1分析代码

SwipeMenuRecyclerView mRecyclerView
 

从这个声明可以看出我们需要扩展一个自定义RecyclerView控件。

mRecyclerView.setLoadMoreListener(mLoadMoreListener); // 加载更多的监听
 

这行代码说明“上拉加载更多”这个事件的触发一定是在自定义RecyclerView里,即SwipeMenuRecyclerView。

而“上拉加载更多”这个事件触发后的 处理或者说响应代码是在mLoadMoreListener这个接口的实例对象里。

综上分析,主要看以下2个部分

(1)SwipeMenuRecyclerView这个自定义RecyclerView的代码,看其“上拉加载”事件是如何触发的。

(2)mLoadMoreListener接口的实现代码,已经在MainActivity里展示过了,mLoadMoreListener实现了onLoadMore函数。也就说在SwipeMenuRecyclerView里“上拉加载”事件触发时一定会回调MainActivity中的mLoadMoreListener的onLoadMore函数。

在这里我们贴出SwipeMenuRecyclerView的代码,分析一下“上拉加载”事件是如何触发的。

SwipeMenuRecyclerView.java:


  
  1. /*
  2. * Copyright 2016 Yan Zhenjie
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.yanzhenjie.recyclerview.swipe;
  17. import android.content.Context;
  18. import android.support.annotation.IntDef;
  19. import android.support.v7.widget.GridLayoutManager;
  20. import android.support.v7.widget.LinearLayoutManager;
  21. import android.support.v7.widget.RecyclerView;
  22. import android.support.v7.widget.StaggeredGridLayoutManager;
  23. import android.util.AttributeSet;
  24. import android.view.MotionEvent;
  25. import android.view.View;
  26. import android.view.ViewConfiguration;
  27. import android.view.ViewGroup;
  28. import android.view.ViewParent;
  29. import com.yanzhenjie.recyclerview.swipe.touch.DefaultItemTouchHelper;
  30. import com.yanzhenjie.recyclerview.swipe.touch.OnItemMoveListener;
  31. import com.yanzhenjie.recyclerview.swipe.touch.OnItemMovementListener;
  32. import com.yanzhenjie.recyclerview.swipe.touch.OnItemStateChangedListener;
  33. import com.yanzhenjie.recyclerview.swipe.widget.DefaultLoadMoreView;
  34. import java.lang.annotation.Retention;
  35. import java.lang.annotation.RetentionPolicy;
  36. import java.util.ArrayList;
  37. import java.util.List;
  38. /**
  39. * Created by
  40. */
  41. public class SwipeMenuRecyclerView extends RecyclerView {
  42. /**
  43. * Left menu.
  44. */
  45. public static final int LEFT_DIRECTION = 1;
  46. /**
  47. * Right menu.
  48. */
  49. public static final int RIGHT_DIRECTION = -1;
  50. @IntDef({LEFT_DIRECTION, RIGHT_DIRECTION})
  51. @Retention(RetentionPolicy.SOURCE)
  52. public @interface DirectionMode {
  53. }
  54. /**
  55. * Invalid position.
  56. */
  57. private static final int INVALID_POSITION = -1;
  58. protected int mScaleTouchSlop;
  59. protected SwipeMenuLayout mOldSwipedLayout;
  60. protected int mOldTouchedPosition = INVALID_POSITION;
  61. private int mDownX;
  62. private int mDownY;
  63. private boolean allowSwipeDelete = false;
  64. private DefaultItemTouchHelper mDefaultItemTouchHelper;
  65. private SwipeMenuCreator mSwipeMenuCreator;
  66. private SwipeMenuItemClickListener mSwipeMenuItemClickListener;
  67. private SwipeItemClickListener mSwipeItemClickListener;
  68. private SwipeItemLongClickListener mSwipeItemLongClickListener;
  69. private SwipeAdapterWrapper mAdapterWrapper;
  70. public SwipeMenuRecyclerView(Context context) {
  71. this(context, null);
  72. }
  73. public SwipeMenuRecyclerView(Context context, AttributeSet attrs) {
  74. this(context, attrs, 0);
  75. }
  76. public SwipeMenuRecyclerView(Context context, AttributeSet attrs, int defStyle) {
  77. super(context, attrs, defStyle);
  78. mScaleTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
  79. }
  80. private void initializeItemTouchHelper() {
  81. if (mDefaultItemTouchHelper == null) {
  82. mDefaultItemTouchHelper = new DefaultItemTouchHelper();
  83. mDefaultItemTouchHelper.attachToRecyclerView(this);
  84. }
  85. }
  86. /**
  87. * Set OnItemMoveListener.
  88. *
  89. * @param onItemMoveListener {@link OnItemMoveListener}.
  90. */
  91. public void setOnItemMoveListener(OnItemMoveListener onItemMoveListener) {
  92. initializeItemTouchHelper();
  93. this.mDefaultItemTouchHelper.setOnItemMoveListener(onItemMoveListener);
  94. }
  95. /**
  96. * Set OnItemMovementListener.
  97. *
  98. * @param onItemMovementListener {@link OnItemMovementListener}.
  99. */
  100. public void setOnItemMovementListener(OnItemMovementListener onItemMovementListener) {
  101. initializeItemTouchHelper();
  102. this.mDefaultItemTouchHelper.setOnItemMovementListener(onItemMovementListener);
  103. }
  104. /**
  105. * Set OnItemStateChangedListener.
  106. *
  107. * @param onItemStateChangedListener {@link OnItemStateChangedListener}.
  108. */
  109. public void setOnItemStateChangedListener(OnItemStateChangedListener onItemStateChangedListener) {
  110. initializeItemTouchHelper();
  111. this.mDefaultItemTouchHelper.setOnItemStateChangedListener(onItemStateChangedListener);
  112. }
  113. /**
  114. * Set can long press drag.
  115. *
  116. * @param canDrag drag true, otherwise is can't.
  117. */
  118. public void setLongPressDragEnabled(boolean canDrag) {
  119. initializeItemTouchHelper();
  120. this.mDefaultItemTouchHelper.setLongPressDragEnabled(canDrag);
  121. }
  122. /**
  123. * Get can long press drag.
  124. *
  125. * @return drag true, otherwise is can't.
  126. */
  127. public boolean isLongPressDragEnabled() {
  128. initializeItemTouchHelper();
  129. return this.mDefaultItemTouchHelper.isLongPressDragEnabled();
  130. }
  131. /**
  132. * Set can swipe delete.
  133. *
  134. * @param canSwipe swipe true, otherwise is can't.
  135. */
  136. public void setItemViewSwipeEnabled(boolean canSwipe) {
  137. initializeItemTouchHelper();
  138. allowSwipeDelete = canSwipe; // swipe and menu conflict.
  139. this.mDefaultItemTouchHelper.setItemViewSwipeEnabled(canSwipe);
  140. }
  141. /**
  142. * Get can long press swipe.
  143. *
  144. * @return swipe true, otherwise is can't.
  145. */
  146. public boolean isItemViewSwipeEnabled() {
  147. initializeItemTouchHelper();
  148. return this.mDefaultItemTouchHelper.isItemViewSwipeEnabled();
  149. }
  150. /**
  151. * Start drag a item.
  152. *
  153. * @param viewHolder the ViewHolder to start dragging. It must be a direct child of RecyclerView.
  154. */
  155. public void startDrag(RecyclerView.ViewHolder viewHolder) {
  156. initializeItemTouchHelper();
  157. this.mDefaultItemTouchHelper.startDrag(viewHolder);
  158. }
  159. /**
  160. * Star swipe a item.
  161. *
  162. * @param viewHolder the ViewHolder to start swiping. It must be a direct child of RecyclerView.
  163. */
  164. public void startSwipe(RecyclerView.ViewHolder viewHolder) {
  165. initializeItemTouchHelper();
  166. this.mDefaultItemTouchHelper.startSwipe(viewHolder);
  167. }
  168. /**
  169. * Check the Adapter and throw an exception if it already exists.
  170. */
  171. private void checkAdapterExist(String message) {
  172. if (mAdapterWrapper != null)
  173. throw new IllegalStateException(message);
  174. }
  175. /**
  176. * Set item click listener.
  177. */
  178. public void setSwipeItemClickListener(SwipeItemClickListener itemClickListener) {
  179. if (itemClickListener == null) return;
  180. checkAdapterExist("Cannot set item click listener, setAdapter has already been called.");
  181. this.mSwipeItemClickListener = new ItemClick(this, itemClickListener);
  182. }
  183. private static class ItemClick implements SwipeItemClickListener {
  184. private SwipeMenuRecyclerView mRecyclerView;
  185. private SwipeItemClickListener mCallback;
  186. public ItemClick(SwipeMenuRecyclerView recyclerView, SwipeItemClickListener callback) {
  187. this.mRecyclerView = recyclerView;
  188. this.mCallback = callback;
  189. }
  190. @Override
  191. public void onItemClick(View itemView, int position) {
  192. position = position - mRecyclerView.getHeaderItemCount();
  193. if (position >= 0)
  194. mCallback.onItemClick(itemView, position);
  195. }
  196. }
  197. /**
  198. * Set item click listener.
  199. */
  200. public void setSwipeItemLongClickListener(SwipeItemLongClickListener itemLongClickListener) {
  201. if (itemLongClickListener == null) return;
  202. checkAdapterExist("Cannot set item long click listener, setAdapter has already been called.");
  203. this.mSwipeItemLongClickListener = new ItemLongClick(this, itemLongClickListener);
  204. }
  205. private static class ItemLongClick implements SwipeItemLongClickListener {
  206. private SwipeMenuRecyclerView mRecyclerView;
  207. private SwipeItemLongClickListener mCallback;
  208. public ItemLongClick(SwipeMenuRecyclerView recyclerView, SwipeItemLongClickListener callback) {
  209. this.mRecyclerView = recyclerView;
  210. this.mCallback = callback;
  211. }
  212. @Override
  213. public void onItemLongClick(View itemView, int position) {
  214. position = position - mRecyclerView.getHeaderItemCount();
  215. if (position >= 0)
  216. mCallback.onItemLongClick(itemView, position);
  217. }
  218. }
  219. /**
  220. * Set to create menu listener.
  221. */
  222. public void setSwipeMenuCreator(SwipeMenuCreator menuCreator) {
  223. if (menuCreator == null) return;
  224. checkAdapterExist("Cannot set menu creator, setAdapter has already been called.");
  225. this.mSwipeMenuCreator = menuCreator;
  226. }
  227. /**
  228. * Set to click menu listener.
  229. */
  230. public void setSwipeMenuItemClickListener(SwipeMenuItemClickListener menuItemClickListener) {
  231. if (menuItemClickListener == null) return;
  232. checkAdapterExist("Cannot set menu item click listener, setAdapter has already been called.");
  233. this.mSwipeMenuItemClickListener = new MenuItemClick(this, menuItemClickListener);
  234. }
  235. private static class MenuItemClick implements SwipeMenuItemClickListener {
  236. private SwipeMenuRecyclerView mRecyclerView;
  237. private SwipeMenuItemClickListener mCallback;
  238. public MenuItemClick(SwipeMenuRecyclerView recyclerView, SwipeMenuItemClickListener callback) {
  239. this.mRecyclerView = recyclerView;
  240. this.mCallback = callback;
  241. }
  242. @Override
  243. public void onItemClick(SwipeMenuBridge menuBridge) {
  244. int position = menuBridge.getAdapterPosition();
  245. position = position - mRecyclerView.getHeaderItemCount();
  246. if (position >= 0) {
  247. menuBridge.mAdapterPosition = position;
  248. mCallback.onItemClick(menuBridge);
  249. }
  250. }
  251. }
  252. @Override
  253. public void setLayoutManager(LayoutManager layoutManager) {
  254. if (layoutManager instanceof GridLayoutManager) {
  255. final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
  256. final GridLayoutManager.SpanSizeLookup spanSizeLookupHolder = gridLayoutManager.getSpanSizeLookup();
  257. gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
  258. @Override
  259. public int getSpanSize(int position) {
  260. if (mAdapterWrapper.isHeaderView(position) || mAdapterWrapper.isFooterView(position)) {
  261. return gridLayoutManager.getSpanCount();
  262. }
  263. if (spanSizeLookupHolder != null)
  264. return spanSizeLookupHolder.getSpanSize(position - getHeaderItemCount());
  265. return 1;
  266. }
  267. });
  268. }
  269. super.setLayoutManager(layoutManager);
  270. }
  271. /**
  272. * Get the original adapter.
  273. */
  274. public Adapter getOriginAdapter() {
  275. if (mAdapterWrapper == null) return null;
  276. return mAdapterWrapper.getOriginAdapter();
  277. }
  278. @Override
  279. public void setAdapter(Adapter adapter) {
  280. if (mAdapterWrapper != null) {
  281. mAdapterWrapper.getOriginAdapter().unregisterAdapterDataObserver(mAdapterDataObserver);
  282. }
  283. if (adapter == null) {
  284. mAdapterWrapper = null;
  285. } else {
  286. adapter.registerAdapterDataObserver(mAdapterDataObserver);
  287. mAdapterWrapper = new SwipeAdapterWrapper(getContext(), adapter);
  288. mAdapterWrapper.setSwipeItemClickListener(mSwipeItemClickListener);
  289. mAdapterWrapper.setSwipeItemLongClickListener(mSwipeItemLongClickListener);
  290. mAdapterWrapper.setSwipeMenuCreator(mSwipeMenuCreator);
  291. mAdapterWrapper.setSwipeMenuItemClickListener(mSwipeMenuItemClickListener);
  292. if (mHeaderViewList.size() > 0) {
  293. for (View view : mHeaderViewList) {
  294. mAdapterWrapper.addHeaderView(view);
  295. }
  296. }
  297. if (mFooterViewList.size() > 0) {
  298. for (View view : mFooterViewList) {
  299. mAdapterWrapper.addFooterView(view);
  300. }
  301. }
  302. }
  303. super.setAdapter(mAdapterWrapper);
  304. }
  305. private AdapterDataObserver mAdapterDataObserver = new AdapterDataObserver() {
  306. @Override
  307. public void onChanged() {
  308. mAdapterWrapper.notifyDataSetChanged();
  309. }
  310. @Override
  311. public void onItemRangeChanged(int positionStart, int itemCount) {
  312. positionStart += getHeaderItemCount();
  313. mAdapterWrapper.notifyItemRangeChanged(positionStart, itemCount);
  314. }
  315. @Override
  316. public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
  317. positionStart += getHeaderItemCount();
  318. mAdapterWrapper.notifyItemRangeChanged(positionStart, itemCount, payload);
  319. }
  320. @Override
  321. public void onItemRangeInserted(int positionStart, int itemCount) {
  322. positionStart += getHeaderItemCount();
  323. mAdapterWrapper.notifyItemRangeInserted(positionStart, itemCount);
  324. }
  325. @Override
  326. public void onItemRangeRemoved(int positionStart, int itemCount) {
  327. positionStart += getHeaderItemCount();
  328. mAdapterWrapper.notifyItemRangeRemoved(positionStart, itemCount);
  329. }
  330. @Override
  331. public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
  332. fromPosition += getHeaderItemCount();
  333. toPosition += getHeaderItemCount();
  334. mAdapterWrapper.notifyItemMoved(fromPosition, toPosition);
  335. }
  336. };
  337. private List<View> mHeaderViewList = new ArrayList<>();
  338. private List<View> mFooterViewList = new ArrayList<>();
  339. /**
  340. * Add view at the headers.
  341. */
  342. public void addHeaderView(View view) {
  343. mHeaderViewList.add(view);
  344. if (mAdapterWrapper != null) {
  345. mAdapterWrapper.addHeaderViewAndNotify(view);
  346. }
  347. }
  348. /**
  349. * Remove view from header.
  350. */
  351. public void removeHeaderView(View view) {
  352. mHeaderViewList.remove(view);
  353. if (mAdapterWrapper != null) {
  354. mAdapterWrapper.removeHeaderViewAndNotify(view);
  355. }
  356. }
  357. /**
  358. * Add view at the footer.
  359. */
  360. public void addFooterView(View view) {
  361. mFooterViewList.add(view);
  362. if (mAdapterWrapper != null) {
  363. mAdapterWrapper.addFooterViewAndNotify(view);
  364. }
  365. }
  366. public void removeFooterView(View view) {
  367. mFooterViewList.remove(view);
  368. if (mAdapterWrapper != null) {
  369. mAdapterWrapper.removeFooterViewAndNotify(view);
  370. }
  371. }
  372. /**
  373. * Get size of headers.
  374. */
  375. public int getHeaderItemCount() {
  376. if (mAdapterWrapper == null) return 0;
  377. return mAdapterWrapper.getHeaderItemCount();
  378. }
  379. /**
  380. * Get size of footer.
  381. */
  382. public int getFooterItemCount() {
  383. if (mAdapterWrapper == null) return 0;
  384. return mAdapterWrapper.getFooterItemCount();
  385. }
  386. /**
  387. * Get ViewType of item.
  388. */
  389. public int getItemViewType(int position) {
  390. if (mAdapterWrapper == null) return 0;
  391. return mAdapterWrapper.getItemViewType(position);
  392. }
  393. /**
  394. * open menu on left.
  395. *
  396. * @param position position.
  397. */
  398. public void smoothOpenLeftMenu(int position) {
  399. smoothOpenMenu(position, LEFT_DIRECTION, SwipeMenuLayout.DEFAULT_SCROLLER_DURATION);
  400. }
  401. /**
  402. * open menu on left.
  403. *
  404. * @param position position.
  405. * @param duration time millis.
  406. */
  407. public void smoothOpenLeftMenu(int position, int duration) {
  408. smoothOpenMenu(position, LEFT_DIRECTION, duration);
  409. }
  410. /**
  411. * open menu on right.
  412. *
  413. * @param position position.
  414. */
  415. public void smoothOpenRightMenu(int position) {
  416. smoothOpenMenu(position, RIGHT_DIRECTION, SwipeMenuLayout.DEFAULT_SCROLLER_DURATION);
  417. }
  418. /**
  419. * open menu on right.
  420. *
  421. * @param position position.
  422. * @param duration time millis.
  423. */
  424. public void smoothOpenRightMenu(int position, int duration) {
  425. smoothOpenMenu(position, RIGHT_DIRECTION, duration);
  426. }
  427. /**
  428. * open menu.
  429. *
  430. * @param position position.
  431. * @param direction use {@link #LEFT_DIRECTION}, {@link #RIGHT_DIRECTION}.
  432. * @param duration time millis.
  433. */
  434. public void smoothOpenMenu(int position, @DirectionMode int direction, int duration) {
  435. if (mOldSwipedLayout != null) {
  436. if (mOldSwipedLayout.isMenuOpen()) {
  437. mOldSwipedLayout.smoothCloseMenu();
  438. }
  439. }
  440. position += getHeaderItemCount();
  441. ViewHolder vh = findViewHolderForAdapterPosition(position);
  442. if (vh != null) {
  443. View itemView = getSwipeMenuView(vh.itemView);
  444. if (itemView instanceof SwipeMenuLayout) {
  445. mOldSwipedLayout = (SwipeMenuLayout) itemView;
  446. if (direction == RIGHT_DIRECTION) {
  447. mOldTouchedPosition = position;
  448. mOldSwipedLayout.smoothOpenRightMenu(duration);
  449. } else if (direction == LEFT_DIRECTION) {
  450. mOldTouchedPosition = position;
  451. mOldSwipedLayout.smoothOpenLeftMenu(duration);
  452. }
  453. }
  454. }
  455. }
  456. /**
  457. * Close menu.
  458. */
  459. public void smoothCloseMenu() {
  460. if (mOldSwipedLayout != null && mOldSwipedLayout.isMenuOpen()) {
  461. mOldSwipedLayout.smoothCloseMenu();
  462. }
  463. }
  464. @Override
  465. public boolean onInterceptTouchEvent(MotionEvent e) {
  466. boolean isIntercepted = super.onInterceptTouchEvent(e);
  467. if (allowSwipeDelete) // swipe and menu conflict.
  468. return isIntercepted;
  469. else {
  470. if (e.getPointerCount() > 1) return true;
  471. int action = e.getAction();
  472. int x = (int) e.getX();
  473. int y = (int) e.getY();
  474. switch (action) {
  475. case MotionEvent.ACTION_DOWN: {
  476. mDownX = x;
  477. mDownY = y;
  478. isIntercepted = false;
  479. int touchingPosition = getChildAdapterPosition(findChildViewUnder(x, y));
  480. if (touchingPosition != mOldTouchedPosition && mOldSwipedLayout != null && mOldSwipedLayout.isMenuOpen()) {
  481. mOldSwipedLayout.smoothCloseMenu();
  482. isIntercepted = true;
  483. }
  484. if (isIntercepted) {
  485. mOldSwipedLayout = null;
  486. mOldTouchedPosition = INVALID_POSITION;
  487. } else {
  488. ViewHolder vh = findViewHolderForAdapterPosition(touchingPosition);
  489. if (vh != null) {
  490. View itemView = getSwipeMenuView(vh.itemView);
  491. if (itemView instanceof SwipeMenuLayout) {
  492. mOldSwipedLayout = (SwipeMenuLayout) itemView;
  493. mOldTouchedPosition = touchingPosition;
  494. }
  495. }
  496. }
  497. break;
  498. }
  499. // They are sensitive to retain sliding and inertia.
  500. case MotionEvent.ACTION_MOVE: {
  501. isIntercepted = handleUnDown(x, y, isIntercepted);
  502. if (mOldSwipedLayout == null) break;
  503. ViewParent viewParent = getParent();
  504. if (viewParent == null) break;
  505. int disX = mDownX - x;
  506. // 向左滑,显示右侧菜单,或者关闭左侧菜单。
  507. boolean showRightCloseLeft = disX > 0 && (mOldSwipedLayout.hasRightMenu() || mOldSwipedLayout.isLeftCompleteOpen());
  508. // 向右滑,显示左侧菜单,或者关闭右侧菜单。
  509. boolean showLeftCloseRight = disX < 0 && (mOldSwipedLayout.hasLeftMenu() || mOldSwipedLayout.isRightCompleteOpen());
  510. viewParent.requestDisallowInterceptTouchEvent(showRightCloseLeft || showLeftCloseRight);
  511. }
  512. case MotionEvent.ACTION_UP:
  513. case MotionEvent.ACTION_CANCEL: {
  514. isIntercepted = handleUnDown(x, y, isIntercepted);
  515. break;
  516. }
  517. }
  518. }
  519. return isIntercepted;
  520. }
  521. private boolean handleUnDown(int x, int y, boolean defaultValue) {
  522. int disX = mDownX - x;
  523. int disY = mDownY - y;
  524. // swipe
  525. if (Math.abs(disX) > mScaleTouchSlop && Math.abs(disX) > Math.abs(disY))
  526. return false;
  527. // click
  528. if (Math.abs(disY) < mScaleTouchSlop && Math.abs(disX) < mScaleTouchSlop)
  529. return false;
  530. return defaultValue;
  531. }
  532. @Override
  533. public boolean onTouchEvent(MotionEvent e) {
  534. int action = e.getAction();
  535. switch (action) {
  536. case MotionEvent.ACTION_DOWN:
  537. break;
  538. case MotionEvent.ACTION_MOVE:
  539. if (mOldSwipedLayout != null && mOldSwipedLayout.isMenuOpen()) {
  540. mOldSwipedLayout.smoothCloseMenu();
  541. }
  542. break;
  543. case MotionEvent.ACTION_UP:
  544. break;
  545. case MotionEvent.ACTION_CANCEL:
  546. break;
  547. }
  548. return super.onTouchEvent(e);
  549. }
  550. private View getSwipeMenuView(View itemView) {
  551. if (itemView instanceof SwipeMenuLayout) return itemView;
  552. List<View> unvisited = new ArrayList<>();
  553. unvisited.add(itemView);
  554. while (!unvisited.isEmpty()) {
  555. View child = unvisited.remove(0);
  556. if (!(child instanceof ViewGroup)) { // view
  557. continue;
  558. }
  559. if (child instanceof SwipeMenuLayout) return child;
  560. ViewGroup group = (ViewGroup) child;
  561. final int childCount = group.getChildCount();
  562. for (int i = 0; i < childCount; i++) unvisited.add(group.getChildAt(i));
  563. }
  564. return itemView;
  565. }
  566. private int mScrollState = -1;
  567. private boolean isLoadMore = false;
  568. private boolean isAutoLoadMore = true;
  569. private boolean isLoadError = false;
  570. private boolean mDataEmpty = true;
  571. private boolean mHasMore = false;
  572. private LoadMoreView mLoadMoreView;
  573. private LoadMoreListener mLoadMoreListener;
  574. @Override
  575. public void onScrollStateChanged(int state) {
  576. this.mScrollState = state;
  577. }
  578. @Override
  579. public void onScrolled(int dx, int dy) {
  580. LayoutManager layoutManager = getLayoutManager();
  581. if (layoutManager != null && layoutManager instanceof LinearLayoutManager) {
  582. LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
  583. int itemCount = layoutManager.getItemCount();
  584. if (itemCount <= 0) return;
  585. int lastVisiblePosition = linearLayoutManager.findLastVisibleItemPosition();
  586. if (itemCount == lastVisiblePosition + 1 &&
  587. (mScrollState == SCROLL_STATE_DRAGGING || mScrollState == SCROLL_STATE_SETTLING)) {
  588. dispatchLoadMore();
  589. }
  590. } else if (layoutManager != null && layoutManager instanceof StaggeredGridLayoutManager) {
  591. StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
  592. int itemCount = layoutManager.getItemCount();
  593. if (itemCount <= 0) return;
  594. int[] lastVisiblePositionArray = staggeredGridLayoutManager.findLastCompletelyVisibleItemPositions(null);
  595. int lastVisiblePosition = lastVisiblePositionArray[lastVisiblePositionArray.length - 1];
  596. if (itemCount == lastVisiblePosition + 1 &&
  597. (mScrollState == SCROLL_STATE_DRAGGING || mScrollState == SCROLL_STATE_SETTLING)) {
  598. dispatchLoadMore();
  599. }
  600. }
  601. }
  602. private void dispatchLoadMore() {
  603. if (isLoadError) return;
  604. if (!isAutoLoadMore) {
  605. if (mLoadMoreView != null)
  606. mLoadMoreView.onWaitToLoadMore(mLoadMoreListener);
  607. } else {
  608. if (isLoadMore || mDataEmpty || !mHasMore) return;
  609. isLoadMore = true;
  610. if (mLoadMoreView != null)
  611. mLoadMoreView.onLoading();
  612. if (mLoadMoreListener != null)
  613. mLoadMoreListener.onLoadMore();
  614. }
  615. }
  616. /**
  617. * Use the default to load more View.
  618. */
  619. public void useDefaultLoadMore() {
  620. DefaultLoadMoreView defaultLoadMoreView = new DefaultLoadMoreView(getContext());
  621. addFooterView(defaultLoadMoreView);
  622. setLoadMoreView(defaultLoadMoreView);
  623. }
  624. /**
  625. * Load more view.
  626. */
  627. public void setLoadMoreView(LoadMoreView loadMoreView) {
  628. mLoadMoreView = loadMoreView;
  629. }
  630. /**
  631. * Load more listener.
  632. */
  633. public void setLoadMoreListener(LoadMoreListener loadMoreListener) {
  634. mLoadMoreListener = loadMoreListener;
  635. }
  636. /**
  637. * Automatically load more automatically.
  638. * <p>
  639. * Non-auto-loading mode, you can to click on the item to load.
  640. * </p>
  641. *
  642. * @param autoLoadMore you can use false.
  643. * @see LoadMoreView#onWaitToLoadMore(LoadMoreListener)
  644. */
  645. public void setAutoLoadMore(boolean autoLoadMore) {
  646. isAutoLoadMore = autoLoadMore;
  647. }
  648. /**
  649. * Load more done.
  650. *
  651. * @param dataEmpty data is empty ?
  652. * @param hasMore has more data ?
  653. */
  654. public final void loadMoreFinish(boolean dataEmpty, boolean hasMore) {
  655. isLoadMore = false;
  656. isLoadError = false;
  657. mDataEmpty = dataEmpty;
  658. mHasMore = hasMore;
  659. if (mLoadMoreView != null) {
  660. mLoadMoreView.onLoadFinish(dataEmpty, hasMore);
  661. }
  662. }
  663. /**
  664. * Called when data is loaded incorrectly.
  665. *
  666. * @param errorCode Error code, will be passed to the LoadView, you can according to it to customize the prompt information.
  667. * @param errorMessage Error message.
  668. */
  669. public void loadMoreError(int errorCode, String errorMessage) {
  670. isLoadMore = false;
  671. isLoadError = true;
  672. if (mLoadMoreView != null) {
  673. mLoadMoreView.onLoadError(errorCode, errorMessage);
  674. }
  675. }
  676. public interface LoadMoreView {
  677. /**
  678. * Show progress.
  679. */
  680. void onLoading();
  681. /**
  682. * Load finish, handle result.
  683. */
  684. void onLoadFinish(boolean dataEmpty, boolean hasMore);
  685. /**
  686. * Non-auto-loading mode, you can to click on the item to load.
  687. */
  688. void onWaitToLoadMore(LoadMoreListener loadMoreListener);
  689. /**
  690. * Load error.
  691. */
  692. void onLoadError(int errorCode, String errorMessage);
  693. }
  694. public interface LoadMoreListener {
  695. /**
  696. * More data should be requested.
  697. */
  698. void onLoadMore();
  699. }
  700. }

我们在上述自定义RecyclerView的源码里搜索onLoadMore,发现是在dispatchLoadMore函数里调用了,好的,然后我们再搜索dispatchLoadMore是在哪里被调用了,发现是在RecyclerView的这个系统函数里调用的:


  
  1. @Override
  2. public void onScrolled(int dx, int dy) {
  3. LayoutManager layoutManager = getLayoutManager();
  4. if (layoutManager != null && layoutManager instanceof LinearLayoutManager) {
  5. LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
  6. int itemCount = layoutManager.getItemCount();
  7. if (itemCount <= 0) return;
  8. int lastVisiblePosition = linearLayoutManager.findLastVisibleItemPosition();
  9. if (itemCount == lastVisiblePosition + 1 &&
  10. (mScrollState == SCROLL_STATE_DRAGGING || mScrollState == SCROLL_STATE_SETTLING)) {
  11. dispatchLoadMore();
  12. }
  13. } ......

 

onScrolled这个函数是在RecyclerView控件滚动回调的。它有两个参数dx:水平滚动的距离,dy垂直滚动的距离,当然这里判断是否滚动到列表页底部不是用这两个参数判断的,在这里我们只是顺便提一下。主要是用下面这个if判断来判断页面是否滚动到了底部:

 

int lastVisiblePosition = linearLayoutManager.findLastVisibleItemPosition();
 

 


  
  1. if (itemCount == lastVisiblePosition + 1 &&
  2. (mScrollState == SCROLL_STATE_DRAGGING || mScrollState == SCROLL_STATE_SETTLING)) {
  3. dispatchLoadMore();
  4. }

当可见列表项的位置为最后一个列表项,且列表滚动状态为 DRAGGING(手指拖曳滚动)时 或 SCROLL_STATE_SETTING(列表惯性滚动)时,调用dispatchLoadMore()函数,从而调用了loadMore上拉加载函数,由于loadMore是在DefaultActivity(显示RecyclerView控件的主页面)里实现的接口函数,因此请求网络数据的代码在以下这个函数里处理的:


  
  1. /**
  2. * 加载更多。
  3. */
  4. private SwipeMenuRecyclerView.LoadMoreListener mLoadMoreListener = new SwipeMenuRecyclerView.LoadMoreListener() {
  5. @Override
  6. public void onLoadMore() {
  7. mRecyclerView.postDelayed(new Runnable() {
  8. @Override
  9. public void run() {
  10. List<String> strings = createDataList(mAdapter.getItemCount());
  11. mDataList.addAll(strings);
  12. // notifyItemRangeInserted()或者notifyDataSetChanged().
  13. mAdapter.notifyItemRangeInserted(mDataList.size() - strings.size(), strings.size());
  14. // 数据完更多数据,一定要掉用这个方法。
  15. // 第一个参数:表示此次数据是否为空。
  16. // 第二个参数:表示是否还有更多数据。
  17. mRecyclerView.loadMoreFinish(false, true);
  18. // 如果加载失败调用下面的方法,传入errorCode和errorMessage。
  19. // errorCode随便传,你自定义LoadMoreView时可以根据errorCode判断错误类型。
  20. // errorMessage是会显示到loadMoreView上的,用户可以看到。
  21. // mRecyclerView.loadMoreError(0, "请求网络失败");
  22. }
  23. }, 1000);
  24. }
  25. };

这个函数里我们模拟了列表数据的增加。

 

OK,至此下拉加载更多的原理和过程也就分析完了。我们来总结以下:

(1)上拉刷新,使用系统SwipeRefreshLayout控件就可以实现,无需自定义代码。

(2)下拉加载更多,需要扩展RecyclerView,并重写onScrolled函数,在滑动页面底部的时候调用dispatchLoadMore()---onLoadMore();

最后点击 源码

 

文章来源: blog.csdn.net,作者:冉航--小虾米,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/gaoxiaoweiandy/article/details/80963228

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。