Android 一個RecyclerView實現(xiàn)篩選列表

效果圖

choice.gif

簡介

如上圖展示的內容,篩選條件的功能很常見屏鳍,一般情況如果條件很多勘纯,那么布局文件就會寫的很復雜,這篇文章可以提供一個簡潔的方案钓瞭,布局文件只用一個RecyclerView就可以了驳遵。當然,處理邏輯可能要少費點功夫山涡,不過這些邏輯可以復用堤结,如果有多個地方用到就省很多事了。

代碼分析

先看看Bean類的處理:
數(shù)據(jù)中有幾個必須要添加的屬性
type:用于區(qū)分是標題item還是內容item鸭丛,標題item也可以分很多類竞穷。
choice:標記item是否選中
multiChoice:這一類型標簽是不是多選
allChoice:標簽item是不是全選按鈕(根據(jù)需求調整)

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鳞溉,分為兩類瘾带,標題類和內容標簽,根據(jù)bean里面的type來區(qū)分穿挨,這部分沒什么要多說的月弛。

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               // 同類型的第一條數(shù)據(jù)位置
        var last = mData.size   //  同類型的最后一條數(shù)據(jù)位置
        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               // 同類型的第一條數(shù)據(jù)位置
        var last = mData.size   //  同類型的最后一條數(shù)據(jù)位置
        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               // 同類型的第一條數(shù)據(jù)位置
        var last = mData.size   //  同類型的最后一條數(shù)據(jù)位置
        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選中的榨崩,更新的時候谴垫,要先查出當前選的這個標簽的所有同類型的坐標,第一個和最后一個母蛛,邏輯就是查上一個標題類和下一個標題類翩剪,中間所有的標簽都是這個類型的,然后根據(jù)是多選還是單選彩郊,更新標簽的choice屬性前弯。
getChoiceItem()和getMultiChoiceItem()是獲取單選或者多選選中的標簽,也是要先獲取同類標簽的第一個和最后一個位置秫逝,遍歷獲取恕出。

最后看看activity中數(shù)據(jù)的準備(根據(jù)需求調整)

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市违帆,隨后出現(xiàn)的幾起案子浙巫,更是在濱河造成了極大的恐慌,老刑警劉巖刷后,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件的畴,死亡現(xiàn)場離奇詭異,居然都是意外死亡尝胆,警方通過查閱死者的電腦和手機丧裁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來班巩,“玉大人渣慕,你說我怎么就攤上這事”Щ牛” “怎么了逊桦?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長抑进。 經(jīng)常有香客問我强经,道長,這世上最難降的妖魔是什么寺渗? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任匿情,我火速辦了婚禮兰迫,結果婚禮上,老公的妹妹穿的比我還像新娘炬称。我一直安慰自己汁果,他們只是感情好,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布玲躯。 她就那樣靜靜地躺著据德,像睡著了一般。 火紅的嫁衣襯著肌膚如雪跷车。 梳的紋絲不亂的頭發(fā)上棘利,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天,我揣著相機與錄音朽缴,去河邊找鬼善玫。 笑死,一個胖子當著我的面吹牛密强,可吹牛的內容都是我干的茅郎。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼誓斥,長吁一口氣:“原來是場噩夢啊……” “哼只洒!你這毒婦竟也來了?” 一聲冷哼從身側響起劳坑,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤毕谴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后距芬,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涝开,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年框仔,在試婚紗的時候發(fā)現(xiàn)自己被綠了舀武。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡离斩,死狀恐怖银舱,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情跛梗,我是刑警寧澤寻馏,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站核偿,受9級特大地震影響诚欠,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一轰绵、第九天 我趴在偏房一處隱蔽的房頂上張望粉寞。 院中可真熱鬧,春花似錦左腔、人聲如沸唧垦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽业崖。三九已至野芒,卻和暖如春蓄愁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背狞悲。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工撮抓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人摇锋。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓丹拯,卻偏偏與公主長得像,于是被迫代替她去往敵國和親荸恕。 傳聞我的和親對象是個殘疾皇子乖酬,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內容

  • 內容 抽屜菜單 ListView WebView SwitchButton 按鈕 點贊按鈕 進度條 TabLayo...
    小狼W閱讀 1,611評論 0 10
  • 最近做了一個Android UI相關開源項目庫匯總,里面集合了OpenDigg 上的優(yōu)質的Android開源項目庫...
    OpenDigg閱讀 17,165評論 6 223
  • 抽屜菜單 MaterialDrawer★7337 - 安卓抽屜效果實現(xiàn)方案 Side-Menu.Android★3...
    彬哥狠逍遙閱讀 5,879評論 4 59
  • 這一周總的來說周一到周五是平淡普通的工作日融求,周末是玩耍忙碌的2天咬像。 雙十二血拼 這周正好趕上雙十二,就算沒什么要買...
    周唐閱讀 310評論 1 0
  • 我想去遠方 找尋自由的他鄉(xiāng) 舒暢萎靡的思慮 讓心在綠海里徜徉 我想去遠方 放棄不存在的幻想 聞聞梔子花的香 親親荷...
    釋空沙閱讀 198評論 0 1