QMUIStickySectionLayout使用方法

这里介绍的是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;
}
创作不易点个赞吧!
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342