需求
先上效果图, Material Design风格的下拉刷新和上拉加载更多。
源码地址(欢迎star) https://github.com/studychen/SeeNewsV2
如果对于RecyclerView还不熟悉,参见这篇 Android Material Design学习之RecyclerView代替 ListView
本文链接 http://blog.csdn.net/never_cxb/article/details/50759109 转载请注明出处
下拉刷新
效果图
上拉时候会有一个圆形动画,刷新加载数据。
思路
使用Google官方的android.support.v4.widget.SwipeRefreshLayout
列表RecyclerView的xml布局
给原来的RecyclerView增加一个SwipeRefreshLayout的父布局
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="?attr/listBackground" android:orientation="vertical"> <android.support.v4.widget.SwipeRefreshLayout android:id="@+id/swiperefreshlayout" android:layout_width="match_parent" android:layout_height="wrap_content"> <!-- 新闻列表展示--> <android.support.v7.widget.RecyclerView android:id="@+id/rcv_article_origin" android:layout_width="match_parent" android:layout_height="match_parent" /> </android.support.v4.widget.SwipeRefreshLayout> </LinearLayout>
|
java代码
这儿有几个注意点:
setColorSchemeColors()
可以控制圆形动画的颜色,最多设置4个。
setOnRefreshListener
设置下拉刷新的回调事件。
下拉刷新后,使用 AsyncTask 根据当前RecyclerView中首个Item的id来加载更多数据。
数据加载完毕后,使用setRefreshing(false);
取消动画。
如果刷新后得到0条记录,提示没有数据更新
。若得到>0条数据,把数据加到RecyclerView中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| mSwipeRefreshLayout.setColorSchemeColors(Color.RED, Color.BLUE); mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { new MoreArticleTask().execute(mAdapter.getTopArticleId()); } }); // Integer 是输入参数 // 得到比某个id大的新闻数组 class MoreArticleTask extends AsyncTask<Integer, Void, List<SimpleArticleItem>> { @Override protected List<SimpleArticleItem> doInBackground(Integer... params) { try { Thread.sleep(1500); } catch (InterruptedException e) { e.printStackTrace(); } return getMoreById(mColumn, params[0]); } @Override protected void onPostExecute(List<SimpleArticleItem> simpleArticleItems) { super.onPostExecute(simpleArticleItems); if (mSwipeRefreshLayout != null) { mSwipeRefreshLayout.setRefreshing(false); } //没有新的数据,提示消息 if (simpleArticleItems == null || simpleArticleItems.size() == 0) { Snackbar.with(mActivity.getApplicationContext()) // context .text(mActivity.getResources().getString(R.string.list_more_data)) // text to display .duration(Snackbar.SnackbarDuration.LENGTH_SHORT) // make it shorter .show(mActivity); // activity where it is displayed } else { mArticleList.addAll(simpleArticleItems); mAdapter.notifyDataSetChanged(); } } }
|
首次进入页面就显示加载ing的动画
效果如下:
直接使用 mSwipeRefreshLayout.setRefreshing(true);
加载动画初始状态并不显示。
看了 http://stackoverflow.com/questions/26858692/swiperefreshlayout-setrefreshing-not-showing-indicator-initially 的解答,
改用下面的代码,初始状态就有加载动画显示。
1 2 3 4 5 6 7
| mSwipeRefreshLayout.post(new Runnable() { @Override public void run() { mSwipeRefreshLayout.setRefreshing(true); new MoreArticleTask().execute(mAdapter.getTopArticleId()); } });
|
上拉加载更多
RecyclerView 展示列表是个通用需求,那么当数据较多时候,如何实现分页加载呢?
比如笔者的项目中,先加载15条新闻,当滑动到底部时候,再加载15条。
效果图
思路
在RecyclerView底部增加一个Footer的ViewHolder,数据加载完毕取出底部的ViewHolder。
很简单,就是一个ProgressBar。本项目为了Material Design,使用了一个开源ProgressBar https://github.com/Todd-Davies/ProgressWheel,你也可以使用原生的ProgressBar。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?xml version="1.0" encoding="utf-8"?><!--上拉加载更多 RecyclerView 底部--> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:wheel="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <com.pnikosis.materialishprogress.ProgressWheel android:id="@+id/rcv_load_more" android:layout_width="40dp" android:layout_height="40dp" android:layout_gravity="center_horizontal" wheel:matProg_barColor="@color/accent" wheel:matProg_progressIndeterminate="true" /> </LinearLayout>
|
##底部Footer的ViewHolder
1 2 3 4 5 6 7 8 9 10 11 12
| /** * 底部加载更多 */ class FooterViewHolder extends RecyclerView.ViewHolder { @InjectView(R.id.rcv_load_more) ProgressWheel rcvLoadMore; public FooterViewHolder(View itemView) { super(itemView); ButterKnife.inject(this, itemView); } }
|
在Fragment中给RecyclerView增加滑动监听
layoutManager.getItemCount()
可以得到当前RecyclerView中Item的总数目
layoutManager.findLastVisibleItemPosition()
得到最后一个可见的Item的位置position
如果 totalItemCount < (lastVisibleItem + Constant.VISIBLE_THRESHOLD)
比如一共15个Item,当前到达第13个, Constant.VISIBLE_THRESHOLD设为3
总数小于最后一个+阈值,就加载更多新闻数据,同时把loading标记为 true 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| private boolean loading = false; @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mRecyclerView.setLayoutManager(new LinearLayoutManager(mActivity)); mAdapter = new OriginArticleAdapter(mActivity, mArticleList); mRecyclerView.setAdapter(mAdapter); mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); int totalItemCount = layoutManager.getItemCount(); int lastVisibleItem = layoutManager.findLastVisibleItemPosition(); if (!loading && totalItemCount < (lastVisibleItem + Constant.VISIBLE_THRESHOLD)) { new ArticleTask(mActivity).execute(mAdapter.getBottomArticleId()); loading = true; } } }); }
|
我们依然使用AsyncTask。
注意 onPreExecute()
给 mArticleList 增加了一个null标记Footer,如果是第一次进入页面(mArticleList为空)不需要加Footer。
当数据加载完毕后,用 mArticleList.remove(mArticleList.size() - 1);
把最下面的Footer删除。
再用 mArticleList.addAll(moreArticles);
增加新增的新闻数据,
并用 mAdapter.notifyDataSetChanged();
通知 RecyclerView.Adapter 有数据改变。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| private List<SimpleArticleItem> mArticleList = new ArrayList<SimpleArticleItem>(); class ArticleTask extends AsyncTask<Integer, Void, List<SimpleArticleItem>> { private Context mContext; public ArticleTask(Context context) { mContext = context; } /** * Runs on the UI thread before {@link #doInBackground}. */ @Override protected void onPreExecute() { super.onPreExecute(); if (mArticleList != null && mArticleList.size() > 0) { mArticleList.add(null); // notifyItemInserted(int position),这个方法是在第position位置 // 被插入了一条数据的时候可以使用这个方法刷新, // 注意这个方法调用后会有插入的动画,这个动画可以使用默认的,也可以自己定义。 mAdapter.notifyItemInserted(mArticleList.size() - 1); } } /** * @param params 偏移量 aid * @return */ @Override protected List<SimpleArticleItem> doInBackground(Integer... params) { try { Thread.sleep(1500); } catch (InterruptedException e) { e.printStackTrace(); } return getArticleList(mColumn, params[0]); } @Override protected void onPostExecute(final List<SimpleArticleItem> moreArticles) { // 新增新闻数据 super.onPostExecute(moreArticles); if (mArticleList.size() == 0) { mArticleList.addAll(moreArticles); mAdapter.notifyDataSetChanged(); } else { //删除 footer mArticleList.remove(mArticleList.size() - 1); mArticleList.addAll(moreArticles); mAdapter.notifyDataSetChanged(); loading = false; } } }
|
Override RecyclerView.Adapter 中的 getItemViewType
extends RecyclerView.Adapter<RecyclerView.ViewHolder>
如果是null,返回Footer的Type;否则,返回正常新闻的Type。
本文,为了UI美观,把新闻分为两种:大于3幅图片 、小于3幅图片,相应返回不同的Type和ViewHolder。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public final static int TYPE_MULTI_IMAGES = 2; // 多个图片的文章 public final static int TYPE_FOOTER = 3;//底部--往往是loading_more public final static int TYPE_NORMAL = 1; // 正常的一条文章 @Override public int getItemViewType(int position) { SimpleArticleItem article = articleList.get(position); if (article == null) { return TYPE_FOOTER; } else if (article.getImageUrls().length >= 3) { return TYPE_MULTI_IMAGES; } else { return TYPE_NORMAL; } }
|
Override RecyclerView.Adapter 中的 onCreateViewHolder
在该方法中利用 switch (viewType)
返回不同的xml布局文件及ViewHolder
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { RecyclerView.ViewHolder vh; View view; switch (viewType) { //其他无法处理的情况使用viewholder_article_simple default: case TYPE_NORMAL: view = mLayoutInflater.inflate( R.layout.item_article_normal, parent, false); vh = new ItemArticleViewHolder(view); return vh; case TYPE_FOOTER: view = mLayoutInflater.inflate( R.layout.recyclerview_footer, parent, false); vh = new FooterViewHolder(view); return vh; case TYPE_MULTI_IMAGES: view = mLayoutInflater.inflate( R.layout.item_article_multi_images, parent, false); vh = new MultiImagesViewHolder(view); return vh; } }
|
Override RecyclerView.Adapter 中的 onBindViewHolder
利用 instanceof 判断是何种类型的ViewHolder,来给控件赋值、绑定数据。
注意:因为Footer是用null表示的,为了防止NullPointerException,
我们先用if把FooterViewHolder的情况处理了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| @Override public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) { //这时候 article是 null,先把 footer 处理了 if (holder instanceof FooterViewHolder) { ((FooterViewHolder) holder).rcvLoadMore.spin(); return; } SimpleArticleItem article = articleList.get(position); String[] imageUrls = article.getImageUrls(); if (holder instanceof ItemArticleViewHolder) { ItemArticleViewHolder newHolder = (ItemArticleViewHolder) holder; newHolder.rcvArticleTitle.setText(article.getTitle()); newHolder.rcvArticleDate.setText(article.getPublishDate()); //当图片小于3张时候 选取第1张图片 if (imageUrls.length > 0) { newHolder.rcvArticlePhoto.setImageURI(Uri.parse(Constant.BUCKET_HOST_NAME + imageUrls[0])); } else { newHolder.rcvArticlePhoto.setImageURI(Uri.parse(ApiUrl.randomImageUrl(article.getId()))); } //注意这个阅读次数是 int 类型,需要转化为 String 类型 newHolder.rcvArticleReadtimes.setText("浏览: " + article.getReadTimes()); newHolder.rcvArticleSummary.setText(article.getSummary()); } else { MultiImagesViewHolder newHolder = (MultiImagesViewHolder) holder; newHolder.articleTitle.setText(article.getTitle()); newHolder.articlePic1.setImageURI(Uri.parse(Constant.BUCKET_HOST_NAME + imageUrls[0])); newHolder.articlePic2.setImageURI(Uri.parse(Constant.BUCKET_HOST_NAME + imageUrls[1])); newHolder.articlePic3.setImageURI(Uri.parse(Constant.BUCKET_HOST_NAME + imageUrls[2])); newHolder.countPics.setText("图片: " + imageUrls.length); newHolder.countRead.setText("浏览: " + article.getReadTimes()); } }
|
下拉刷新和上拉加载更多的源码地址(欢迎star) https://github.com/studychen/SeeNewsV2
本文链接 http://blog.csdn.net/never_cxb/article/details/50759109 转载请注明出处
一些坑
注意给RecyclerView setLayoutManager,不然RecyclerView可能不显示
1 2 3 4 5
| // 1. get a reference to recyclerView RecyclerView recyclerView = (RecyclerView) findViewById(R.id.list); // 2. set layoutManger recyclerView.setLayoutManager(new LinearLayoutManager(this));
|
参考文章