更快實現(xiàn)Android多級樹形選擇列表

快速實現(xiàn)Android多級樹形列表,這個庫是在鴻洋多級樹形列表demo中修改而來。

解決的問題

  1. 支持ID為int類型和String類型春锋。
  2. 支持多級復選框選中,使用只需一行代碼差凹。
  3. 支持動態(tài)更新數(shù)據(jù)并保持原有展開/關閉狀態(tài)期奔。
  4. 支持ListView、RecyclerView危尿。

項目地址:https://github.com/zhangke3016/MultilevelTreeList

一呐萌、概述

這幾天項目中有一個多級列表的菜單,最開始給我的感覺應該就是嵌套ListView,或者用ExpandableListView谊娇,但問題是ExpandableListView只支持兩級列表肺孤,而且關鍵的是具體分幾級是不確定的,也就是可能一級,可能多級赠堵,這要是五六級嵌套ListView小渊,想想那酸爽。茫叭。酬屉。最終在偷懶心態(tài)的驅使下到網(wǎng)上查查看有沒有類似的,也確實查到鴻洋大佬之前寫的一篇關于實現(xiàn)Android多級樹形列表的文章杂靶,實現(xiàn)很巧妙梆惯,使用一個ListView就可以實現(xiàn)多級列表效果,就download下demo吗垮,在demo基礎上做了部分修改垛吗,功能順利實現(xiàn)。

其實到這里應該就結束了烁登,但使用過程中遇到的一些問題讓我覺得這個可以進一步優(yōu)化怯屉,比如我要做個多級復選列表,每次處理子級選中與父級選中搞得很累饵沧,生怕哪個遞歸錯了锨络。而且在新增數(shù)據(jù)的時候刷新頁面也需要自己處理,直接刷新沒有效果狼牺,再加上現(xiàn)在RecyclerView已經(jīng)用的越來越多了羡儿。就想著在周末好好總結下,封裝個方便使用的庫是钥,方便下一次有類似需求的時候使用掠归。說到底,還是為了下次可以偷偷懶唄悄泥。如果小伙伴有類似需求虏冻,也可以直接拿來用~

先看下效果吧:

MultilevelTreeList

這篇文章主要介紹這個庫如何使用,如果對具體實現(xiàn)細節(jié)感興趣弹囚,可以查看源碼或者搜索鴻洋的博客厨相。

二、具體使用

我們關聯(lián)列表樹需要有三個必須元素鸥鹉,當前id蛮穿、父級id即pid,顯示的內容宋舷。id和pid可以為int或者String以及其他類型绪撵。要顯示的內容需要包裝一下:

//id pid name  FileNode為實際用的實體Bean對象
mlist.add(new Node("223","0","我也是添加的root節(jié)點",new FileNode()));

對于ListView,需要繼承自TreeListViewAdapter,如:

public class SimpleTreeAdapter extends TreeListViewAdapter
{
    public SimpleTreeAdapter(ListView mTree, Context context, List<Node> datas, int defaultExpandLevel, int iconExpand, int iconNoExpand) {
        super(mTree, context, datas, defaultExpandLevel, iconExpand, iconNoExpand);
    }

    public SimpleTreeAdapter(ListView mTree, Context context, List<Node> datas,
                             int defaultExpandLevel) {
        super(mTree, context, datas, defaultExpandLevel);
    }

    @Override
    public View getConvertView(final Node node , int position, View convertView, ViewGroup parent)
    {

       final ViewHolder viewHolder ;
        if (convertView == null) {
            convertView = mInflater.inflate(R.layout.list_item, parent, false);
            viewHolder = new ViewHolder();
            viewHolder.cb = (CheckBox) convertView
                    .findViewById(R.id.cb_select_tree);
            viewHolder.label = (TextView) convertView
                    .findViewById(R.id.id_treenode_label);
            viewHolder.icon = (ImageView) convertView.findViewById(R.id.icon);
            convertView.setTag(viewHolder);

        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }
        viewHolder.cb.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                setChecked(node,viewHolder.cb.isChecked());
            }
        });

        if (node.isChecked()){
            viewHolder.cb.setChecked(true);
        }else {
            viewHolder.cb.setChecked(false);
        }

        if (node.getIcon() == -1) {
            viewHolder.icon.setVisibility(View.INVISIBLE);
        } else {
            viewHolder.icon.setVisibility(View.VISIBLE);
            viewHolder.icon.setImageResource(node.getIcon());
        }

        viewHolder.label.setText(node.getName());

        return convertView;
    }

    private final class ViewHolder
    {
        ImageView icon;
        CheckBox cb;
        TextView label;
    }

}

