Android RecyclerView的多种布局混合的Adapter

910 阅读4分钟

不得不服,BaseRecyclerViewAdapterHelper已经基本上一统Android端列表布局江湖了。

implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.6'

BaseQuickAdapter我这里就不多说了吧,主要介绍多布局混合的界面。


import android.util.SparseArray;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.chad.library.adapter.base.BaseMultiItemQuickAdapter;
import com.chad.library.adapter.base.viewholder.BaseViewHolder;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import site.doramusic.app.R;

/**
 * 带字母的多选条目适配器,可以用它的{@link #generateLetterItems}生成字母条目,这样就可以无需关心字母的问题了。
 * {@link #getItemCount()}是带头部和底部布局的条目数量,所有条目数量均不以这个作为参考,真正参考的是
 * {@link #getData()}的尺寸。
 *
 * @param <T> 业务层实体类
 */
public abstract class BaseLetterMultiChoiceAdapter<T extends PinyinComparable<T>>
        extends BaseMultiItemQuickAdapter<LetterChoiceEntity<T>, BaseViewHolder> {

    protected boolean mChoiceEffective = true;

    /**
     * 回调在全选或全不选,某个位置的选中状态改变。
     */
    private OnSelectListener<T> mOnSelectListener;

    /**
     * 非字母条目的点击回调。
     */
    protected OnRealItemClickListener<T> mOnRealItemClickListener;

    /**
     * 在子类调它。
     *
     * @param entities
     */
    protected BaseLetterMultiChoiceAdapter(@Nullable List<LetterChoiceEntity<T>> entities) {
        super(entities);
        addItemType(LetterChoiceEntity.ITEM_HEAD, R.layout.adapter_multi_choice_head);
        addItemType(LetterChoiceEntity.ITEM_CONTENT, getLayoutId());
        addItemType(LetterChoiceEntity.ITEM_PLACEHOLDER, R.layout.adapter_multi_choice_placeholder);
    }

    /**
     * 简易包装,不会增加字母条目数量,也不会增加原条目数量。
     *
     * @param entities
     * @param clazz
     * @param <T>
     * @return
     */
    protected static <T extends PinyinComparable<T>> List<LetterChoiceEntity<T>>
    generateNormalItems(@NonNull List<T> entities, Class<T> clazz) {
        List<LetterChoiceEntity<T>> multiChoiceEntities = new ArrayList<>();
        if (entities.size() > 0) {
            for (T entity : entities) {
                String pinyin = entity.getPinyin();
                LetterChoiceEntity<T> contentEntity = new LetterChoiceEntity<>();
                contentEntity.setLetter(pinyin);
                contentEntity.setItemType(LetterChoiceEntity.ITEM_CONTENT);
                contentEntity.setEntity(entity);
                multiChoiceEntities.add(contentEntity);
            }
        }
        return multiChoiceEntities;
    }

    /**
     * 生成字母条目,但不显示字母条目,仅占位,便于{@link #acquireLetters()}和{@link #acquireLettersArray()}
     * 能取到值。
     *
     * @param entities
     * @param clazz
     * @param addLetterHeader
     * @param <T>
     * @return
     */
    protected static <T extends PinyinComparable<T>> List<LetterChoiceEntity<T>>
    generateLetterItems(@NonNull List<T> entities, Class<T> clazz, boolean addLetterHeader) {
        List<LetterChoiceEntity<T>> multiChoiceEntities = new ArrayList<>();
        List<String> letters = new ArrayList<>();
        if (entities.size() > 0) {
            for (T entity : entities) {
                String pinyin = entity.getPinyin();
                String letter = PinyinUtils.getLetter(pinyin);
                //字母第一次出现,就先添加字母头布局
                if (!letters.contains(letter)) {
                    LetterChoiceEntity<T> headEntity = new LetterChoiceEntity<>();
                    headEntity.setLetter(letter);
                    if (addLetterHeader) {
                        headEntity.setItemType(LetterChoiceEntity.ITEM_HEAD);
                    } else {
                        headEntity.setItemType(LetterChoiceEntity.ITEM_PLACEHOLDER);
                    }
                    multiChoiceEntities.add(headEntity);
                    letters.add(letter);
                }
                LetterChoiceEntity<T> contentEntity = new LetterChoiceEntity<>();
                contentEntity.setLetter(pinyin);
                contentEntity.setItemType(LetterChoiceEntity.ITEM_CONTENT);
                contentEntity.setEntity(entity);
                multiChoiceEntities.add(contentEntity);
            }
        }
        return multiChoiceEntities;
    }

    /**
     * 生成字母条目,并显示字母条目。
     *
     * @param entities
     * @param clazz
     * @param <T>
     * @return
     */
    protected static <T extends PinyinComparable<T>> List<LetterChoiceEntity<T>>
    generateLetterItems(@NonNull List<T> entities, Class<T> clazz) {
        return generateLetterItems(entities, clazz, true);
    }

    /**
     * 生成字母条目,可以指定具体位置的选中状态。
     *
     * @param entities
     * @param checkedPos 设置默认的选中状态,位置的取值小于传入数据集合的大小
     * @param clazz
     * @param <T>
     * @return
     */
    protected static <T extends PinyinComparable<T>> List<LetterChoiceEntity<T>>
    generateLetterItems(@NonNull List<T> entities, List<Integer> checkedPos, Class<T> clazz) {
        if (checkedPos == null) {
            checkedPos = new ArrayList<>();
        }
        List<LetterChoiceEntity<T>> multiChoiceEntities = new ArrayList<>();
        List<String> letters = new ArrayList<>();
        if (entities.size() > 0) {
            Collections.sort(entities);
            for (int i = 0; i < entities.size(); i++) {
                T entity = entities.get(i);
                String pinyin = entity.getPinyin();
                if (pinyin != null) {
                    //字母第一次出现,就先添加字母头布局
                    if (!letters.contains(PinyinUtils.getLetter(pinyin))) {
                        LetterChoiceEntity<T> headEntity = new LetterChoiceEntity<>();
                        headEntity.setLetter(PinyinUtils.getLetter(pinyin));
                        headEntity.setItemType(LetterChoiceEntity.ITEM_HEAD);
                        multiChoiceEntities.add(headEntity);
                        letters.add(PinyinUtils.getLetter(pinyin));
                    }
                    LetterChoiceEntity<T> contentEntity = new LetterChoiceEntity<>();
                    contentEntity.setLetter(PinyinUtils.getLetter(pinyin));
                    contentEntity.setItemType(LetterChoiceEntity.ITEM_CONTENT);
                    contentEntity.setEntity(entity);
                    if (checkedPos.contains(i)) {
                        contentEntity.setSelected(true);
                    }
                    multiChoiceEntities.add(contentEntity);
                }
            }
        }
        return multiChoiceEntities;
    }

    /**
     * 设置是否启用多选框。
     *
     * @param choiceEffective true表示显示CheckBox,false反之
     */
    public void setChoiceEffective(boolean choiceEffective) {
        this.mChoiceEffective = choiceEffective;
    }

    /**
     * 取得所有字母条目的位置和名称。
     *
     * @return
     */
    public SparseArray<String> acquireLettersArray(SparseArray<String> lettersArray) {
        lettersArray.clear();
        List<LetterChoiceEntity<T>> data = getData();
        for (int i = 0; i < data.size(); i++) {
            LetterChoiceEntity<T> letterChoiceEntity = data.get(i);
            if (letterChoiceEntity.getItemType() == LetterChoiceEntity.ITEM_HEAD
                    || letterChoiceEntity.getItemType() == LetterChoiceEntity.ITEM_PLACEHOLDER) {
                lettersArray.put(i, letterChoiceEntity.getLetter());
            }
        }
        return lettersArray;
    }

    /**
     * 取得所有字母条目的名称。
     *
     * @return
     */
    public ArrayList<String> acquireLetters() {
        ArrayList<String> letters = new ArrayList<>();
        List<LetterChoiceEntity<T>> data = getData();
        for (int i=0;i<data.size();i++) {
            LetterChoiceEntity<T> letterChoiceEntity = data.get(i);
            if (letterChoiceEntity.getItemType() == LetterChoiceEntity.ITEM_HEAD
                    || letterChoiceEntity.getItemType() == LetterChoiceEntity.ITEM_PLACEHOLDER) {
                letters.add(letterChoiceEntity.getLetter());
            }
        }
        return letters;
    }

    /**
     * 取得所有选中的数据。
     *
     * @return
     */
    public List<T> acquireCheckedData() {
        List<T> entities = new ArrayList<>();
        for (LetterChoiceEntity<T> entity : getData()) {
            if (entity.getItemType() == LetterChoiceEntity.ITEM_CONTENT && entity.isSelected()) {
                entities.add(entity.getEntity());
            }
        }
        return entities;
    }

    public void removeAllLetters() {
        for (int i = 0; i< getData().size();i++) {
            LetterChoiceEntity<T> letterChoiceEntity = getData().get(i);
            if (letterChoiceEntity.getItemType() == LetterEntity.ITEM_HEAD
                    || letterChoiceEntity.getItemType() == LetterEntity.ITEM_PLACEHOLDER) {
                getData().remove(letterChoiceEntity);
            }
        }
    }

    /**
     * 带字母的索引转换成不带字母的。
     *
     * @param rawPos
     * @return
     */
    public int convertToRealPos(int rawPos) {
        LetterChoiceEntity<T> letterChoiceEntity = getData().get(rawPos);
        return seekForRawPos(letterChoiceEntity);
    }

    /**
     * 不带字母的索引转换成带字母的。
     *
     * @param realPos
     * @return
     */
    public int convertToRawPos(int realPos) {
        T t = getRealData().get(realPos);
        return seekForRawPos(t);
    }

    /**
     * 取得选中的位置。
     *
     * @return
     */
    public List<Integer> acquireCheckedRealPos() {
        List<Integer> result = new ArrayList<>();
        for (int i = 0; i < getData().size(); i++) {
            LetterChoiceEntity<T> entity = getData().get(i);
            if (entity.getItemType() == LetterChoiceEntity.ITEM_CONTENT && entity.isSelected()) {
                result.add(convertToRealPos(i));
            }
        }
        return result;
    }

    /**
     * 除开字母条目的条目个数。
     */
    public int calcRealDataCount() {
        int count = 0;
        for (LetterChoiceEntity<T> item : getData()) {
            if (item.getItemType() == LetterChoiceEntity.ITEM_CONTENT) {
                count++;
            }
        }
        return count;
    }

    /**
     * 获取当前选中条目的数量。
     *
     * @return
     */
    public int getCheckedCount() {
        int checkedCount = 0;
        for (int i = 0; i < getData().size(); i++) {
            LetterChoiceEntity<T> item = getItem(i);
            if (item.getItemType() == LetterChoiceEntity.ITEM_CONTENT) {
                if (item.isSelected()) {
                    checkedCount++;
                }
            }
        }
        return checkedCount;
    }

    /**
     * 选中的条目。
     *
     * @return
     */
    public List<T> getCheckedRealData() {
        List<T> checked = new ArrayList<>();
        for (int i = 0; i < getData().size(); i++) {
            LetterChoiceEntity<T> item = getItem(i);
            if (item.getItemType() == LetterChoiceEntity.ITEM_CONTENT) {
                if (item.isSelected()) {
                    checked.add(item.getEntity());
                }
            }
        }
        return checked;
    }

    /**
     * 当前是否全选,有一个没有被选中则返回false。
     *
     * @return
     */
    public boolean isAllChecked() {
        return calcRealDataCount() == getCheckedCount();
    }

    /**
     * 全选则取消全选,取消全选则全选。
     */
    public void turnCheckState() {
        if (isAllChecked()) {
            uncheckAll();
        } else {
            checkAll();
        }
    }

    /**
     * 选中。
     *
     * @param realPos
     */
    public void check(int realPos) {
        T t = getRealData().get(realPos);
        int rawPos = seekForRawPos(t);
        LetterChoiceEntity<T> letterChoiceEntity = getData().get(rawPos);
        if (letterChoiceEntity.getItemType() == LetterChoiceEntity.ITEM_CONTENT) {
            letterChoiceEntity.setSelected(true);
            notifyItemChanged(rawPos);
        }
    }

    /**
     * 取消选中。
     *
     * @param realPos
     */
    public void uncheck(int realPos) {
        if (realPos == -1) {
            return;
        }
        T t = getRealData().get(realPos);
        int rawPos = seekForRawPos(t);
        LetterChoiceEntity<T> letterChoiceEntity = getData().get(rawPos);
        if (letterChoiceEntity.getItemType() == LetterChoiceEntity.ITEM_CONTENT) {
            letterChoiceEntity.setSelected(false);
            notifyItemChanged(rawPos);
        }
    }

    /**
     * 全选。
     */
    public void checkAll() {
        for (LetterChoiceEntity<T> letterChoiceEntity : getData()) {
            if (letterChoiceEntity.getItemType() == LetterChoiceEntity.ITEM_CONTENT) {
                letterChoiceEntity.setSelected(true);
            }
        }
        notifyDataSetChanged();
        if (mOnSelectListener != null) {
            mOnSelectListener.onSelectStateChanged(true);
        }
    }

    /**
     * 取消全选。
     */
    public void uncheckAll() {
        for (LetterChoiceEntity<T> letterChoiceEntity : getData()) {
            if (letterChoiceEntity.getItemType() == LetterChoiceEntity.ITEM_CONTENT) {
                letterChoiceEntity.setSelected(false);
            }
        }
        notifyDataSetChanged();
        if (mOnSelectListener != null) {
            mOnSelectListener.onSelectStateChanged(false);
        }
    }

    /**
     * 除开字母条目的数据。
     *
     * @return
     */
    public List<T> getRealData() {
        List<T> entities = new ArrayList<>();
        for (LetterChoiceEntity<T> entity : getData()) {
            if (entity.getItemType() == LetterChoiceEntity.ITEM_CONTENT) {
                entities.add(entity.getEntity());
            }
        }
        return entities;
    }

    /**
     * 获取除开字母条目的条目数量。
     *
     * @return
     */
    public int getRealDataCount() {
        return getRealData().size();
    }

    /**
     * 获取除开字母条目的某条数据。
     *
     * @param realPos
     * @return
     */
    public T getRealDataAt(int realPos) {
        List<T> realData = getRealData();
        return realData.get(realPos);
    }

    /**
     * 搜寻带字母详细信息的实体类。
     *
     * @param t
     * @return
     */
    public LetterChoiceEntity<T> seekForRawEntity(T t) {
        for (LetterChoiceEntity<T> letterChoiceEntity : getData()) {
            T entity = letterChoiceEntity.getEntity();
            if (t.equals(entity) && letterChoiceEntity.getItemType() == LetterChoiceEntity.ITEM_CONTENT) {
                return letterChoiceEntity;
            }
        }
        return null;
    }

    /**
     * 搜寻除开字母条目的位置。
     *
     * @param t
     * @return
     */
    public int seekForRealPos(T t) {
        for (int i = 0; i < getRealData().size(); i++) {
            T entity = getRealData().get(i);
            if (t.equals(entity)) {
                return i;
            }
        }
        return -1;
    }

    /**
     * 搜寻原始位置,即带字母条目该数据条目原先的position。
     *
     * @param t
     * @return
     */
    public int seekForRawPos(T t) {
        for (int i = 0; i < getData().size(); i++) {
            LetterChoiceEntity<T> letterChoiceEntity = getData().get(i);
            T entity = letterChoiceEntity.getEntity();
            if (t.equals(entity)) {
                return i;
            }
        }
        return -1;
    }

    /**
     * 搜寻原始位置,即带字母条目该数据条目原先的position。
     *
     * @param letterChoiceEntity
     * @return
     */
    private int seekForRawPos(LetterChoiceEntity<T> letterChoiceEntity) {
        List<LetterChoiceEntity<T>> data = getData();
        int pos = 0;
        for (LetterChoiceEntity<T> entity : data) {
            if (letterChoiceEntity.equals(entity)) {
                return pos;
            }
            if (entity.getItemType() == LetterChoiceEntity.ITEM_CONTENT) {
                pos++;
            }
        }
        return pos;
    }

    @Override
    protected void convert(@NonNull BaseViewHolder holder, LetterChoiceEntity<T> letterChoiceEntity) {
        holder.itemView.setOnClickListener(v -> {
            if (mChoiceEffective && letterChoiceEntity.getItemType() == LetterChoiceEntity.ITEM_CONTENT) {
                letterChoiceEntity.setSelected(!letterChoiceEntity.isSelected());
                notifyItemChanged(holder.getAdapterPosition());
                if (mOnSelectListener != null) {
                    mOnSelectListener.onSelected(seekForRawPos(letterChoiceEntity),
                            letterChoiceEntity.getEntity(), letterChoiceEntity.isSelected());
                }
            }
        });
        T entity = letterChoiceEntity.getEntity();
        switch (letterChoiceEntity.getItemType()) {
            case LetterChoiceEntity.ITEM_HEAD:
                holder.setText(R.id.tv_multi_choice_pinyin, letterChoiceEntity.getLetter());
                break;
            case LetterChoiceEntity.ITEM_CONTENT:
                onBindViewHolder(holder, entity, entity.getPinyin(), letterChoiceEntity.isSelected());
                break;
            case LetterChoiceEntity.ITEM_PLACEHOLDER:
                break;
        }
    }

    /**
     * 内容布局。
     *
     * @return
     */
    public abstract int getLayoutId();

    /**
     * 绑定内容数据。
     *
     * @param holder
     * @param entity
     * @param pinyin
     * @param selected
     */
    public abstract void onBindViewHolder(BaseViewHolder holder, T entity, String pinyin, boolean selected);

    public void setOnRealItemClickListener(OnRealItemClickListener<T> l) {
        this.mOnRealItemClickListener = l;
    }

    /**
     * 设置选中的监听器。
     *
     * @param l
     */
    public void setOnSelectListener(OnSelectListener<T> l) {
        this.mOnSelectListener = l;
    }

    public interface OnRealItemClickListener<T> {
        void onItemClick(int position, T entity);
    }

    public interface OnSelectListener<T> {

        /**
         * @param isAllChecked true表示全选,false表示全不选
         */
        void onSelectStateChanged(boolean isAllChecked);

        /**
         * position位置的选中状态改变。
         *
         * @param position
         * @param entity
         * @param selected
         */
        void onSelected(int position, T entity, boolean selected);
    }
}

