Android 一个RecyclerView实现筛选列表

效果图

choice.gif

简介

如上图展示的内容,筛选条件的功能很常见,一般情况如果条件很多,那么布局文件就会写的很复杂,这篇文章可以提供一个简洁的方案,布局文件只用一个RecyclerView就可以了。当然,处理逻辑可能要少费点功夫,不过这些逻辑可以复用,如果有多个地方用到就省很多事了。

代码分析

先看看Bean类的处理:
数据中有几个必须要添加的属性
type:用于区分是标题item还是内容item,标题item也可以分很多类。
choice:标记item是否选中
multiChoice:这一类型标签是不是多选
allChoice:标签item是不是全选按钮(根据需求调整)

public class GridItemBean {

    /**
     * type:
     * 标题item 自定义
     * 内容item 默认为0
     */
    private int type;
    /**
     * 标题(标题item)
     */
    private String title;

    private String id;
    private String name;
    /**
     * 内容item 是否选择(内容item)
     */
    private boolean choice;
    /**
     * 是否是多选(标题item)
     */
    private boolean multiChoice;
    /**
     * 是否是全选按钮(内容item)
     */
    private boolean allChoice;

    public GridItemBean(int type, String title) {
        this.type = type;
        this.title = title;
    }

    public GridItemBean(int type, String title, boolean multiChoice) {
        this.type = type;
        this.title = title;
        this.multiChoice = multiChoice;
    }

    public GridItemBean(String id, String name) {
        this.id = id;
        this.name = name;
    }

    public GridItemBean(String id, String name, boolean allChoice) {
        this.id = id;
        this.name = name;
        this.allChoice = allChoice;
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isChoice() {
        return choice;
    }

    public void setChoice(boolean choice) {
        this.choice = choice;
    }

    public boolean isMultiChoice() {
        return multiChoice;
    }

    public void setMultiChoice(boolean multiChoice) {
        this.multiChoice = multiChoice;
    }

    public boolean isAllChoice() {
        return allChoice;
    }

    public void setAllChoice(boolean allChoice) {
        this.allChoice = allChoice;
    }

    @Override
    public String toString() {
        return "name='" + name + '\'';
    }
}

既然布局只是一个RecyclerView,那么逻辑都在adapter中了,首先是adapter的ViewHolder,分为两类,标题类和内容标签,根据bean里面的type来区分,这部分没什么要多说的。

class ChoiceAdapter : ChoiceGridAdapter() {

    override fun initData(mData: MutableList<GridItemBean>) {
        this.mData = mData
        notifyDataSetChanged()
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return if (viewType == 0){
            TabHolder(View.inflate(parent?.context , R.layout.item_choice_tab , null))
        }else{
            TitleHolder(View.inflate(parent?.context , R.layout.item_choice_title , null))
        }
    }

    override fun getItemCount(): Int {
        return mData.size
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        if (getItemViewType(position) == 0){
            (holder as TabHolder).bindData(position)
        }else{
            (holder as TitleHolder).bindData(position)
        }
    }

    inner class TabHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
        fun bindData(position: Int)= with(itemView){
            var data = mData[position]
            tv_item_tab?.text = data.name
            if (data.isChoice){
                tv_item_tab?.setBackgroundResource(R.mipmap.search_label_bg_sel02)
            }else{
                tv_item_tab?.setBackgroundResource(R.mipmap.search_label_bg_nor)
            }
            setOnClickListener {
                if(data.isChoice){
                    mData[position].isChoice = false
                    notifyItemChanged(position)
                }else{
                    changeStatus(position)
                }
            }
        }

    }

    inner class TitleHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
        fun bindData(position: Int) = with(itemView){
            tv_item_title?.text = mData[position].title
        }

    }

}

下面是处理逻辑,写在了adapter的基类中,方便复用,布局毕竟是都不一样的,但是逻辑可以通用,具体要按项目需求来定,灵活修改。

