不得不服,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设置界面的数据。