對于RecyclerView祝蝠,需繼承自TreeRecyclerAdapter,如:

public class SimpleTreeRecyclerAdapter extends TreeRecyclerAdapter {

    public SimpleTreeRecyclerAdapter(RecyclerView mTree, Context context, List<Node> datas, int defaultExpandLevel, int iconExpand, int iconNoExpand) {
        super(mTree, context, datas, defaultExpandLevel, iconExpand, iconNoExpand);
    }

    public SimpleTreeRecyclerAdapter(RecyclerView mTree, Context context, List<Node> datas, int defaultExpandLevel) {
        super(mTree, context, datas, defaultExpandLevel);
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new MyHoder(View.inflate(mContext, R.layout.list_item,null));
    }

    @Override
    public void onBindViewHolder(final Node node, RecyclerView.ViewHolder holder, int position) {

        final MyHoder viewHolder = (MyHoder) holder;
        //todo do something
        viewHolder.cb.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                setChecked(node,viewHolder.cb.isChecked());
            }
        });

        if (node.isChecked()){
            viewHolder.cb.setChecked(true);
        }else {
            viewHolder.cb.setChecked(false);
        }

        if (node.getIcon() == -1) {
            viewHolder.icon.setVisibility(View.INVISIBLE);
        } else {
            viewHolder.icon.setVisibility(View.VISIBLE);
            viewHolder.icon.setImageResource(node.getIcon());
        }

        viewHolder.label.setText(node.getName());


    }

    class MyHoder extends RecyclerView.ViewHolder{

        public CheckBox cb;

        public TextView label;

        public ImageView icon;
        public MyHoder(View itemView) {
            super(itemView);

            cb = (CheckBox) itemView
                    .findViewById(R.id.cb_select_tree);
            label = (TextView) itemView
                    .findViewById(R.id.id_treenode_label);
            icon = (ImageView) itemView.findViewById(R.id.icon);

        }

    }
}

初始化
ListView:

        //第一個參數(shù)  ListView
        //第二個參數(shù)  上下文
        //第三個參數(shù)  數(shù)據(jù)集
        //第四個參數(shù)  默認展開層級數(shù) 0為不展開
        //第五個參數(shù)  展開的圖標
        //第六個參數(shù)  閉合的圖標
         mAdapter = new SimpleTreeAdapter(mTree, ListViewActivity.this,
                        mDatas, 1,R.mipmap.tree_ex,R.mipmap.tree_ec);
         mTree.setAdapter(mAdapter);

RecyclerView

        //第一個參數(shù)  RecyclerView
        //第二個參數(shù)  上下文
        //第三個參數(shù)  數(shù)據(jù)集
        //第四個參數(shù)  默認展開層級數(shù) 0為不展開
        //第五個參數(shù)  展開的圖標
        //第六個參數(shù)  閉合的圖標
        mAdapter = new SimpleTreeRecyclerAdapter(mTree, RecyclerViewActivity.this,
                mDatas, 1,R.mipmap.tree_ex,R.mipmap.tree_ec);

        mTree.setAdapter(mAdapter);

添加數(shù)據(jù),可以保持原有選中或者展開狀態(tài):

        List<Node> mlist = new ArrayList<>();
        mlist.add(new Node("223","0","我也是添加的root節(jié)點",new FileNode()));
        mAdapter.addData(0,mlist);

獲取選中內容:如果node的isChecked()為true,即為選中狀態(tài)绎狭。

    StringBuilder sb = new StringBuilder();
        //獲取排序過的nodes
        //如果不需要刻意直接用 mDatas既可
        final List<Node> allNodes = mAdapter.getAllNodes();
        for (int i = 0; i < allNodes.size(); i++) {
            if (allNodes.get(i).isChecked()){
                sb.append(allNodes.get(i).getName()+",");
            }
        }
        String strNodesName = sb.toString();
        if (!TextUtils.isEmpty(strNodesName))
            Toast.makeText(this, strNodesName.substring(0, strNodesName.length()-1),Toast.LENGTH_SHORT).show();

控制父子之間聯(lián)動的選中與取消狀態(tài)细溅,只需調用setChecked方法既可,注意如果在setOnCheckedChangeListener中處理會有問題:因為如果要子節(jié)點/父節(jié)點選中或者取消需要刷新頁面儡嘶,而刷新頁面又會觸發(fā)viewHolder.cb.setChecked(true/false);的判斷從而又會進入setOnCheckedChangeListener喇聊,會導致如果父節(jié)點選中某些子節(jié)點取消不了的情況。

 //viewHolder.cb 為CheckBox
 viewHolder.cb.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                setChecked(node,viewHolder.cb.isChecked());
            }
        });

