1. 程式人生 > >RecyclerView item多佈局顯示新姿勢

RecyclerView item多佈局顯示新姿勢

問題

RecyclerView正在逐漸取代ListView,在使用RecyclerView中遇到了一個問題,需要在RecyclerView的固定位置設定不同佈局的特殊Item,其中最大的問題在於不同型別data和View佈局如何進行正確對應。

思路

  • 將需要顯示的不同型別資料都放到同一個容器中
  • 顯示時根據不同的型別載入不同的佈局

實現

在使用RecyclerView時最重要的一個角色是Adapter,所以先從它入手,看下Adapter的程式碼

public class MyAdapter extends RecyclerView.Adapter {
    @Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return null; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { } @Override public int getItemCount() { return 0; } }

從程式碼中可以看出其中有兩個比較重要比較重要的函式,onCreateViewHolder、onBindViewHolder兩個函式,含義從字面可以理解其含義,主要作用是用來將View和ViewHolder以及ViewHolder和data進行繫結。
我們知道每個不同型別的data將需要不同型別ViewHolder進行繫結,不同型別的ViewHolder也對應著不同的View佈局,所以對於不同data在顯示過程中執行的onCreateViewHolder、onBindViewHolder方法內容都不同,我們可以抽象一個用於繫結的介面如下:

public interface ItemViewBinder<T, H extends RecyclerView.ViewHolder> {
    H onCreateViewHolder(LayoutInflater inflater, ViewGroup parent);

    void onBindViewHolder(H viewHolder, T t);
}

不同data可以有不同的實現,其中一個實現如下,其他類似:

public class NormalItemViewBinder implements ItemViewBinder<NormalItemBean
, NormalItemViewBinder.NormalViewHolder> {
@Override public NormalViewHolder onCreateViewHolder(LayoutInflater inflater, ViewGroup parent) { return new NormalViewHolder(inflater.inflate(R.layout.recycle_item_normal, null)); } @Override public void onBindViewHolder(NormalViewHolder viewHolder, final NormalItemBean normalItemBean) { viewHolder.tv.setText(normalItemBean.content); } static class NormalViewHolder extends BaseViewHolder { TextView tv; public NormalViewHolder(View itemView) { super(itemView); tv = itemView.findViewById(R.id.tv); } } }

解決了不同型別的繫結問題,接下來需要面對的問題就是如何得到想要型別的ItemViewBinder例項。

  • onCreateViewHolder(ViewGroup parent, int viewType)方法中可以通過viewType引數判斷型別,構造一個viewType和ItemViewBinder的對應關係即可;
  • 要使用viewType需要重寫getItemViewType(int position)方法,因為引數只有position,在item的位置和顯示樣式沒有固定對應關係的情況下,position和viewType是沒辦法直接產生聯絡的,通過position可以直接獲得的一般是data,所以可以通過data的型別讓position和viewType產生聯絡,即通過position可以知道該位置的data型別,然後每個型別都會對應著一個viewType
  • onBindViewHolder(RecyclerView.ViewHolder holder, int position)方法中傳入的引數除了ViewHolder以外只有一個position引數,同樣position和ItemViewBinder在一般情況下也不會有直接對應關係,我們仍然可以通過data的型別讓兩者產生聯絡,即通過position可以得到data型別,每個data型別都會對應特定的ItemViewBinder

綜合上面問題我們知道需要找到一個合適的資料結構讓viewType、data型別分別與ItemViewBinder例項產生對應關係,可以想到如下資料結構:

  • 用兩個Map分別儲存兩兩對應關係
  • 利用ArrayMap集合

這裡使用ArrayMap集合實現,主要利用ArrayMap的特性,可以通過index或者key查詢value,我們viewType作為index,讓Class型別作為key,ItemViewBinder例項作為value,實現程式碼如下:

public class TypeTool {
    private static volatile TypeTool instance;
    private ArrayMap<Class, ItemViewBinder> itemViewBinders;

    @TargetApi(Build.VERSION_CODES.KITKAT)
    private TypeTool() {
        itemViewBinders = new ArrayMap<>();
    }

    public static TypeTool getInstance() {
        if (instance == null) {
            synchronized (TypeTool.class) {
                if (instance == null) {
                    instance = new TypeTool();
                }
            }
        }
        return instance;
    }

    public void register(Class clazz, ItemViewBinder itemViewBinder) {
        itemViewBinders.put(clazz, itemViewBinder);
    }

    public int indexOfClass(Class clazz) {
        return itemViewBinders.indexOfKey(clazz);
    }

    public ItemViewBinder getItemViewBinderByIndex(int index) {
        return itemViewBinders.valueAt(index);
    }

    public ItemViewBinder getItemViewBinderByClass(Class clazz) {
        return itemViewBinders.get(clazz);
    }

    public void clear() {
        itemViewBinders.clear();
    }
}

解決了上面問題基本就完成了RecyclerView的Item多佈局顯示問題,下面是對ItemViewBinder的呼叫方法:

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    List<Object> datas;
    LayoutInflater inflater;
    TypeTool typeTool;

    public MyAdapter(Context context, List<Object> datas) {
        inflater = LayoutInflater.from(context);
        this.datas = datas;
        typeTool = TypeTool.getInstance();
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        ItemViewBinder itemViewBinder = typeTool.getItemViewBinderByIndex(viewType);
        return itemViewBinder.onCreateViewHolder(inflater, parent);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        ItemViewBinder itemViewBinder = typeTool.getItemViewBinderByClass(datas.get(position).getClass());
        itemViewBinder.onBindViewHolder(holder, datas.get(position));
    }

    @Override
    public int getItemCount() {
        return datas.size();
    }

    @Override
    public int getItemViewType(int position) {
        return typeTool.indexOfClass(datas.get(position).getClass());
    }

    public void register(Class clazz, ItemViewBinder itemViewBinder) {
        typeTool.register(clazz, itemViewBinder);
    }
}

Activity中程式碼如下:

public class MainActivity extends AppCompatActivity {

    RecyclerView rv;
    MyAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        rv = (RecyclerView) findViewById(R.id.rv);
        adapter = new MyAdapter(this, getDatas());
        rv.setAdapter(adapter);
        rv.setLayoutManager(new LinearLayoutManager(this));
        initData();
    }

    public void initData() {
        adapter.register(NormalItemBean.class, new NormalItemViewBinder());
        adapter.register(ADItemBean.class, new ADItemViewBinder());
    }

    public List<Object> getDatas() {
        List<Object> datas = new ArrayList<>();
        for (int i = 0; i < 30; i++) {
            datas.add(new NormalItemBean("item:" + i));
        }
        datas.add(2, new ADItemBean(R.drawable.ad1));
        datas.add(8, new ADItemBean(R.drawable.ad2));
        return datas;
    }
}

效果

如圖,RecyclerView中包含兩種佈局,一種是文字,一種是圖片
這裡寫圖片描述