废话不在前面说,先介绍功能!
简化 Adapter
下面是简化过的 Adapter,后面简单介绍,底部还有高级 Adapter:
public abstract class BaseRecyclerAdapter<T> extends RecyclerView.Adapter<ViewHolder> {
//布局
private final int mItemLayoutRes;
//数据
public List<T> mItems;
//点击事件回调接口
private ItemClickListener<T> mItemClickListener ;
private ItemLongClickListnener<T> mItemLongClickListnener ;
public BaseRecyclerAdapter(@LayoutRes int mItemLayoutRes, List<T> items) {
this.mItemLayoutRes = mItemLayoutRes;
// 如果没有传入的数据 ,则自动创建一个空的集合 ,防止报空指针
this.mItems = items == null ? new ArrayList<>() : items;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(mItemLayoutRes, parent, false);
//根据view创建多功能view holder
ViewHolder viewHolder = new ViewHolder(view);
// 初始化Item事件监听
initOnItemListener(viewHolder);
return viewHolder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
//获取数据
T item = mItems.get(position);
//绑定数据到布局
convertView(holder, item, position);
}
@Override
public int getItemCount() {
return mItems.size();
}
//关键--外部将数据绑定到布局去
public abstract void convertView(ViewHolder viewHolder, T item, int position) ;
//设置点击事件
private void initOnItemListener(ViewHolder holder) {
if (mItemClickListener != null) {
//根布局点击事件
holder.itemView.setOnClickListener(v -> {
T o = mItems.get(holder.getLayoutPosition());
mItemClickListener.onItemClick(v, o , holder.getLayoutPosition());
});
}
if (mItemLongClickListnener != null) {
//根布局长按事件
holder.itemView.setOnLongClickListener(v -> {
T o = mItems.get(holder.getLayoutPosition());
mItemLongClickListnener.onItemLongClick(v, o, holder.getLayoutPosition());
return true;
});
}
}
public void setOnItemClickListener(ItemClickListener<T> listener) {
mItemClickListener = listener ;
}
public void setOnItemLongClickListener(ItemLongClickListnener<T> listener) {
mItemLongClickListnener = listener ;
}
public interface ItemClickListener<T> {
void onItemClick(View view, T itemObj, int position) ;
}
public interface ItemLongClickListnener<T> {
void onItemLongClick(View view, T itemObj, int position) ;
}
}
这里是使用办法:
recyclerView.setAdapter(new BaseRecyclerAdapter<Character>(R.layout.item_data_list, infos) {
@Override
public void convertView(ViewHolder viewHolder, Character item, int position) {
viewHolder.setText(R.id.order, item.getOrder() + "")
.setText(R.id.name, item.getName())
.setOnClickListener(R.id.name, view -> showToast(say))
.setText(R.id.old, item.getOld())
.setText(R.id.sex, item.getSex())
.setText(R.id.from, item.getFrom());
}
});
是不是简单多了,创建的时候将泛型类型、布局、数据传进去,重写方法里面绑定数据就可以了。
下面是通用的 view holder,网上应该很多地方能找到:
public class ViewHolder extends RecyclerView.ViewHolder {
private View mConvertView;
private SparseArray<View> mViews;
/**
*
* @param itemView
* itemView
*/
public ViewHolder(View itemView) {
super(itemView);
mViews = new SparseArray<>() ;
mConvertView = itemView ;
}
/**
*
* @param view
* view
* @return
* holder
*/
public static ViewHolder create(View view) {
return new ViewHolder(view);
}
/**
* 根据资源获取View对象
* @param res
* 控件ID
* @param <T>
* 类型
* @return
* 控件
*/
public <T extends View> T getView(@IdRes int res) {
View view = mViews.get(res);
if (view == null) {
view = mConvertView.findViewById(res) ;
mViews.put(res,view);
}
return (T) view;
}
/**
* 提供TextView和Button设置文本简化操作
* @param idRes
* 控件ID
* @param charSequence
* 字符串
* @return
* holder
*/
public ViewHolder setText(@IdRes int idRes , CharSequence charSequence) {
View view = getView(idRes);
if (view instanceof TextView) {
((TextView)view).setText(charSequence);
}else if (view instanceof Button) {
((Button)view).setText(charSequence);
}
return this ;
}
/**
* 提供TextView和Button设置文本颜色简化操作
* @param idRes
* 控件ID
* @param color
* 颜色
* @return
* holder
*/
public ViewHolder setTextColor(@IdRes int idRes , int color) {
View view = getView(idRes);
if (view instanceof TextView) {
((TextView)view).setTextColor(color);
}else if (view instanceof Button) {
((Button)view).setTextColor(color);
}
return this ;
}
/**
* 设置指定ViewId的背景颜色
* @param idRes
* 控件ID
* @param color
* 颜色
* @return
* holder
*/
public ViewHolder setBackgroundColor(@IdRes int idRes , int color) {
View view = getView(idRes);
view.setBackgroundColor(color);
return this;
}
/**
* 设置指定ViewId的可见度
* @param idRes
* 控件ID
* @param visibility
* 可见度
* @return
* holder
*/
public ViewHolder setVisibility(@IdRes int idRes , @DrawableRes int visibility) {
View view = getView(idRes);
view.setVisibility(visibility);
return this ;
}
/**
* 设置ImageView显示图片
* @param idRes
* 控件ID
* @param res
* 图片路径
* @return
* holder
*/
public ViewHolder setImageResource(@IdRes int idRes , @DrawableRes int res) {
View view = getView(idRes);
if (view instanceof ImageView) {
((ImageView)view).setImageResource(res);
}
return this ;
}
/**
* 设置指定控件ID的点击事件
* @param idRes
* 控件ID
* @param listener
* 监听接口
* @return
* holder
*/
public ViewHolder setOnClickListener(@IdRes int idRes , View.OnClickListener listener) {
View view = getView(idRes);
view.setOnClickListener(listener);
return this;
}
/**
* 设置指定控件ID的长按事件
* @param idRes
* 控件ID
* @param listener
* 监听接口
* @return
* holder
*/
public ViewHolder setOnLongClickListener(@IdRes int idRes , View.OnLongClickListener listener) {
View view = getView(idRes);
view.setOnLongClickListener(listener);
return this;
}
/**
* 设置指定控件的TAG
* @param idRes
* 控件ID
* @param tag
* tag
* @return
* holder
*/
public ViewHolder setTag(@IdRes int idRes , Object tag) {
View view = getView(idRes);
view.setTag(tag);
return this;
}
/**
* 获取指定控件的TAG
* @param idRes
* 控件ID
* @return
* holder
*/
public Object getTag(@IdRes int idRes) {
View view = getView(idRes);
return view.getTag() ;
}
}
简单讲讲
还记得我们之前要重写的 onCreateViewHolder、onBindViewHolder、getItemCount 函数吗?onCreateViewHolder 根据布局创建 ViewHolder,我们这根据传进去的布局 id,创建了一个通用的 view holder;onBindViewHolder 函数绑定数据到布局,这里我们取得数据,调用了一个抽象函数去实现,这个抽象函数就是我们继承的时候要写的,在里面绑定数据;getItemCount 函数返回数据数目,数据传进去了可以直接获得数目返回。至于外层的点击事件,很简单,不必多说。
通用的 ViewHolder 提供了很多绑定数据到布局的方法,例如文字、背景、点击事件等,也可以自己增加,类似写就可以,很简单。
多类型 Adapter
如果是简单使用 RecyclerView 上面的适配器应该是满足要求了,但是如果需要使用多类型呢?这个需求也很常见,下面看简化的多类型适配器:
public abstract class BaseMultiRecyclerAdapter extends RecyclerView.Adapter<ViewHolder> {
//需要存储不同类型的对象
private final List<Object> mItems;
//点击事件回调接口
private ItemClickListener mItemClickListener ;
private ItemLongClickListnener mItemLongClickListnener ;
public BaseMultiRecyclerAdapter(List<Object> items) {
// 如果没有传入的数据 ,则自动创建一个空的集合 ,防止报空指针
this.mItems = items == null ? new ArrayList<>() : items;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
//获取资源文件id
int mItemLayoutRes = convertType(viewType);
//根据给到的不同资源文件id,创建不同的view
View view = LayoutInflater.from(parent.getContext()).inflate(mItemLayoutRes, parent, false);
//根据view创建多功能view holder
ViewHolder viewHolder = new ViewHolder(view);
// 初始化Item事件监听
initOnItemListener(viewHolder);
return viewHolder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position){
//所有对象都继承自object
Object o = mItems.get(position);
//绑定数据到布局
convertView(holder, o, getItemViewType(position));
}
@Override
public int getItemCount(){
return mItems.size();
}
//将重写getItemViewType获得的类型转换成布局
public abstract @LayoutRes int convertType(int viewType);
//关键--外部将数据绑定到布局去
public abstract void convertView(ViewHolder viewHolder, Object itemObj, int viewType);
//设置点击事件
private void initOnItemListener(final RecyclerView.ViewHolder holder) {
if (mItemClickListener != null) {
//根布局点击事件
holder.itemView.setOnClickListener(v -> {
Object o = mItems.get(holder.getLayoutPosition());
mItemClickListener.onItemClick(v,o ,holder.getLayoutPosition());
});
}
if (mItemLongClickListnener != null) {
//根布局长按事件
holder.itemView.setOnLongClickListener(v -> {
Object o = mItems.get(holder.getLayoutPosition());
mItemLongClickListnener.onItemLongClick(v,o,holder.getLayoutPosition());
return true;
});
}
}
public void setOnItemClickListener(ItemClickListener listener) {
mItemClickListener = listener ;
}
public void setOnItemLongClickListener(ItemLongClickListnener listener) {
mItemLongClickListnener = listener ;
}
public interface ItemClickListener {
void onItemClick(View view , Object itemObj , int position) ;
}
public interface ItemLongClickListnener {
void onItemLongClick(View view ,Object itemObj, int position) ;
}
}
下面是使用:
final BaseMultiRecyclerAdapter adapter = new BaseMultiRecyclerAdapter(datas) {
@Override
public int getItemViewType(int position) {
Object item = datas.get(position);
if (item instanceof People) return 0;
if (item instanceof Character) return 1;
if (item instanceof Monster) return 2;
return 0;
}
@Override
public int convertType(int viewType) {
switch (viewType){
case 0:
return R.layout.item_people_list;
case 1:
return R.layout.item_data_list;
case 2:
return R.layout.item_monster_list;
default:
}
return R.layout.item_people_list;
}
@Override
public void convertView(ViewHolder viewHolder, Object itemObj, int viewType) {
switch (viewType){
case 0:
viewHolder.setText(R.id.people_sex, ((People)itemObj).getSex());
break;
case 1:
viewHolder.setText(R.id.order, ((Character)itemObj).getOrder() + "");
viewHolder.setText(R.id.name, ((Character)itemObj).getName());
viewHolder.setText(R.id.old, ((Character)itemObj).getOld());
viewHolder.setText(R.id.sex, ((Character)itemObj).getSex());
viewHolder.setText(R.id.from, ((Character)itemObj).getFrom());
final String say = "我叫" + ((Character)itemObj).getName() + "请多关照!";
viewHolder.setOnClickListener(R.id.name, view -> showToast(say));
break;
case 2:
viewHolder.setText(R.id.monster_name, ((Monster)itemObj).getName());
viewHolder.setText(R.id.monster_belong, ((Monster)itemObj).getBelong());
viewHolder.setText(R.id.monster_property, ((Monster)itemObj).getProperty());
break;
default:
}
}
};
multiList.setAdapter(adapter);
简单讲讲
-
储存不同类型对象
这里我们要储存不同类型的对象,所以数据的 list 不能再使用泛型了,即使是通配符 ? ,也是不行的,因为只要加入一个数据,整个 list 的类型就确定了。所以这里使用了 Object 作为数据类型,所有对象都继承自 Object。
-
传入不同资源布局
因为我们的数据有多种类型,对应的布局可能也有多种类型,我们要根据不同的数据创建不同的 view holder对象。这里 onCreateViewHolder 方法中已经给到了 viewType,即应该创建的 view 的类型,但是需要我们重写 RecyclerView.Adapter 已经有的 getItemViewType 函数,在 getItemViewType 中根据数据返回对应的类型,拿到 viewType 了,我们就能创建不同的 view 及 view holder 了。
总结一下就是要我们自己判断下类型(getItemViewType),再拿着类型去绑定数据。
-
根据不同类型绑定数据
和前面的绑定数据类似,但是我们绑定数据到布局也需要分类型,这里再次调用了 getItemViewType 函数,并将得到的 viewType 传递出去。绑定数据的时候,数据是 Object 类型的,根据 viewType 类型转换就可以了。
列表展开及折叠
使用多类型列表的地方很有可能会用到折叠、展开功能,像什么使用说明啊,下面是我很久之前写的一个辅助类,其实上面的代码也是刚毕业的时候写的,现在改了改,下面看折叠展开的辅助类:
public class ThreeLayerListHelper {
//按节点整理数据,可以展开多条数据
private static final List<Object> openedDatas = new ArrayList<>();//已经展开的节点
public static void sortDatasByNode(List<Object> datas, Object itemObj, List<? extends HeadDataBean> headDatas, List<? extends MediumDataBean> mediumDatas, List<? extends TailDataBean> tailDatas){
if (openedDatas.contains(itemObj)){
removeChildDataByNode(datas, itemObj, headDatas, mediumDatas, tailDatas);
openedDatas.remove(itemObj);
}else {
addChildDataByNode(datas, itemObj, headDatas, mediumDatas, tailDatas);
openedDatas.add(itemObj);
}
}
//按节点增加数据
private static void addChildDataByNode(List<Object> datas, Object itemObj, List<? extends HeadDataBean> headDatas, List<? extends MediumDataBean> mediumDatas, List<? extends TailDataBean> tailDatas){
List<Object> increments = new ArrayList<>();
//在第一层增加
if (itemObj instanceof HeadDataBean){
//获取前向属性
String headProperty = ((HeadDataBean)itemObj).getHeadProperty();
for (MediumDataBean mediumDataBean : mediumDatas){
if (mediumDataBean.getHeadProperty().equals(headProperty)){
increments.add(mediumDataBean);
}
}
}
//在第二层增加
if (itemObj instanceof MediumDataBean){
//后向属性
String footProperty = ((MediumDataBean)itemObj).getFootProperty();
for (TailDataBean tailDataBean : tailDatas){
if (tailDataBean.getFootProperty().equals(footProperty)){
increments.add(tailDataBean);
}
}
}
//执行插入操作
List<Object> temp = new ArrayList<>();
for (Object data : datas){
temp.add(data);
//当前点后面加入
if (data.equals(itemObj)){
temp.addAll(increments);
}
}
datas.clear();
datas.addAll(temp);
}
//按节点删除数据
private static void removeChildDataByNode(List<Object> datas, Object itemObj, List<? extends HeadDataBean> headDatas, List<? extends MediumDataBean> mediumDatas, List<? extends TailDataBean> tailDatas){
List<Object> decrements = new ArrayList<>();
//在第一层查找
if (itemObj instanceof HeadDataBean){
//获取前向属性
String headProperty = ((HeadDataBean)itemObj).getHeadProperty();
for (MediumDataBean mediumDataBean : mediumDatas){
if (mediumDataBean.getHeadProperty().equals(headProperty)){
decrements.add(mediumDataBean);
//该第二层对应的第三层同样应该需要移除
String footProperty = mediumDataBean.getFootProperty();
for (TailDataBean tailDataBean : tailDatas){
if (tailDataBean.getFootProperty().equals(footProperty)){
decrements.add(tailDataBean);
}
}
}
}
}
//在第二层查找
if (itemObj instanceof MediumDataBean){
//后向属性
String footProperty = ((MediumDataBean)itemObj).getFootProperty();
for (TailDataBean tailDataBean : tailDatas){
if (tailDataBean.getFootProperty().equals(footProperty)){
decrements.add(tailDataBean);
}
}
}
//执行移除操作,不需要找节点
List<Object> temp = new ArrayList<>();
for (Object data : datas){
//判断每一个data值是否在消除数组中
boolean isInDecrementsFlag = false;
for (Object decrement : decrements){
if (decrement.equals(data)){
isInDecrementsFlag = true;
}
}
if (!isInDecrementsFlag){
temp.add(data);
}
}
datas.clear();
datas.addAll(temp);
}
//能用,但是属于硬搞,可以忽略,我自己也看不懂了
private static String passedFirstLayerProperty = "";
private static String passedSecondLayerProperty = "";
public static void sortDatas(List<Object> datas, Object itemObj, List<? extends HeadDataBean> headDatas, List<? extends MediumDataBean> mediumDatas, List<? extends TailDataBean> tailDatas){
List<Object> tempdatas = new ArrayList<>();
if (itemObj instanceof HeadDataBean){
//获取前向属性
String headProperty = ((HeadDataBean)itemObj).getHeadProperty();
//二次点击事件
if (headProperty.equals(passedFirstLayerProperty)){
datas.clear();
datas.addAll(headDatas);
passedFirstLayerProperty = "";
passedSecondLayerProperty = "";
return;
}else {
passedFirstLayerProperty = headProperty;
passedSecondLayerProperty = "";
}
//第一层
for (HeadDataBean headDataBean : headDatas) {
//先加后判断
tempdatas.add(headDataBean);
if (headDataBean.getHeadProperty().equals(headProperty)) {
//第二层
for (MediumDataBean mediumDataBean : mediumDatas) {
//先判断后加
if (mediumDataBean.getHeadProperty().equals(headProperty)) {
tempdatas.add(mediumDataBean);
}
}
}
}
}
if (itemObj instanceof MediumDataBean){
//前向属性
String headProperty = ((MediumDataBean)itemObj).getHeadProperty();
//后向属性
String footProperty = ((MediumDataBean)itemObj).getFootProperty();
//二次点击事件
boolean thirdLayerDisplayFlag = true;
if (footProperty.equals(passedSecondLayerProperty)){
thirdLayerDisplayFlag = false;
passedSecondLayerProperty = "";
}else {
//passedFirstLayerProperty = "";
passedSecondLayerProperty = footProperty;
}
//第一层
for (HeadDataBean headDataBean : headDatas){
//先加后判断
tempdatas.add(headDataBean);
if (headDataBean.getHeadProperty().equals(headProperty)){
//第二层
for (MediumDataBean mediumDataBean : mediumDatas){
//先判断后加
if (mediumDataBean.getHeadProperty().equals(headProperty)){
tempdatas.add(mediumDataBean);
//转折处
if (mediumDataBean.getFootProperty().equals(footProperty)){
//第三层
for (TailDataBean tailDataBean : tailDatas){
if (tailDataBean.getFootProperty().equals(footProperty) && thirdLayerDisplayFlag){
tempdatas.add(tailDataBean);
}
}
}
}
}
}
}
}
if (itemObj instanceof TailDataBean){
tempdatas.addAll(datas);
}
datas.clear();
datas.addAll(tempdatas);
}
}
这里写的非常不行,还得依赖三个类型:
//一级标题
public class HeadDataBean {
private String headProperty;
public String getHeadProperty() {
return headProperty;
}
public void setHeadProperty(String headProperty) {
this.headProperty = headProperty;
}
}
//二级标题
public class MediumDataBean {
private String headProperty;
private String footProperty;
public String getHeadProperty() {
return headProperty;
}
public void setHeadProperty(String headProperty) {
this.headProperty = headProperty;
}
public String getFootProperty() {
return footProperty;
}
public void setFootProperty(String footProperty) {
this.footProperty = footProperty;
}
}
//三级标题
public class TailDataBean {
private String footProperty;
public String getFootProperty() {
return footProperty;
}
public void setFootProperty(String footProperty) {
this.footProperty = footProperty;
}
}
下面是使用:
//注意使用viewholder注册点击事件,第一项无点击效果,即点击事件在第一项中注册
adapter.setOnItemClickListener((view, itemObj, position) -> {
//方法一,直接数据操作,只能展开一项数据(不推荐)
ThreeLayerListHelper.sortDatas(datas, itemObj, peoples, characters, monsters);
//方法二,通过节点形式增删数据,能展开多项(推荐)
//ThreeLayerListHelper.sortDatasByNode(datas, itemObj, peoples, characters, monsters);
//做一些其他操作 - 例如:第三项Monster提示语
if (itemObj instanceof Monster){
showToast("这是" + ((Monster)itemObj).getBelong()
+ "的" + ((Monster)itemObj).getName());
}
adapter.notifyDataSetChanged();
});
简单讲讲
这里就讲讲那个按结点增加删除子列表数据的方法,另一个我也看不懂了。
实际就是类似于指针,每个对象有至少包含一个上一级节点,或一个下一级节点,有一种属于关系,当点击该点的时候,先找到属于它的下一级节点列表,并在它的后面插入数据就可以了。这里还用到了一个 openedDatas 来记录展开的节点,用来判断是展开还是折叠,如果包含该节点就是展开,否则就折叠,并从 openedDatas 中移除该节点。
思路很简单,写的有点复杂,能用就用,不行就参考参考。
结语
RecyclerView 是安卓开发中必不可少的东西,处处用得到,可是有时候,仅仅编写一个简单的列表,就要新建一个 Adapter 类,代码一多,Adapter 遍地都是,有点烦。
虽然 Adapter 的适配器思想很不错,RecyclerView.Adapter 的分层也很合理,可是就是麻烦,每次重写都得写一个 ViewHolder ,再重写 onCreateViewHolder、onBindViewHolder、getItemCount 等函数,实际上我们就是想向布局绑定数据而已。
虽然我觉得我写的适配器不错了,可是使用的话还是先推荐 BRVAH 列表适配器吧!
https://github.com/CymChad/BaseRecyclerViewAdapterHelper
功能类似,但是上面这些代码都是没看 BRVAH 的情况下自己写的,写的不怎么样,如果觉得有帮助但又没看懂的话,可以看我写的一个 demo,已经上传的 GitHub 了:
https://github.com/silencefly96/Utils