首先,不上图就是耍流氓!
项目中要做图中类似的效果。
方案一:
RecyclerView嵌套RecyclerView
缺点:页面卡顿
结果:放弃该方案
方案二:
重写适配器
缺点:需要做数据处理(可接受)
优点:顺滑无比,不用嵌套,不用修改recyclerview
结果:使用该方案
使用过程中,愈发感觉RecyclerView的强大,服,是真服!
借鉴了github的开源项目,在此对作者表示感谢。
地址:SectionedRecyclerView
开始使用
RecyclerView照常写就好,这里需要注意的Adapter。先说一下整个设计思路:在adapter 设置分组数getSectionCount , 设置每一组的个数getItemCountForSection,根据这个关键数据,把header和footer视为一个单项,计算出总共的单项数目,然后根据不同的类型,header,footer,item,让GridLayoutManager的getSpanSize不同,即可达到效果。
连个关键
一:在于记录每一个单项(包括头部和底部单项的 )详细属性,
1,是头部,正常,还是底部
2,在哪一个分组,又在分组的第一个位置
二:当是header或者footer是,将GridLayoutManger的spanSize设为全屏
GridLayoutManager layoutManager = new GridLayoutManager(getContext(), 5);
SectionedSpanSizeLookup lookup = new SectionedSpanSizeLookup(mAdapter, layoutManager);
layoutManager.setSpanSizeLookup(lookup);
来看下SectionedSpanSizeLookup的实现,这个就是用来处理每一项显示的宽度大小的,神奇的东西
public class SectionedSpanSizeLookup extends GridLayoutManager.SpanSizeLookup {
protected SectionedRecyclerViewAdapter<?, ?, ?> adapter = null;
protected GridLayoutManager layoutManager = null;
public SectionedSpanSizeLookup(SectionedRecyclerViewAdapter<?, ?, ?> adapter, GridLayoutManager layoutManager) {
this.adapter = adapter;
this.layoutManager = layoutManager;
}
@Override
public int getSpanSize(int position) {
if(adapter.isSectionHeaderPosition(position) || adapter.isSectionFooterPosition(position)){
return layoutManager.getSpanCount();
}else{
return 1;
}
}
}
下面来看一下Adapter的实现,
public class RoomPreAdapter2 extends SimpleSectionedAdapter2<RoomPreAdapter2.RoomHodler> {
private Context context;
@Override
protected String getSectionHeaderTitle(int section) {
return section + "#";
}
/** * 获取分组数 * @return */
@Override
protected int getSectionCount() {
return 10;
}
/** * 获取指定组的个数 * @param section * @return */
@Override
protected int getItemCountForSection(int section) {
return section + 6;
}
@Override
protected RoomHodler onCreateItemViewHolder(ViewGroup parent, int viewType) {
context = parent.getContext();
View itemView = LayoutInflater.from(context).inflate(R.layout.item_room, parent, false);
return new RoomHodler(itemView);
}
@Override
protected void onBindItemViewHolder(RoomHodler holder, int section, final int position) {
holder.rb_room.setText(section + "-" + position);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context, "点了" + position, Toast.LENGTH_SHORT).show();
}
});
holder.rb_room.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Toast.makeText(context, "选了" + position, Toast.LENGTH_SHORT).show();
}
});
}
class RoomHodler extends RecyclerView.ViewHolder {
RadioButton rb_room;
public RoomHodler(View itemView) {
super(itemView);
rb_room = (RadioButton) itemView.findViewById(R.id.rb_room);
}
}
}
这个没什么讲的,就是普通的用法,注意两个方法,getSectionCount()和getItemCountForSection(),还有一个方法getSectionHeaderTitle()用于设置显示的分组名,显然父类中已经进行了完美的封装,下面我们来看父类
public abstract class SimpleSectionedAdapter2<VH extends RecyclerView.ViewHolder> extends SectionedRecyclerViewAdapter<HeaderViewHolder,
VH, RecyclerView.ViewHolder> {
/** * 是否显示每一个分组的底部 * @param section * @return */
@Override
protected boolean hasFooterInSection(int section) {
return true;
}
/* 创建头部header的ViewHolder */
@Override
protected HeaderViewHolder onCreateSectionHeaderViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(getLayoutResource(), parent, false);
HeaderViewHolder holder = new HeaderViewHolder(view, getTitleTextID());
return holder;
}
/* 创建底部footer的ViewHolder */
@Override
protected RecyclerView.ViewHolder onCreateSectionFooterViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(getFooterLayoutResource(), parent, false);
FooterViewHolder holder = new FooterViewHolder(view);
return holder;
}
private int getFooterLayoutResource() {
return R.layout.item_room_footer;
}
@Override
protected void onBindSectionHeaderViewHolder(HeaderViewHolder holder, int section) {
String title = getSectionHeaderTitle(section);
holder.render(title);
}
@Override
protected void onBindSectionFooterViewHolder(RecyclerView.ViewHolder holder, int section) {
}
/** * Provides a layout identifier for the header. Override it to change the appearance of the * header view. */
protected
@LayoutRes
int getLayoutResource() {
return R.layout.item_room_header;
}
/** * Provides the identifier of the TextView to render the section header title. Override it if * you provide a custom layout for a header. */
protected
@IdRes
int getTitleTextID() {
return R.id.tv_group_name;
}
/** * Returns the title for a given section */
protected abstract String getSectionHeaderTitle(int section);
/** * 底部Footer的ViewHolder */
class FooterViewHolder extends RecyclerView.ViewHolder{
public FooterViewHolder(View itemView) {
super(itemView);
}
}
}
而这个父类这是封装了header和footer的显示,至于header和footer是如何判断?以及如何 根据分组数和每个分组的个数 绘制出我们要的效果?这些问题还得再找父类,找的爷爷辈,别急,继续看,
public abstract class SectionedRecyclerViewAdapter<H extends RecyclerView.ViewHolder, VH extends RecyclerView.ViewHolder, F extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
protected static final int TYPE_SECTION_HEADER = -1;
protected static final int TYPE_SECTION_FOOTER = -2;
protected static final int TYPE_ITEM = -3;
//用四个数组,对应保存每一个单项(包括header和footer)的四个属性
//1.哪个分组;2.在该组的位置;3.是否是头部;4.是否是底部;
private int[] sectionForPosition = null;
private int[] positionWithinSection = null;
private boolean[] isHeader = null;
private boolean[] isFooter = null;
private int count = 0;
public SectionedRecyclerViewAdapter() {
super();
registerAdapterDataObserver(new SectionDataObserver());
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
setupIndices();
}
/** * Returns the sum of number of items for each section plus headers and footers if they * are provided. */
@Override
public int getItemCount() {
return count;
}
private void setupIndices(){
count = countItems();
allocateAuxiliaryArrays(count);
precomputeIndices();
}
private int countItems() {
int count = 0;
int sections = getSectionCount();
//计算总个数
for(int i = 0; i < sections; i++){
count += 1 + getItemCountForSection(i) + (hasFooterInSection(i) ? 1 : 0);
}
return count;
}
//为每一个单项,设置四大属性
private void precomputeIndices(){
int sections = getSectionCount();
int index = 0;
for(int i = 0; i < sections; i++){
setPrecomputedItem(index, true, false, i, 0);
index++;
for(int j = 0; j < getItemCountForSection(i); j++){
setPrecomputedItem(index, false, false, i, j);
index++;
}
if(hasFooterInSection(i)){
setPrecomputedItem(index, false, true, i, 0);
index++;
}
}
}
private void allocateAuxiliaryArrays(int count) {
sectionForPosition = new int[count];
positionWithinSection = new int[count];
isHeader = new boolean[count];
isFooter = new boolean[count];
}
private void setPrecomputedItem(int index, boolean isHeader, boolean isFooter, int section, int position) {
this.isHeader[index] = isHeader;
this.isFooter[index] = isFooter;
sectionForPosition[index] = section;
positionWithinSection[index] = position;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
RecyclerView.ViewHolder viewHolder;
if(isSectionHeaderViewType(viewType)){
viewHolder = onCreateSectionHeaderViewHolder(parent, viewType);
}else if(isSectionFooterViewType(viewType)){
viewHolder = onCreateSectionFooterViewHolder(parent, viewType);
}else{
viewHolder = onCreateItemViewHolder(parent, viewType);
}
return viewHolder;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
int section = sectionForPosition[position];
int index = positionWithinSection[position];
if(isSectionHeaderPosition(position)){
onBindSectionHeaderViewHolder((H) holder, section);
}else if(isSectionFooterPosition(position)){
onBindSectionFooterViewHolder((F) holder, section);
}else{
onBindItemViewHolder((VH) holder, section, index);
}
}
@Override
public int getItemViewType(int position) {
if(sectionForPosition == null){
setupIndices();
}
int section = sectionForPosition[position];
int index = positionWithinSection[position];
if(isSectionHeaderPosition(position)){
return getSectionHeaderViewType(section);
}else if(isSectionFooterPosition(position)){
return getSectionFooterViewType(section);
}else{
return getSectionItemViewType(section, index);
}
}
protected int getSectionHeaderViewType(int section){
return TYPE_SECTION_HEADER;
}
protected int getSectionFooterViewType(int section){
return TYPE_SECTION_FOOTER;
}
protected int getSectionItemViewType(int section, int position){
return TYPE_ITEM;
}
/** * Returns true if the argument position corresponds to a header */
public boolean isSectionHeaderPosition(int position){
if(isHeader == null){
setupIndices();
}
return isHeader[position];
}
/** * Returns true if the argument position corresponds to a footer */
public boolean isSectionFooterPosition(int position){
if(isFooter == null){
setupIndices();
}
return isFooter[position];
}
protected boolean isSectionHeaderViewType(int viewType){
return viewType == TYPE_SECTION_HEADER;
}
protected boolean isSectionFooterViewType(int viewType){
return viewType == TYPE_SECTION_FOOTER;
}
/** * Returns the number of sections in the RecyclerView */
protected abstract int getSectionCount();
/** * Returns the number of items for a given section */
protected abstract int getItemCountForSection(int section);
/** * Returns true if a given section should have a footer */
protected abstract boolean hasFooterInSection(int section);
/** * Creates a ViewHolder of class H for a Header */
protected abstract H onCreateSectionHeaderViewHolder(ViewGroup parent, int viewType);
/** * Creates a ViewHolder of class F for a Footer */
protected abstract F onCreateSectionFooterViewHolder(ViewGroup parent, int viewType);
/** * Creates a ViewHolder of class VH for an Item */
protected abstract VH onCreateItemViewHolder(ViewGroup parent, int viewType);
/** * Binds data to the header view of a given section */
protected abstract void onBindSectionHeaderViewHolder(H holder, int section);
/** * Binds data to the footer view of a given section */
protected abstract void onBindSectionFooterViewHolder(F holder, int section);
/** * Binds data to the item view for a given position within a section */
protected abstract void onBindItemViewHolder(VH holder, int section, int position);
class SectionDataObserver extends RecyclerView.AdapterDataObserver{
@Override
public void onChanged() {
setupIndices();
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
setupIndices();
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
setupIndices();
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
setupIndices();
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
setupIndices();
}
}