在RecyclerView列表中添加自定义的列表头部与尾部视图
参考资料
前言
基本思路:
首先,头部和尾部也是列表的一部分,它们的添加方式应该和列表中显示数据的主体部分没有太多区别。除了其本身使用了新的布局视图片段。
RecyclerView中管理的视图项应该与数据列表中的数据保持一一对应的关系。
基于以上两点,可以找到一种实现方式,即:通过增加两条数据,让视图可以多创建两个视图项;将多出的两个视图项分别采用头部与尾部视图的布局来生成。这就可以得到一个带有自定义头部与尾部的列表视图。
以下的例子基于以上思路,需要修改与RecyclerView相关的框架代码,主要是适配器adapter代码,数据源DataSource代码。
实现RecyclerView展示列表(没有头部与尾部)的基本代码来自于上方参考资料 android–使用RecyclerView及相关架构组件实现列表数据展示 ,头部与尾部的实现在此代码基础之上修改。
加入头部与尾部
先创建好用于显示头部与尾部的layout布局文件。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- TODO: Update blank fragment layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Here is the header"
android:textSize="18dp" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- TODO: Update blank fragment layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Here is the tail"
android:textSize="18dp" />
</LinearLayout>
接下来,分别调整相关的功能框架代码。
首先需要调整一下列表项的字段,在列表项中增加一个字段标记该对象是正常数据项,或是作为占位用的头部/尾部的空数据项。如下所示,新增了字段pos,用于标记。
public class ItemInfo {
//字符串,用于在textView中显示
public String str;
//图片链接,用于显示图片
public String imgUrl;
//一个唯一标识,可以作为列表项的键
public String key;
//头-"head",尾-"tail",正常数据-null
public String pos;
}
然后,需要修改数据源DataSource代码。需要实现的效果是,确保第一条数据是头部的占位数据。在翻页加载数据直到所有数据加载完成之后,再传一条尾部的占位数据。
public class MyDataSource extends ItemKeyedDataSource<String, ItemInfo> {
private int pageIndex=1;
private boolean addTail=false;//是否已经加入尾部
private boolean addHead=false;//是否已经加入头部
@Override
public void loadInitial(@NonNull LoadInitialParams<String> params, @NonNull LoadInitialCallback<ItemInfo> callback) {
fetchItems(params.requestedInitialKey,params.requestedLoadSize,pageIndex,callback);
}
private void fetchItems(String requestedInitialKey, int requestedLoadSize,int pageIndex,LoadCallback<ItemInfo> callback)
{
try
{
//在此处写入获取当前页列表数据的代码,可以从数据库中获取,或者从后端服务器API获取
//List<ItemInfo> itemInfoList=*******
//callback.onResult(itemInfoList);//通过回调返回列表数据
if (!addHead)
{
ArrayList<ItemInfo> headList=new ArrayList<ItemInfo>();
ItemInfo tail=new ItemInfo();
tail.pos="tail";
headList.add(tail);
addHead=true;
callback.onResult(headList);
return;
}
List<ItemInfo> itemInfoList=new ArrayList<ItemInfo>();
RetrofitSigleton retrofitSigleton=new RetrofitSigleton();
Response<List<ItemInfo>> response= retrofitSigleton.getService().ImgAndTextList(String.valueOf(pageIndex)).execute();
itemInfoList=response.body();
if (itemInfoList.size()==0)//没有数据返回,已经可以加入尾部数据
{
if (!addTail)//还没有返回尾部数据,该代码块仅执行一次
{
ArrayList<ItemInfo> tailList=new ArrayList<ItemInfo>();
ItemInfo tail=new ItemInfo();
tail.pos="tail";
tailList.add(tail);
addTail=true;
callback.onResult(tailList);
return;
}
}
pageIndex=pageIndex+1;
callback.onResult(itemInfoList);
}
catch (Exception e)
{
e.printStackTrace();
}
}
@Override
public void loadAfter(@NonNull LoadParams<String> params, @NonNull LoadCallback<ItemInfo> callback) {
try {
pageIndex=pageIndex+1;
fetchItems(params.key, params.requestedLoadSize,pageIndex,callback);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void loadBefore(@NonNull LoadParams<String> params, @NonNull LoadCallback<ItemInfo> callback) {
}
@NonNull
@Override
public String getKey(@NonNull ItemInfo item) {
return item.key;
}
}
最后,需要修改列表的适配器代码。需要重写getItemViewType()方法,对头部,尾部返回不同的值,用于区分视图类型。然后生成列表项视图的时候根据viewType来加载不同的视图布局。
public class MyListAdapter extends PagedListAdapter<ItemInfo,MyListAdapter.ViewHolder> {
public MyListAdapter()
{
super(DIFF_CALLBACK);
}
private final int bodyItemViewType=0;
private final int tailItemViewType=1;
private final int headItemViewType=2;
private static DiffUtil.ItemCallback<ItemInfo> DIFF_CALLBACK =
new DiffUtil.ItemCallback<ItemInfo>() {
@Override
public boolean areItemsTheSame(@NonNull ItemInfo oldItem, @NonNull ItemInfo newItem) {
// The ID property identifies when items are the same.
return oldItem.key == newItem.key;
}
@Override
public boolean areContentsTheSame(@NonNull ItemInfo oldItem, @NonNull ItemInfo newItem) {
// Don't use the "==" operator here. Either implement and use .equals(),
// or write custom data comparison logic here.
return oldItem.key.equals(newItem.key);
}
};
@NonNull
@Override
public MyListAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view;
if (viewType==this.headItemViewType)
{
view= LayoutInflater.from(parent.getContext()).inflate(R.layout.fragment_header_of_list,parent,false);
}
else if(viewType==this.tailItemViewType)
{
view= LayoutInflater.from(parent.getContext()).inflate(R.layout.fragment_tail_of_list,parent,false);
}
else {
view= LayoutInflater.from(parent.getContext()).inflate(R.layout.fragment_list_item,parent,false);
}
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull MyListAdapter.ViewHolder holder, int position) {
try {
ItemInfo _itemInfo=getItem(position);
if (_itemInfo.pos==null)
{
holder._textView.setText(_itemInfo.str);
Glide.with(holder.mView).load(_itemInfo.imgUrl).into(holder._imageView);
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
@Override
public int getItemViewType(int position) {
if (getItem(position).pos=="head")
{
return this.headItemViewType;
}
else if(getItem(position).pos=="tail")
{
return this.tailItemViewType;
}
else {
return this.bodyItemViewType;
}
}
public class ViewHolder extends RecyclerView.ViewHolder
{
public final ImageView _imageView;
public final TextView _textView;
public final View mView;
public ViewHolder(@NonNull View itemView)
{
super(itemView);
_textView=(TextView)itemView.findViewById(R.id.idOfTextView);
_imageView=(ImageView)itemView.findViewById(R.id.idOfImageView);
mView=itemView;
}
}
}
经过以上工作,可以实现展示一个带有简单头部与尾部的RecyclerView列表视图。