目录
1.简介
2.静态联动效果实现
3.动态联动效果实现
4.无侵入式粘性Header
1.简介
双列表联动,一般由左右两个列表组成,在这个例子中,左侧列表显示分类,右侧列表显示每个分类的具体项,在每一组具体项的顶部由一个header进行区分。
点击左侧列表某一个分类时,右侧列表滚动到对应分类的首个header位置。滑动右侧列表显示下一个或上一个分类的数据时,左侧对应的分类选中状态自动变化。
最后会介绍RecyclerView通过ItemDecoration实现无侵入式的header吸顶效果。
2.静态联动效果实现
这里会把联动效果分为两部分来实现,第一部分先实现静态的联动,这里指的静态联动意思是当点击左侧的分类时右侧直接跳到对应分类的第一个item位置,跳转过程无滑动效果。静态联动效果的实现相对来说需要考虑的状态更少,专注于通用滑动逻辑的抽离,在完成第一步后再改造成动态联动效果即可。
联动列表的关键点:
两个列表中的每一个item都需要绑定对方列表中的关键item位置
假设现在有ABCD四个分类,对于左侧的分类列表来说,A item需要绑定右侧列表中对应A分类的首个item,它的作用是当用户点击左侧A分类的时候,可以拿到右侧列表中需要跳转的对应位置,右侧列表直接跳转到这个绑定的位置即可。
同样假设ABCD四个分类,对于右侧的列表来说,当用户向下滑动列表使B分类的数据到达屏幕可显示的第一个位置时,可以获取到第一个item绑定的左侧分类位置,左侧列表使该位置的item直接出现选中效果即可。
定义联动列表的通用bean,里面只有一个属性bindPosition,代表绑定另一个列表跳转的位置。
public class BaseBindPositionBean {
public int bindPosition; //绑定另一个列表自动跳转位置
}
定义大分类列表BaseTitleAdapter和数据列表BaseValueAdapter,这里定义的Adapter使用了BaseRecyclerViewAdapterHelper库,专注于业务逻辑的编写,不了解的可以到github看看该库。
BaseTitleAdapter定义了一个abstract方法updateSelectedPos(),实现类只要在此位置做item的高亮实现即可。
再看BaseValueAdapter,里面好像什么内容都没有,是的它就是什么内容都没有,定义这个类是为了后面联动工具类中可以使用这个类型做强制转换处理一些通用逻辑。
public abstract class BaseTitleAdapter<T extends BaseBindPositionBean, K extends BaseViewHolder> extends BaseQuickAdapter<T, K> {
public BaseTitleAdapter(int layoutResId, @Nullable List<T> data) {
super(layoutResId, data);
}
/**
* 子类实现该方法做高亮逻辑处理
* 点击title列表某一个item时会进入该回调
* 当value列表滑动时会进入该回调方法
* 需要注意recyclerView onScroll方法滑动时会一直触发,所以在方法内部需要判断pos是否与当前高亮pos相同,相同则不做处理,优化性能
* @param pos 被选中高亮的item位置
*/
public abstract void updateSelectedPos(int pos);
}
public abstract class BaseValueAdapter<T extends BaseBindPositionBean, K extends BaseViewHolder> extends BaseQuickAdapter<T, K> {
public BaseValueAdapter(@Nullable List<T> data) {
super(data);
}
}
下面是两个列表的bean实现类以及adapter实现类,这里拿年份月份两个列表来做示例,实现效果如下图。
title列表的bean实现以及adapter实现,代码非常简单给选中的item加个背景色即可,数据也只有一个年份。Adapter继承了BaseTitleAdapter并实现了updateSelectedPos方法,在方法内做了item高亮选中的处理。
//bean
public class YearTitleBean extends BaseBindPositionBean {
public String year;
}
//adapter
public class YearTitleAdapter extends BaseTitleAdapter<YearTitleBean, BaseViewHolder> {
private String selectedYear;
public YearTitleAdapter(List<YearTitleBean> data) {
super(R.layout.year_item, data);
if (data != null && data.size() > 0) {
selectedYear = data.get(0).year;
}
}
@Override
protected void convert(BaseViewHolder helper, YearTitleBean item) {
String text = item.year + "年";
helper.setText(R.id.year_text, text);
if (item.year.equals(selectedYear)) {
//设置选中
helper.setBackgroundColor(R.id.year_text, mContext.getResources().getColor(android.R.color.white));
} else {
helper.setBackgroundColor(R.id.year_text, mContext.getResources().getColor(android.R.color.transparent));
}
}
public void updateSelectedPos(int pos) {
YearTitleBean item = getItem(pos);
if (item != null && !item.year.equals(selectedYear)) {
selectedYear = item.year;
notifyDataSetChanged();
}
}
}
接下来实现value列表的bean以及adapter。可以看到value列表的子view类型分两种,一种是显示年份的header,一种是显示月份的view,所以定义bean的时候拆了一个HeaderValueBean出来存储bean的类型和年份。对应的拆出了一个HeaderValueAdapter用于处理头部数据显示。
//带有头部的通用HeaderValueBean
public class HeaderValueBean extends BaseBindPositionBean {
public static final int HEADER_TYPE = 0;
public static final int ITEM_TYPE = 1;
public int type; //item类型
public String year; //头部显示年份
}
//value列表的月份bean
public class MonthDataBeanV3 extends HeaderValueBean {
public String month;
//子view类型的构造方法
public MonthDataBeanV3() {
this.type = ITEM_TYPE;
}
//header类型的构造方法
public MonthDataBeanV3(String year) {
this.type = HEADER_TYPE;
this.year = year;
}
}
//ValueAdapter基类,处理header的数据
public abstract class HeaderValueAdapter<T extends HeaderValueBean, K extends BaseViewHolder>
extends BaseValueAdapter<T, K> {
public HeaderValueAdapter(@Nullable List<T> data) {
super(data);
setMultiTypeDelegate(new MultiTypeDelegate<T>() {
@Override
protected int getItemType(T entity) {
//根据你的实体类来判断布局类型
return entity.type;
}
});
getMultiTypeDelegate()
.registerItemType(T.HEADER_TYPE, R.layo