为什么分页?
从开发者的角度来看,如何加载所有内容?一次不可能显示很多的内容。我们只能显示它们的部分。
分页允许用户看到最新的内容,等待时间很少。当我们在用户滚动到底部时加载下一个“页面”,更多的内容被加载并可用。
何时使用分页?
如果你有大量的内容需要太长时间才能加载。这可以是本地数据库或API调用。那么使用分页是有意义的。如果您从数据库中提取数据,请分批请求数据(每个请求20个)。同样的情况也适用于API调用。
Android分页与RecyclerView
① 自定义OnScrollListener
public abstract class PaginationScrollListener extends RecyclerView.OnScrollListener {
LinearLayoutManager layoutManager;
public PaginationScrollListener(LinearLayoutManager layoutManager) {
this.layoutManager = layoutManager;
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int visibleItemCount = layoutManager.getChildCount();
int totalItemCount = layoutManager.getItemCount();
int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition();
if (!isLoading() && !isLastPage()) {
if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount
&& firstVisibleItemPosition >= 0) {
loadMoreItems();
}
}
}
protected abstract void loadMoreItems();
public abstract int getTotalPageCount();
public abstract boolean isLastPage();
public abstract boolean isLoading();
}
如果要启用分页,我们必须要检测达到列表(RecyclerView)的结尾。PaginationScrollListener才能启用分页。
② 布局设置
创建一个布局RecyclerView和一个ProgressBar(用于指示初始内容的加载)。
<FrameLayout>
<android.support.v7.widget.RecyclerView />
<ProgressBar android:layout_gravity="center”/>
</FrameLayout>
③ 创建RecyclerView.Adapter
首先,创建类PaginationAdapter扩展RecyclerView.Adapter,然后创建两个RecyclerView.ViewHolder。
1、class ContentVH (main content item)
2、class LoadingVH (footer ProgressBar used for Pagination)
public class PaginationAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
// flag for footer ProgressBar
private boolean isLoadingAdded = false;
...
@Override
public int getItemCount() {
return movies == null ? 0 : movies.size();
}
@Override
public int getItemViewType(int position) {
return (position == movies.size() - 1 && isLoadingAdded) ? LOADING : ITEM;
}
...
}
对于我们的例子,我们假设要显示一个电影列表。
④ Adapter的辅助方法
将以下方法添加到PaginationAdapter中。它们对于通过分页获取的添加数据很有用。
public void add(Movie mc) {
movies.add(mc);
notifyItemInserted(movies.size() - 1);
}
public void addAll(List<Movie> mcList) {
for (Movie mc : mcList) {
add(mc);
}
}
public void remove(Movie city) {
int position = movies.indexOf(city);
if (position > -1) {
movies.remove(position);
notifyItemRemoved(position);
}
}
public void clear() {
isLoadingAdded = false;
while (getItemCount() > 0) {
remove(getItem(0));
}
}
public boolean isEmpty() {
return getItemCount() == 0;
}
public void addLoadingFooter() {
isLoadingAdded = true;
add(new Movie());
}
public void removeLoadingFooter() {
isLoadingAdded = false;
int position = movies.size() - 1;
Movie item = getItem(position);
if (item != null) {
movies.remove(position);
notifyItemRemoved(position);
}
}
public Movie getItem(int position) {
return movies.get(position);
}
⑤ 初始化RECYCLERVIEW
PaginationAdapter adapter = new PaginationAdapter(this);
linearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
rv.setLayoutManager(linearLayoutManager);
rv.setItemAnimator(new DefaultItemAnimator());
rv.setAdapter(adapter);
到这里,我们来介绍一下分页在此流程中如何工作的:
1、ProgressDialog在取得初始数据的同时在空白屏幕上显示加载进度
2、隐藏ProgressDialog和显示数据
3、检测用户滚动到列表的末尾
4、ProgressDialog在提取下一页数据时在页脚显示
5、删除页脚ProgressDialog并显示提取的数据
6、重复步骤3,4和5,直到所有页面都已加载
Activity设置
public class MainActivity extends AppCompatActivity {
PaginationAdapter adapter;
LinearLayoutManager linearLayoutManager;
RecyclerView rv;
ProgressBar progressBar;
// 分页开始的索引(0是我们的第一页)
private static final int PAGE_START = 0;
//表示是否显示了页脚ProgressBar(即下一页正在加载)
private boolean isLoading = false;
// 如果当前页面是最后一页(页面加载后分页将停止)
private boolean isLastPage = false;
//总的页面加载数。初始加载为第0页,之后再加载2页。
private int TOTAL_PAGES = 3;
// 表示分页正在加载的页面。
private int currentPage = PAGE_START;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
...
rv = (RecyclerView) findViewById(R.id.main_recycler);
progressBar = (ProgressBar) findViewById(R.id.main_progress);
adapter = new PaginationAdapter(this);
linearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
rv.setLayoutManager(linearLayoutManager);
rv.setItemAnimator(new DefaultItemAnimator());
rv.setAdapter(adapter);
rv.addOnScrollListener(new PaginationScrollListener(linearLayoutManager) {
@Override
protected void loadMoreItems() {
isLoading = true;
currentPage += 1; //增加页面索引以加载下一个
loadNextPage();
}
@Override
public int getTotalPageCount() {
return TOTAL_PAGES;
}
@Override
public boolean isLastPage() {
return isLastPage;
}
@Override
public boolean isLoading() {
return isLoading;
}
});
loadFirstPage();
}
...
}
加载初始数据
以下是我们如何使用该方法执行初始加载(即第一页请求):
private void loadFirstPage() {
////获取虚拟数据
List<Movie> movies = Movie.createMovies(adapter.getItemCount());
progressBar.setVisibility(View.GONE);
adapter.addAll(movies);
if (currentPage <= TOTAL_PAGES) adapter.addLoadingFooter();
else isLastPage = true;
}
我们甚至可以使用Handler模拟网络延迟。
// 模拟1秒网络延迟
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
loadFirstPage();
}
}, 1000);
一旦我们加载初始请求并获取数据,请隐藏ProgressBar。接下来,将获取的数据添加到适配器并通知更新。PaginationAdapter中的addAll()方法完成了此操作。
使用Paginati onScrollListener
请注意PaginationScrollListener如何使用我们Activity中定义的标志。它需要LayoutManager提供给RecyclerView来计数和比较它的数量。这更准确地知道在布局中实际有多少项目,而不是计算List <Model>。但是现在,它的构造函数只支持LinearLayoutManager。
一旦初始数据加载,它的时间倾听滚动更改并触发下一页
private void loadNextPage() {
List<Movie> movies = Movie.createMovies(adapter.getItemCount()); // 1
adapter.removeLoadingFooter(); // 2
isLoading = false; // 3
adapter.addAll(movies); // 4
if (currentPage != TOTAL_PAGES) adapter.addLoadingFooter(); // 5
else isLastPage = true;
}
最终结果
根据我们的代码设置,我们每页加载10个虚拟内容。分页将发生的次数为3(TOTAL_PAGE)。添加初始页面加载,您正在查看40个项目。
到这里咱们就结束了。