Android開發---在RecyclerView列表中新增自定義的列表頭部與尾部檢視

0522發表於2020-12-27

在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列表檢視。
在這裡插入圖片描述在這裡插入圖片描述

相關文章