Android 繪制多級樹形選擇列表實例代碼
一、概述
前段時間有個項目的需要在Android端顯示一個復選的多層樹形控件,主要展示一個公司的組織架構,類似總部下面有各個部門,部門之下是組和員工等。另外需要加上展開與回收部門詳情、關閉部分已開展的布局、勾選等功能。
效果圖如下:
二、思路分析
毫無疑問,對于這種數(shù)據(jù)可能達到幾千幾萬行的列表視圖,我們需要選擇recyclerview等具有回收item功能的控件,因此Item的狀態(tài)保持放在Model中而不是View中。
由于原始數(shù)據(jù)是樹形結構的,我們需要先將樹形結構轉(zhuǎn)換為列表數(shù)據(jù),類似根結點 - 父節(jié)點1 - 子結點1 - 子節(jié)點2 - 父節(jié)點2......這種形式 - 這恰恰是樹的前序遍歷
實現(xiàn)思路 - 為了更簡潔明白,左右顛倒處理
三、具體實現(xiàn)
簡單的節(jié)點實現(xiàn)
public abstract class SimpleTreeNode { //層級 protected int hierarchy; //父節(jié)點 protected K parent = null; //子節(jié)點 protected final List<K> children = new ArrayList<>(); protected boolean isSelected; // 是否被選中 protected boolean isExpand; // 是否展開 } 前序遍歷則發(fā)生在adapter的getItem和getItemCount的時候 public T getItem(int position) { int[] cur = {position}; return getNode(topGroups, cur); } /** * 先序遍歷 - 獲取指定位置的節(jié)點 * * @param nodes nodes * @param position itemPosition 數(shù)組只是為了實現(xiàn)手動box實現(xiàn)共享position * @return MultiSelectNode or null */ protected T getNode(List<T> nodes, final int[] position) { for (T node : nodes) { if (position[0] == 0) { return node; } position[0]--; if (node.getChildren().size() > 0) { T finalNode = getNode(node.getChildren(), position); if (finalNode != null) { return finalNode; } } } return null; } /** * 先序遍歷 - 獲取展示的總長度 (isExpand = true) * * @param nodes nodes * @return int */ protected int getTreeSize(List<T> nodes) { int size = 0; for (T node : nodes) { size++; size += getTreeSize(node.getChildren()); } return size; }
對于如何實現(xiàn)展開和收縮的功能,我嘗試了兩種方式:
在渲染item的時候判斷node.isExpand = false時,對item進行Gone處理,實際處理發(fā)現(xiàn)列表卡頓非常嚴重:假設所有的item都是隱藏的,那么因為列表沒有顯示全,所有的item都會進行渲染一遍....
數(shù)據(jù)遍歷的時候?qū)⒎钦归_的數(shù)據(jù)過濾掉:這種方式完美可行,只需要修改下遍歷方法即可
protected int getTreeSize(List<T> nodes) { int size = 0; for (T node : nodes) { size++; // 展開過濾 if (node.isExpand()) { size += getTreeSize(node.getChildren()); } } return size; } protected T getNode(List<T> nodes, final int[] position) { for (T node : nodes) { if (position[0] == 0) { return node; } position[0]--; // 展開過濾 if (node.isExpand() && node.getChildren().size() > 0) { T finalNode = getNode(node.getChildren(), position); if (finalNode != null) { return finalNode; } } } return null; }
以上多級樹形列表的展開與隱藏便完成了,剩下的便是對樹節(jié)點的一些操作:例如一個item展開的時候?qū)ζ渌塱tem隱藏;一個item被勾選或取消勾選的時候改變其父節(jié)點和子節(jié)點的狀態(tài)等。對于這些操作,我采用了類似Motion Event的方式 - 用事件傳遞與分發(fā)來處理。
比如展開的時候同級的item隱藏,其實便是通知兄弟節(jié)點設置expand為false。
通知兄弟節(jié)點
勾選的操作稍麻煩,可能需要遞歸通知父節(jié)點檢查更新,以及遞歸通知子節(jié)點勾選操作,取消勾選亦如此。
關鍵代碼如下
/** * Class: SimpleTreeNode * Author: zwgg * Date: 2017/10/16 * Time: 10:35 * 簡單的樹節(jié)點模板類 * 這個自限定泛型可能有點費解:用于以基類導出類作為自身的泛型,以實現(xiàn)模板功能 * 例如:ClassNameA extend SimpleTreeNode< ClassNameA , T > * @see Enum */ public abstract class SimpleTreeNode<K extends SimpleTreeNode<K, T>, T extends TreeNodeEvent> { //層級 protected int hierarchy; //父節(jié)點 protected K parent = null; //子節(jié)點 protected final List<K> children = new ArrayList<>(); public SimpleTreeNode() { } public SimpleTreeNode(int hierarchy) { this.hierarchy = hierarchy; } public void bindingParent(K parent) { this.parent = parent; } public void bindingChild(K child) { this.children.add(child); } public void bindingChildren(List<K> children) { this.children.clear(); this.children.addAll(children); } public void dataBinding(K parent, K child) { parent.bindingChild(child); child.bindingParent(parent); } public int getHierarchy() { return hierarchy; } public void setHierarchy(int hierarchy) { this.hierarchy = hierarchy; } /** * 通知父節(jié)點 * @param event event */ public void notifyParent(T event) { if (parent != null) { event.setNotifyType(TreeNodeEvent.NOTIFY_PARENT); parent.onEvent(event); } } /** * 通知子節(jié)點 * @param event event */ public void notifyChildren(T event) { event.setNotifyType(TreeNodeEvent.NOTIFY_CHILDREN); for (K child : children) { child.onEvent(event); } } /** * 通知兄弟節(jié)點 - 需要具有相同的Parent * @param event event */ public void notifyBrother(T event) { if (parent != null) { event.setNotifyType(TreeNodeEvent.NOTIFY_BROTHER); for (K child : parent.children) { if (child != this) { child.onEvent(event); } } } } public abstract void onEvent(T event); public List<K> getChildren() { return children; } }
業(yè)務節(jié)點
public class MultiSelectNode<T extends MultiSelectNode<T>> extends SimpleTreeNode<T, MultiSelectEvent> { private boolean isSelected; // 是否被選中 private boolean isExpand; // 是否展開 /** * @param hierarchy view層級 - 用于產(chǎn)生viewType */ public MultiSelectNode(int hierarchy) { super(hierarchy); } /** * 事件處理方法 * * @param event 傳遞得到的事件 */ @Override public void onEvent(MultiSelectEvent event) { switch (event.getNotifyType()) { case TreeNodeEvent.NOTIFY_CHILDREN: // 父節(jié)點通知子節(jié)點改變選擇狀態(tài) if (event.getEventType() == MultiSelectEvent.EVENT_SET_SELECTED) { // 如果子節(jié)點選擇狀態(tài)有變,則繼續(xù)通知下層節(jié)點改變狀態(tài) if (event.isSelected() != isSelected()) { setSelected(event.isSelected()); notifyChildren(event); } } break; case TreeNodeEvent.NOTIFY_PARENT: // 子節(jié)點選擇狀態(tài)更改,則通知父節(jié)點重新根據(jù)所有子節(jié)點設置自身狀態(tài) if (event.getEventType() == MultiSelectEvent.EVENT_SET_SELECTED) { if (recheckSelected() != isSelected()) { setSelected(!isSelected()); // 如果父節(jié)點有變,則繼續(xù)遞歸通知 notifyParent(event); } } break; case TreeNodeEvent.NOTIFY_BROTHER: // 通知兄弟節(jié)點改變擴展狀態(tài) if (event.getEventType() == MultiSelectEvent.EVENT_SET_EXPAND) { if (event.isExpand() != isExpand()) { setExpand(event.isExpand()); } } break; default: break; } } /** * 關閉兄弟節(jié)點擴展 * * @param isExpand 是否擴展 */ public void setOtherGroupsExpand(boolean isExpand) { MultiSelectEvent event = new MultiSelectEvent(MultiSelectEvent.EVENT_SET_EXPAND); event.setExpand(isExpand); notifyBrother(event); } /** * 通知父節(jié)點根據(jù)子節(jié)點設置狀態(tài) * 注:選擇具有遞歸性,如果父類狀態(tài)有變會繼續(xù)通知父類 */ public void setParentRecheckSelected() { MultiSelectEvent event = new MultiSelectEvent(MultiSelectEvent.EVENT_SET_SELECTED); notifyParent(event); } /** * 通知子節(jié)點設置選擇狀態(tài) * 注:選擇具有遞歸性,會設置所有孩子以及孩子的孩子狀態(tài) * * @param isSelected 是否選擇 */ public void setChildrenSelected(boolean isSelected) { MultiSelectEvent event = new MultiSelectEvent(MultiSelectEvent.EVENT_SET_SELECTED); event.setSelected(isSelected); notifyChildren(event); } /** * 根據(jù)子節(jié)點設置自身狀態(tài) * * @return isSelected boolean */ public boolean recheckSelected() { for (MultiSelectNode child : children) { if (!child.isSelected()) { return false; } } return true; } public boolean isExpand() { return isExpand; } public void setExpand(boolean expand) { isExpand = expand; } public boolean isSelected() { return isSelected; } public void setSelected(boolean selected) { isSelected = selected; } }
Event事件
public class TreeNodeEvent { public static final int NOTIFY_PARENT = 1; public static final int NOTIFY_CHILDREN = 2; public static final int NOTIFY_BROTHER = 3; private int notifyType; public TreeNodeEvent(){ } public TreeNodeEvent(int notifyType) { this.notifyType = notifyType; } public int getNotifyType() { return notifyType; } public void setNotifyType(int notifyType) { this.notifyType = notifyType; } } public class MultiSelectEvent extends TreeNodeEvent { public static final int EVENT_SET_SELECTED = 1; public static final int EVENT_SET_EXPAND = 2; //事件類型 private int eventType; private boolean isSelected; private boolean isExpand; }
詳細可見Github: https://github.com/zwgg/MultiSelectList
總結
以上所述是小編給大家介紹的Android 繪制多級樹形選擇列表實例代碼,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關文章
Android中imageView圖片放大縮小及旋轉(zhuǎn)功能示例代碼
這篇文章主要介紹了Android中imageView圖片放大縮小及旋轉(zhuǎn)功能示例代碼,需要的朋友可以參考下2017-08-08Android GridView 滑動條設置一直顯示狀態(tài)(推薦)
這篇文章主要介紹了Android GridView 滑動條設置一直顯示狀態(tài)的相關資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-12-12解決Eclipse創(chuàng)建android項目無法正常預覽布局文件問題的方法
這篇文章主要介紹了解決Eclipse創(chuàng)建android項目無法正常預覽布局文件問題的方法,需要的朋友可以參考下2015-12-12Android報錯Didn‘t?find?class?“android.view.x“問題解決原理剖析
這篇文章主要為大家介紹了Android報錯Didn‘t?find?class?“android.view.x“問題解決及原理剖析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03為Retrofit統(tǒng)一添加post請求的默認參數(shù)的方法
這篇文章主要介紹了為Retrofit統(tǒng)一添加post請求的默認參數(shù)的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-04-04