三蹦狂、簡單介紹

通過一個ListView來展示所有數(shù)據(jù)誓篱,每一級內容的顯示根據(jù)當前展示數(shù)據(jù)的等級縮進一定的padding值,讓我們看起來有縮進效果凯楔。

使用過程中感覺不是很舒服的地方在于最終用于顯示在界面實體Bean并不是我們傳進去的數(shù)據(jù)窜骄,而是經(jīng)過轉化并且過濾的數(shù)據(jù),這樣最直接的影響就是在我新增數(shù)據(jù)的數(shù)據(jù)之后摆屯,拿著Adapter來刷新的時候邻遏,并沒有任何效果。因為我們沒有將后面新加的數(shù)據(jù)進行轉化虐骑。

而我們如何能在不改變原有數(shù)據(jù)結構的基礎上准验,添加我們的新內容,并保持原有的選中或者展開正常呢廷没?我的想法是這樣的糊饱,如果可以直接給它傳入轉化后的Node節(jié)點類型數(shù)據(jù)就好了,我想到了繼承颠黎,讓實體類去繼承基類Node另锋,但一旦繼承Node則意味著實體類就不能再繼承其他類了,感覺不是很靈活盏缤,而且也影響了實體類本身的結構砰蠢。后來想到了包裝設計模式的一些東西,那我就在實體類外再包上一層唉铜,也就是將實體類傳給Node台舱,最終我們使用的還是Node,但也可以用node.bean很輕松的取出實體類做其他操作潭流,并且實體類本身的結構并沒有被破壞竞惋。

在此基礎上,因為我們的Node不需要轉化重新創(chuàng)建灰嫉,那么它就可以保存一些狀態(tài)比如展開拆宛、選中等等,而在新加入數(shù)據(jù)時只需標記下新加入的數(shù)據(jù)讼撒,只需對新加入的數(shù)據(jù)進行初始化狀態(tài)浑厚,已有老數(shù)據(jù)不進行狀態(tài)改變:

 if (node.isNewAdd && defaultExpandLeval >= currentLevel) {
            node.setExpand(true);
        }

這樣股耽,我們可以保持動態(tài)更新添加數(shù)據(jù)又可以保持原有的展開或選中狀態(tài)不發(fā)生變化,實現(xiàn)我們的需求钳幅。

四物蝙、源碼

項目地址:https://github.com/zhangke3016/MultilevelTreeList

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市敢艰,隨后出現(xiàn)的幾起案子诬乞,更是在濱河造成了極大的恐慌,老刑警劉巖钠导,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件震嫉,死亡現(xiàn)場離奇詭異,居然都是意外死亡牡属,警方通過查閱死者的電腦和手機票堵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來湃望,“玉大人换衬,你說我怎么就攤上這事≈ぐ牛” “怎么了瞳浦?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長废士。 經(jīng)常有香客問我叫潦,道長,這世上最難降的妖魔是什么官硝? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任矗蕊,我火速辦了婚禮,結果婚禮上氢架,老公的妹妹穿的比我還像新娘傻咖。我一直安慰自己,他們只是感情好岖研,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布卿操。 她就那樣靜靜地躺著,像睡著了一般孙援。 火紅的嫁衣襯著肌膚如雪害淤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天拓售,我揣著相機與錄音窥摄,去河邊找鬼。 笑死础淤,一個胖子當著我的面吹牛崭放,可吹牛的內容都是我干的哨苛。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼莹菱,長吁一口氣:“原來是場噩夢啊……” “哼移国!你這毒婦竟也來了吱瘩?” 一聲冷哼從身側響起道伟,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎使碾,沒想到半個月后蜜徽,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡票摇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年拘鞋,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片矢门。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡盆色,死狀恐怖,靈堂內的尸體忽然破棺而出祟剔,到底是詐尸還是另有隱情隔躲,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布物延,位于F島的核電站宣旱,受9級特大地震影響,放射性物質發(fā)生泄漏叛薯。R本人自食惡果不足惜浑吟,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望耗溜。 院中可真熱鬧组力,春花似錦、人聲如沸抖拴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽城舞。三九已至轩触,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間家夺,已是汗流浹背脱柱。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拉馋,地道東北人榨为。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓惨好,卻偏偏與公主長得像,于是被迫代替她去往敵國和親随闺。 傳聞我的和親對象是個殘疾皇子日川,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內容