abstract class ChoiceGridAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    var mData: MutableList<GridItemBean> = mutableListOf()

    abstract fun initData(mData: MutableList<GridItemBean>)

    override fun getItemViewType(position: Int): Int {
        return mData[position].type
    }

    /**
     * 修改选择条目
     */
    fun changeStatus(position: Int){
        var first = 0               // 同类型的第一条数据位置
        var last = mData.size   //  同类型的最后一条数据位置
        for(index in position downTo 0){
            if (mData[index].type != 0){
                first = index
                break
            }
        }
        for (index in (position + 1) until mData.size){
            if (mData[index].type != 0){
                last = index
                break
            }
        }
        if (last > first){
            if (mData[first].isMultiChoice){
                // 是多选
                if (mData[position].isAllChoice){ // 全选按钮
                    for (index in first until last){
                        mData[index].isChoice = false
                    }
                    mData[position].isChoice = true
                    notifyDataSetChanged()
                }else{
                    for (index in first until last){
                        // 重置全选按钮
                        if (mData[index].isAllChoice){
                            mData[index].isChoice = false
                            notifyItemChanged(index)
                            break
                        }
                    }
                    mData[position].isChoice = true
                    notifyItemChanged(position)
                }
            }else{
                // 是单选
                var currentIndex = 0
                for (index in first until last){
                    // 查找当前选择的位置
                    if (mData[index].isChoice){
                        mData[index].isChoice = false
                        currentIndex = index
                        break
                    }
                }
                notifyItemChanged(currentIndex)
                mData[position].isChoice = true
                notifyItemChanged(position)
            }
        }
    }

    /**
     * 查找选择的条目
     */
    fun getChoiceItem(type: Int): GridItemBean?{
        var first = 0               // 同类型的第一条数据位置
        var last = mData.size   //  同类型的最后一条数据位置
        for (index in mData.indices){
            if (mData[index].type == type){
                first = index
                break
            }
        }
        for (index in (first + 1) until mData.size){
            if (mData[index].type != 0){
                last = index
                break
            }
        }
        for (index in first until last){
            if (mData[index].isChoice){
                return mData[index]
                break
            }
        }
        return null
    }

    /**
     * 查找多选选择的条目
     */
    fun getMultiChoiceItem(type: Int): MutableList<GridItemBean>?{
        var multiData = mutableListOf<GridItemBean>()
        var first = 0               // 同类型的第一条数据位置
        var last = mData.size   //  同类型的最后一条数据位置
        for (index in mData.indices){
            if (mData[index].type == type){
                first = index
                break
            }
        }
        for (index in (first + 1) until mData.size){
            if (mData[index].type != 0){
                last = index
                break
            }
        }
        var allChoice = false
        for (index in first until last){
            // 判断是否全选
            if (mData[index].isAllChoice){
                allChoice = mData[index].isChoice
                break
            }
        }
        if (allChoice){
            // 全选
            for (index in first until last){
                if (mData[index].type == 0 && !mData[index].isAllChoice){
                    multiData.add(mData[index])
                }
            }
        }else{
            // 非全选
            for (index in first until last){
                if (mData[index].isChoice){
                    multiData.add(mData[index])
                }
            }
        }
        return multiData
    }

    /**
     * 重置
     */
    fun clearChoice(){
        for (index in mData.indices){
            mData[index].isChoice = false
            notifyDataSetChanged()
        }
    }

}

changeStatus()方法是用来更新item选中的,更新的时候,要先查出当前选的这个标签的所有同类型的坐标,第一个和最后一个,逻辑就是查上一个标题类和下一个标题类,中间所有的标签都是这个类型的,然后根据是多选还是单选,更新标签的choice属性。
getChoiceItem()和getMultiChoiceItem()是获取单选或者多选选中的标签,也是要先获取同类标签的第一个和最后一个位置,遍历获取。

最后看看activity中数据的准备(根据需求调整)

override fun initData() {

        mData.add(GridItemBean(1, "类型(多选)" , true))
        mData.add(GridItemBean("1" , "全部" , true))
        mData.add(GridItemBean("2" , "11"))
        mData.add(GridItemBean("3" , "12"))
        mData.add(GridItemBean("4" , "13"))
        mData.add(GridItemBean("4" , "14"))

        mData.add(GridItemBean(2 , "标准(单选)"))
        mData.add(GridItemBean("5" , "20"))
        mData.add(GridItemBean("6" , "21"))
        mData.add(GridItemBean("7" , "22"))
        mData.add(GridItemBean("8" , "23"))
        mData.add(GridItemBean("8" , "24"))

        mData.add(GridItemBean(3 , "属性(多选)", true))
        mData.add(GridItemBean("5" , "30"))
        mData.add(GridItemBean("6" , "31"))
        mData.add(GridItemBean("7" , "32"))
        mData.add(GridItemBean("8" , "33"))
        mData.add(GridItemBean("8" , "34"))

        mAdapter = ChoiceAdapter()
        mAdapter?.initData(mData)

        RecyclerViewUtil.initGrid(this, recycler_grid , mAdapter,4)

        var layoutManager : GridLayoutManager = recycler_grid?.layoutManager as GridLayoutManager
        layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup(){
            override fun getSpanSize(position: Int): Int {
                var type = mAdapter?.getItemViewType(position)
                return if (type == 0) 1 else 4
            }
        }

    }

    fun getData(view: View){

        var data1 = mAdapter?.getMultiChoiceItem(1)
        LogUtil.logShow(data1?.toString())
        var data2 = mAdapter?.getChoiceItem(2)
        LogUtil.logShow(data2?.toString())
        var data3 = mAdapter?.getMultiChoiceItem(3)
        LogUtil.logShow(data3?.toString())

        tv_show?.text = "data1 = " + data1.toString() + "\ndata2 = " + data2.toString() + "\ndata3 = " + data3.toString()
    }


    fun backData(view: View){
        mAdapter?.clearChoice()
        tv_show?.text = ""
    }

源码地址

https://github.com/QQzs/ChoiceView

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

推荐阅读更多精彩内容

  • 内容 抽屉菜单 ListView WebView SwitchButton 按钮 点赞按钮 进度条 TabLayo...
    小狼W阅读 1,614评论 0 10
  • 最近做了一个Android UI相关开源项目库汇总,里面集合了OpenDigg 上的优质的Android开源项目库...
    OpenDigg阅读 17,199评论 6 222
  • 抽屉菜单 MaterialDrawer★7337 - 安卓抽屉效果实现方案 Side-Menu.Android★3...
    彬哥狠逍遥阅读 5,889评论 4 59
  • 这一周总的来说周一到周五是平淡普通的工作日,周末是玩耍忙碌的2天。 双十二血拼 这周正好赶上双十二,就算没什么要买...
    周唐阅读 312评论 1 0
  • 我想去远方 找寻自由的他乡 舒畅萎靡的思虑 让心在绿海里徜徉 我想去远方 放弃不存在的幻想 闻闻栀子花的香 亲亲荷...
    释空沙阅读 203评论 0 1