这里介绍的是Sticky Section for List
QMUIStickySectionLayout 用于解决两个需求场景:
1.可折叠展开的 section 列表(list/grid)
2.类似 iOS 一样可以在列表(list/grid)滚动过程中悬浮(sticky)当前的 section header
截图
QMUIStickySectionLayout 的Wiki很简略难以看懂
开始
导入库我们就不说了直接上重点
1.通过XML代码构造QMUIStickySectionLayout实例:
<com.qmuiteam.qmui.widget.section.QMUIStickySectionLayout
android:id="@+id/section_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
2. 准备好两个数据模型,头部和脚部
Header
import cn.jiulang.efficiency.UIcomponent.UINote.AccountBook.Tree.ItemBase.SectionHeader;
import com.qmuiteam.qmui.widget.section.QMUISection;
public class SectionHeader implements QMUISection.Model<SectionHeader> {
private String tvWeek;
private String tvDate;
private String tvTotalAmount;
public SectionHeader(String name1, String name2, String name3) {
this.tvWeek = name1;
this.tvDate = name2;
this.tvTotalAmount = name3;
}
public String getWeek() {
return tvWeek;
}
public String getDate() {
return tvDate;
}
public String getTotalAmount() {
return tvTotalAmount;
}
@Override
public SectionHeader cloneForDiff() {
return new SectionHeader(getWeek(), getDate(), getTotalAmount());
}
@Override
public boolean isSameItem(SectionHeader other) {
return tvWeek == other.tvWeek || (tvWeek != null && tvWeek.equals(other.tvWeek))
&& tvDate == other.tvDate || (tvDate != null && tvDate.equals(other.tvDate))
&& tvTotalAmount == other.tvTotalAmount || (tvTotalAmount != null && tvTotalAmount.equals(other.tvTotalAmount));
}
@Override
public boolean isSameContent(SectionHeader other) {
return true;
}
}
Item
public class SectionItem implements QMUISection.Model<SectionItem> {
private String tvTag;
private String tvNote;
private String tvAccount;
public SectionItem(String name1, String name2, String name3) {
this.tvTag = name1;
this.tvNote = name2;
this.tvAccount = name3;
}
public String getTag() {
return tvTag;
}
public String getNote() {
return tvNote;
}
public String getAccount() {
return tvAccount;
}
@Override
public SectionItem cloneForDiff() {
return new SectionItem(getTag(), getNote(), getAccount());
}
@Override
public boolean isSameItem(SectionItem other) {
return //tvTag == other.tvTag || (tvTag != null && tvTag.equals(other.tvTag))
// && tvNote == other.tvNote || (tvNote != null && tvNote.equals(other.tvNote))
tvAccount == other.tvAccount || (tvAccount != null && tvAccount.equals(other.tvAccount));
}
@Override
public boolean isSameContent(SectionItem other) {
return true;
}
}
QMUISection.Model 的接口 isSameItem 与 isSameContent 的概念都来源于 DiffUtil, 如果对 DiffUtil还不了解的话, 可以去官网查看与学习。
在 QMUIDemo 中,提供了 SectionHeader 和 SectionItem 的示例,可点击查看。
在准备好这两个 model 后,我们就可以构建 adapter 需要用到的 QMUISection<H, T> 了, 其中 H、T 就是之前准备的 SectionHeader 和 SectionItem。
3.构建Adapter
public class QDListSectionAdapter extends QMUIDefaultStickySectionAdapter<SectionHeader, SectionItem> {
private TextView tvTag ,tvNote,tvAccount;
@NonNull
@Override
protected ViewHolder onCreateSectionHeaderViewHolder(@NonNull ViewGroup viewGroup) {
return new ViewHolder(new QDSectionHeaderView(viewGroup.getContext()));
}
public QDListSectionAdapter() {
}
public QDListSectionAdapter(boolean removeSectionTitleIfOnlyOneSection) {
super(removeSectionTitleIfOnlyOneSection);
}
@NonNull
@Override
protected ViewHolder onCreateSectionItemViewHolder(@NonNull ViewGroup viewGroup) {
Context context = viewGroup.getContext();
//这里是通过xml绑定布局,当然你也可以用代码new出来,方法请看头部视图
View itemView = LayoutInflater.from(context).inflate(R.layout.note_rv_tree_second, viewGroup, false);
return new ViewHolder(itemView);
}
@Override
protected void onBindSectionHeader(final ViewHolder holder, final int position, QMUISection<SectionHeader, SectionItem> section) {
QDSectionHeaderView itemView = (QDSectionHeaderView) holder.itemView;
itemView.render(section.getHeader(), section.isFold());
//点击事件
itemView.getArrowView().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int pos = holder.isForStickyHeader ? position : holder.getAdapterPosition();
toggleFold(pos, false);
}
});
}
@Override
protected void onBindSectionItem(ViewHolder holder, int position, QMUISection<SectionHeader, SectionItem> section, int itemIndex) {
//获取id
tvTag = holder.itemView.findViewById(R.id.Tag_text);
//设置文本
tvTag.setText(section.getItemAt(itemIndex).getTag());
tvNote = holder.itemView.findViewById(R.id.Note_text);
tvNote.setText(section.getItemAt(itemIndex).getNote());
tvAccount = holder.itemView.findViewById(R.id.Account_text);
tvAccount.setText(section.getItemAt(itemIndex).getAccount());
}
}
创建头部视图(不然会报空指针)
public class QDSectionHeaderView extends RelativeLayout {
private TextView tvWeek,tvDate,tvTotalAmount;
private ImageView mArrowView; private int headerHeight = QMUIDisplayHelper.dp2px(getContext(), 56);
public QDSectionHeaderView(Context context) {
this(context, null);
}
public QDSectionHeaderView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
setOrientation(LinearLayout.HORIZONTAL);
setGravity(Gravity.CENTER_VERTICAL);
setBackgroundColor(Color.WHITE);
int paddingHor = QMUIDisplayHelper.dp2px(context, 24);
mTitleTv = new TextView(getContext());
mTitleTv.setTextSize(20);
mTitleTv.setTextColor(Color.BLACK);
mTitleTv.setPadding(paddingHor, 0, paddingHor, 0);
addView(mTitleTv, new LinearLayout.LayoutParams(
0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f));
mArrowView = new AppCompatImageView(context);
mArrowView.setImageDrawable(QMUIResHelper.getAttrDrawable(getContext(),
R.attr.qmui_common_list_item_chevron));
mArrowView.setScaleType(ImageView.ScaleType.CENTER);
addView(mArrowView, new LinearLayout.LayoutParams(headerHeight, headerHeight));tvWeek = new TextView(getContext());tvWeek.setTextSize(21);
tvWeek.setTextColor(Color.BLACK);
RelativeLayout.LayoutParams rlWeek = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
rlWeek.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
rlWeek.addRule(RelativeLayout.CENTER_VERTICAL);
layout.addView(tvWeek, rlWeek);
tvDate = new TextView(getContext());
tvDate.setTextSize(16);
tvDate.setTextColor(Color.BLACK);
RelativeLayout.LayoutParams rlDate = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
//与父组件顶部对齐
rlDate.addRule(RelativeLayout.ALIGN_PARENT_TOP);
//横向居中
rlDate.addRule(RelativeLayout.CENTER_HORIZONTAL);
layout.addView(tvDate, rlDate);
tvTotalAmount = new TextView(getContext());
tvTotalAmount.setTextSize(19);
tvTotalAmount.setTextColor(Color.BLACK);
RelativeLayout.LayoutParams rlTotalAmount = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
rlTotalAmount.addRule(RelativeLayout.LEFT_OF, mArrowView.getId());
rlTotalAmount.addRule(RelativeLayout.CENTER_VERTICAL);
layout.addView(tvTotalAmount, rlTotalAmount);
}
public ImageView getArrowView() {
return mArrowView;
}
public void render(SectionHeader header, boolean isFold) {
mTitleTv.setText(header.getText());
mArrowView.setRotation(isFold ? 0f : 90f);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec,
MeasureSpec.makeMeasureSpec(headerHeight, MeasureSpec.EXACTLY));
}
adaper就写好了
接下来就初始化就行了
private QMUIStickySectionLayout mSectionLayout;
private RecyclerView.LayoutManager mLayoutManager;
protected static QMUIStickySectionAdapter<SectionHeader, SectionItem, QMUIStickySectionAdapter.ViewHolder> mAdapter;
private void initAdapter() {
mLayoutManager = createLayoutManager();
mSectionLayout.setLayoutManager(mLayoutManager);
mAdapter = createAdapter();
mSectionLayout.setAnimation(AnimationUtils.loadAnimation(getContext(), R.anim.aword_fade_enter));
}
private void initAdapterLogicalOperation() {
mAdapter.setCallback(new QMUIStickySectionAdapter.Callback<SectionHeader, SectionItem>() {
@Override
public void loadMore(final QMUISection<SectionHeader, SectionItem> section, final boolean loadMoreBefore) {
}
//点击事件
@Override
public void onItemClick(QMUIStickySectionAdapter.ViewHolder holder, int position) {
if (holder.getItemViewType() != 1) {
int pos = holder.isForStickyHeader ? position : holder.getAdapterPosition();
mAdapter.toggleFold(pos, false);
}
// Toast.makeText(getContext(), "click item " + mAdapter.getItemIndex(position), Toast.LENGTH_SHORT).show();
}
//长按事件
@Override
public boolean onItemLongClick(QMUIStickySectionAdapter.ViewHolder holder, int position) {
int FirstPos = mAdapter.getSectionIndex(position);
int SecondPos =mAdapter.getItemIndex(position);
// ItemPopupWindow.show(getContext(), holder.itemView, holder.getItemViewType(), FirstPos, SecondPos);
// Toast.makeText(getContext(), "一级条目" +FirstPos+ "二级"+ SecondPos, Toast.LENGTH_SHORT).show();
return true;
}
});
mSectionLayout.setAdapter(mAdapter, true);
mAdapter.setData(getList(0));
}
protected QMUIStickySectionAdapter<SectionHeader, SectionItem, QMUIStickySectionAdapter.ViewHolder> createAdapter() {
return new QDListSectionAdapter(true);
}
protected RecyclerView.LayoutManager createLayoutManager() {
return new LinearLayoutManager(getContext()) {
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new RecyclerView.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
};
}
最后加载数据
模拟加载
private static ArrayList<QMUISection<SectionHeader, SectionItem>> getList() {
ArrayList<QMUISection<SectionHeader, SectionItem>> list = new ArrayList<>();
int n = 0;
for (int i=0;i < 5;i++) {
SectionHeader header = new SectionHeader("周末"+i,"时间"+ i,"金额"+ i);
ArrayList<SectionItem> contents = new ArrayList<>();
for (int j = 0; j <20/* 二级条目辅助数据*/; j++) {
contents.add(new SectionItem("标签"+n, "备注"+n, "金额"+n));
n++;
}
QMUISection<SectionHeader, SectionItem> section = new QMUISection<>(header, contents, i != unfold);
list.add(section);
}
return list;
}
创作不易点个赞吧!