这是我定义的一个万能字母导航的列表适配器。

import com.chad.library.adapter.base.entity.MultiItemEntity;

public class LetterEntity<T> implements MultiItemEntity {

    public static final int ITEM_HEAD = 1;
    public static final int ITEM_CONTENT = 2;
    public static final int ITEM_PLACEHOLDER = 3;
    private T mEntity;
    private String letter;
    private int itemType;

    public String getLetter() {
        return letter;
    }

    public void setLetter(String letter) {
        this.letter = letter;
    }

    public T getEntity() {
        return mEntity;
    }

    public void setEntity(T entity) {
        this.mEntity = entity;
    }

    @Override
    public int getItemType() {
        return itemType;
    }

    public void setItemType(int itemType) {
        this.itemType = itemType;
    }
}
public class LetterChoiceEntity<T> extends LetterEntity<T> {

    private boolean selected;

    public boolean isSelected() {
        return selected;
    }

    public void setSelected(boolean selected) {
        this.selected = selected;
    }
}

写得比较粗糙,请大佬批评指正。首先实体类需要实现MultiItemEntity接口,这样就可以重写getItemType()方法了,然后继承BaseMultiItemQuickAdapter,然后在适配器的构造方法中调用addItemType()来映射item类型和布局,最后在convert()方法中通过判断实现了MultiItemEntity的itemType来给holder设置界面的数据。