Android多級(jí)樹(shù)形列表控件
我們開(kāi)發(fā)app過(guò)程中,經(jīng)常會(huì)碰到需要 多級(jí)列表展示的效果。而Android原生sdk中根本沒(méi)有3級(jí) 4級(jí)甚至更多級(jí)別的列表控件。
所以我們就要自己去實(shí)現(xiàn)一個(gè)類(lèi)似treeListView 的控件,下面這個(gè)是我項(xiàng)目中的一個(gè)效果圖,可支持多級(jí)列表擴(kuò)展。

android中有ExpandListView控件,但是這個(gè)控件只支持兩級(jí)列表。對(duì)于多級(jí)列表如果重寫(xiě)這個(gè)不是很好用。
實(shí)現(xiàn)這種列表 思想就是遞歸,構(gòu)造一個(gè)子父級(jí)的關(guān)系。
話不多說(shuō) 代碼中體會(huì)
Activity
package com.example.customtreeviewdemo;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;
import com.example.customtreeviewdemo.bean.MyNodeBean;
import com.example.customtreeviewdemo.tree.Node;
import com.example.customtreeviewdemo.tree.TreeListViewAdapter.OnTreeNodeClickListener;
public class MainActivity extends Activity {
private ListView treeLv;
private Button checkSwitchBtn;
private MyTreeListViewAdapter<MyNodeBean> adapter;
private List<MyNodeBean> mDatas = new ArrayList<MyNodeBean>();
//標(biāo)記是顯示Checkbox還是隱藏
private boolean isHide = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initDatas();
treeLv = (ListView) this.findViewById(R.id.tree_lv);
checkSwitchBtn = (Button)this.findViewById(R.id.check_switch_btn);
checkSwitchBtn.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
if(isHide){
isHide = false;
}else{
isHide = true;
}
adapter.updateView(isHide);
}
});
try {
adapter = new MyTreeListViewAdapter<MyNodeBean>(treeLv, this,
mDatas, 10, isHide);
adapter.setOnTreeNodeClickListener(new OnTreeNodeClickListener() {
@Override
public void onClick(Node node, int position) {
if (node.isLeaf()) {
Toast.makeText(getApplicationContext(), node.getName(),
Toast.LENGTH_SHORT).show();
}
}
@Override
public void onCheckChange(Node node, int position,
List<Node> checkedNodes) {
StringBuffer sb = new StringBuffer();
for (Node n : checkedNodes) {
int pos = n.getId() - 1;
sb.append(mDatas.get(pos).getName()).append("---")
.append(pos + 1).append(";");
}
Toast.makeText(getApplicationContext(), sb.toString(),
Toast.LENGTH_SHORT).show();
}
});
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
treeLv.setAdapter(adapter);
}
private void initDatas() {
mDatas.add(new MyNodeBean(1, 0, "中國(guó)古代"));
mDatas.add(new MyNodeBean(2, 1, "唐朝"));
mDatas.add(new MyNodeBean(3, 1, "宋朝"));
mDatas.add(new MyNodeBean(4, 1, "明朝"));
mDatas.add(new MyNodeBean(5, 2, "李世民"));
mDatas.add(new MyNodeBean(6, 2, "李白"));
mDatas.add(new MyNodeBean(7, 3, "趙匡胤"));
mDatas.add(new MyNodeBean(8, 3, "蘇軾"));
mDatas.add(new MyNodeBean(9, 4, "朱元璋"));
mDatas.add(new MyNodeBean(10, 4, "唐伯虎"));
mDatas.add(new MyNodeBean(11, 4, "文征明"));
mDatas.add(new MyNodeBean(12, 7, "趙建立"));
mDatas.add(new MyNodeBean(13, 8, "蘇東東"));
mDatas.add(new MyNodeBean(14, 10, "秋香"));
}
}
Adapter
這個(gè)adapter是繼承了自己的定義的一個(gè)TreeListViewAdapter,核心實(shí)現(xiàn)都是在TreeListViewAdapter這個(gè)里面
package com.example.customtreeviewdemo;
import java.util.List;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import com.example.customtreeviewdemo.tree.Node;
import com.example.customtreeviewdemo.tree.TreeListViewAdapter;
public class MyTreeListViewAdapter<T> extends TreeListViewAdapter<T> {
public MyTreeListViewAdapter(ListView mTree, Context context,
List<T> datas, int defaultExpandLevel,boolean isHide)
throws IllegalArgumentException, IllegalAccessException {
super(mTree, context, datas, defaultExpandLevel,isHide);
}
@SuppressWarnings("unchecked")
@Override
public View getConvertView(Node node, int position, View convertView,
ViewGroup parent) {
ViewHolder viewHolder = null;
if (convertView == null)
{
convertView = mInflater.inflate(R.layout.list_item, parent, false);
viewHolder = new ViewHolder();
viewHolder.icon = (ImageView) convertView
.findViewById(R.id.id_treenode_icon);
viewHolder.label = (TextView) convertView
.findViewById(R.id.id_treenode_name);
viewHolder.checkBox = (CheckBox)convertView.findViewById(R.id.id_treeNode_check);
convertView.setTag(viewHolder);
} else
{
viewHolder = (ViewHolder) convertView.getTag();
}
if (node.getIcon() == -1)
{
viewHolder.icon.setVisibility(View.INVISIBLE);
} else
{
viewHolder.icon.setVisibility(View.VISIBLE);
viewHolder.icon.setImageResource(node.getIcon());
}
if(node.isHideChecked()){
viewHolder.checkBox.setVisibility(View.GONE);
}else{
viewHolder.checkBox.setVisibility(View.VISIBLE);
setCheckBoxBg(viewHolder.checkBox,node.isChecked());
}
viewHolder.label.setText(node.getName());
return convertView;
}
private final class ViewHolder
{
ImageView icon;
TextView label;
CheckBox checkBox;
}
/**
* checkbox是否顯示
* @param cb
* @param isChecked
*/
private void setCheckBoxBg(CheckBox cb,boolean isChecked){
if(isChecked){
cb.setBackgroundResource(R.drawable.check_box_bg_check);
}else{
cb.setBackgroundResource(R.drawable.check_box_bg);
}
}
}
自定義TreeListViewAdapter 這個(gè)是整個(gè)樹(shù)形結(jié)構(gòu)的一個(gè)適配器,這里面主要是實(shí)現(xiàn)對(duì)Node節(jié)點(diǎn)的操作 點(diǎn)擊,選中改變 更新等
package com.example.customtreeviewdemo.tree;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ListView;
import android.widget.RelativeLayout;
/**
* tree適配器
* @param <T>
*/
public abstract class TreeListViewAdapter<T> extends BaseAdapter {
protected Context mContext;
/**
* 存儲(chǔ)所有可見(jiàn)的Node
*/
protected List<Node> mNodes;
protected LayoutInflater mInflater;
/**
* 存儲(chǔ)所有的Node
*/
protected List<Node> mAllNodes;
/**
* 點(diǎn)擊的回調(diào)接口
*/
private OnTreeNodeClickListener onTreeNodeClickListener;
public interface OnTreeNodeClickListener {
/**
* 處理node click事件
* @param node
* @param position
*/
void onClick(Node node, int position);
/**
* 處理checkbox選擇改變事件
* @param node
* @param position
* @param checkedNodes
*/
void onCheckChange(Node node, int position,List<Node> checkedNodes);
}
public void setOnTreeNodeClickListener(
OnTreeNodeClickListener onTreeNodeClickListener) {
this.onTreeNodeClickListener = onTreeNodeClickListener;
}
/**
*
* @param mTree
* @param context
* @param datas
* @param defaultExpandLevel
* 默認(rèn)展開(kāi)幾級(jí)樹(shù)
* @throws IllegalArgumentException
* @throws IllegalAccessException
*/
public TreeListViewAdapter(ListView mTree, Context context, List<T> datas,
int defaultExpandLevel, boolean isHide)
throws IllegalArgumentException, IllegalAccessException {
mContext = context;
/**
* 對(duì)所有的Node進(jìn)行排序
*/
mAllNodes = TreeHelper
.getSortedNodes(datas, defaultExpandLevel, isHide);
/**
* 過(guò)濾出可見(jiàn)的Node
*/
mNodes = TreeHelper.filterVisibleNode(mAllNodes);
mInflater = LayoutInflater.from(context);
/**
* 設(shè)置節(jié)點(diǎn)點(diǎn)擊時(shí),可以展開(kāi)以及關(guān)閉;并且將ItemClick事件繼續(xù)往外公布
*/
mTree.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
expandOrCollapse(position);
if (onTreeNodeClickListener != null) {
onTreeNodeClickListener.onClick(mNodes.get(position),
position);
}
}
});
}
/**
* 相應(yīng)ListView的點(diǎn)擊事件 展開(kāi)或關(guān)閉某節(jié)點(diǎn)
*
* @param position
*/
public void expandOrCollapse(int position) {
Node n = mNodes.get(position);
if (n != null)// 排除傳入?yún)?shù)錯(cuò)誤異常
{
if (!n.isLeaf()) {
n.setExpand(!n.isExpand());
mNodes = TreeHelper.filterVisibleNode(mAllNodes);
notifyDataSetChanged();// 刷新視圖
}
}
}
@Override
public int getCount() {
return mNodes.size();
}
@Override
public Object getItem(int position) {
return mNodes.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
final Node node = mNodes.get(position);
convertView = getConvertView(node, position, convertView, parent);
// 設(shè)置內(nèi)邊距
convertView.setPadding(node.getLevel() * 30, 3, 3, 3);
if (!node.isHideChecked()) {
//獲取各個(gè)節(jié)點(diǎn)所在的父布局
RelativeLayout myView = (RelativeLayout) convertView;
//父布局下的CheckBox
CheckBox cb = (CheckBox) myView.getChildAt(1);
cb.setOnCheckedChangeListener(new OnCheckedChangeListener(){
@Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
TreeHelper.setNodeChecked(node, isChecked);
List<Node> checkedNodes = new ArrayList<Node>();
for(Node n:mAllNodes){
if(n.isChecked()){
checkedNodes.add(n);
}
}
onTreeNodeClickListener.onCheckChange(node,position,checkedNodes);
TreeListViewAdapter.this.notifyDataSetChanged();
}
});
}
return convertView;
}
public abstract View getConvertView(Node node, int position,
View convertView, ViewGroup parent);
/**
* 更新
* @param isHide
*/
public void updateView(boolean isHide){
for(Node node:mAllNodes){
node.setHideChecked(isHide);
}
this.notifyDataSetChanged();
}
}
node 模型類(lèi)
package com.example.customtreeviewdemo.bean;
public class MyNodeBean {
/**
* 節(jié)點(diǎn)Id
*/
private int id;
/**
* 節(jié)點(diǎn)父id
*/
private int pId;
/**
* 節(jié)點(diǎn)name
*/
private String name;
/**
*
*/
private String desc;
/**
* 節(jié)點(diǎn)名字長(zhǎng)度
*/
private long length;
public MyNodeBean(int id, int pId, String name) {
super();
this.id = id;
this.pId = pId;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getPid() {
return pId;
}
public void setPid(int pId) {
this.pId = pId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public long getLength() {
return length;
}
public void setLength(long length) {
this.length = length;
}
}
TreeHelper這個(gè)也是核心操作類(lèi),主要功能是將業(yè)務(wù)數(shù)據(jù)和節(jié)點(diǎn)數(shù)據(jù)進(jìn)行匹配
package com.example.customtreeviewdemo.tree;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import com.example.customtreeviewdemo.R;
public class TreeHelper {
/**
* 根據(jù)所有節(jié)點(diǎn)獲取可見(jiàn)節(jié)點(diǎn)
*
* @param allNodes
* @return
*/
public static List<Node> filterVisibleNode(List<Node> allNodes) {
List<Node> visibleNodes = new ArrayList<Node>();
for (Node node : allNodes) {
// 如果為根節(jié)點(diǎn),或者上層目錄為展開(kāi)狀態(tài)
if (node.isRoot() || node.isParentExpand()) {
setNodeIcon(node);
visibleNodes.add(node);
}
}
return visibleNodes;
}
/**
* 獲取排序的所有節(jié)點(diǎn)
*
* @param datas
* @param defaultExpandLevel
* @return
* @throws IllegalArgumentException
* @throws IllegalAccessException
*/
public static <T> List<Node> getSortedNodes(List<T> datas,
int defaultExpandLevel, boolean isHide)
throws IllegalAccessException, IllegalArgumentException {
List<Node> sortedNodes = new ArrayList<Node>();
// 將用戶(hù)數(shù)據(jù)轉(zhuǎn)化為L(zhǎng)ist<Node>
List<Node> nodes = convertData2Nodes(datas, isHide);
// 拿到根節(jié)點(diǎn)
List<Node> rootNodes = getRootNodes(nodes);
// 排序以及設(shè)置Node間關(guān)系
for (Node node : rootNodes) {
addNode(sortedNodes, node, defaultExpandLevel, 1);
}
return sortedNodes;
}
/**
* 把一個(gè)節(jié)點(diǎn)上的所有的內(nèi)容都掛上去
*/
private static void addNode(List<Node> nodes, Node node,
int defaultExpandLeval, int currentLevel) {
nodes.add(node);
if (defaultExpandLeval >= currentLevel) {
node.setExpand(true);
}
if (node.isLeaf())
return;
for (int i = 0; i < node.getChildrenNodes().size(); i++) {
addNode(nodes, node.getChildrenNodes().get(i), defaultExpandLeval,
currentLevel + 1);
}
}
/**
* 獲取所有的根節(jié)點(diǎn)
*
* @param nodes
* @return
*/
public static List<Node> getRootNodes(List<Node> nodes) {
List<Node> rootNodes = new ArrayList<Node>();
for (Node node : nodes) {
if (node.isRoot()) {
rootNodes.add(node);
}
}
return rootNodes;
}
/**
* 將泛型datas轉(zhuǎn)換為node
*
* @param datas
* @return
* @throws IllegalArgumentException
* @throws IllegalAccessException
*/
public static <T> List<Node> convertData2Nodes(List<T> datas, boolean isHide)
throws IllegalAccessException, IllegalArgumentException {
List<Node> nodes = new ArrayList<Node>();
Node node = null;
for (T t : datas) {
int id = -1;
int pId = -1;
String name = null;
Class<? extends Object> clazz = t.getClass();
Field[] declaredFields = clazz.getDeclaredFields();
/**
* 與MyNodeBean實(shí)體一一對(duì)應(yīng)
*/
for (Field f : declaredFields) {
if ("id".equals(f.getName())) {
f.setAccessible(true);
id = f.getInt(t);
}
if ("pId".equals(f.getName())) {
f.setAccessible(true);
pId = f.getInt(t);
}
if ("name".equals(f.getName())) {
f.setAccessible(true);
name = (String) f.get(t);
}
if ("desc".equals(f.getName())) {
continue;
}
if ("length".equals(f.getName())) {
continue;
}
if (id == -1 && pId == -1 && name == null) {
break;
}
}
node = new Node(id, pId, name);
node.setHideChecked(isHide);
nodes.add(node);
}
/**
* 比較nodes中的所有節(jié)點(diǎn),分別添加children和parent
*/
for (int i = 0; i < nodes.size(); i++) {
Node n = nodes.get(i);
for (int j = i + 1; j < nodes.size(); j++) {
Node m = nodes.get(j);
if (n.getId() == m.getpId()) {
n.getChildrenNodes().add(m);
m.setParent(n);
} else if (n.getpId() == m.getId()) {
n.setParent(m);
m.getChildrenNodes().add(n);
}
}
}
for (Node n : nodes) {
setNodeIcon(n);
}
return nodes;
}
/**
* 設(shè)置打開(kāi),關(guān)閉icon
*
* @param node
*/
public static void setNodeIcon(Node node) {
if (node.getChildrenNodes().size() > 0 && node.isExpand()) {
node.setIcon(R.drawable.tree_expand);
} else if (node.getChildrenNodes().size() > 0 && !node.isExpand()) {
node.setIcon(R.drawable.tree_econpand);
} else
node.setIcon(-1);
}
public static void setNodeChecked(Node node, boolean isChecked) {
// 自己設(shè)置是否選擇
node.setChecked(isChecked);
/**
* 非葉子節(jié)點(diǎn),子節(jié)點(diǎn)處理
*/
setChildrenNodeChecked(node, isChecked);
/** 父節(jié)點(diǎn)處理 */
setParentNodeChecked(node);
}
/**
* 非葉子節(jié)點(diǎn),子節(jié)點(diǎn)處理
*/
private static void setChildrenNodeChecked(Node node, boolean isChecked) {
node.setChecked(isChecked);
if (!node.isLeaf()) {
for (Node n : node.getChildrenNodes()) {
// 所有子節(jié)點(diǎn)設(shè)置是否選擇
setChildrenNodeChecked(n, isChecked);
}
}
}
/**
* 設(shè)置父節(jié)點(diǎn)選擇
*
* @param node
*/
private static void setParentNodeChecked(Node node) {
/** 非根節(jié)點(diǎn) */
if (!node.isRoot()) {
Node rootNode = node.getParent();
boolean isAllChecked = true;
for (Node n : rootNode.getChildrenNodes()) {
if (!n.isChecked()) {
isAllChecked = false;
break;
}
}
if (isAllChecked) {
rootNode.setChecked(true);
} else {
rootNode.setChecked(false);
}
setParentNodeChecked(rootNode);
}
}
}
核心的代碼就是這些,希望對(duì)大家有幫助。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Flutter手勢(shì)密碼的實(shí)現(xiàn)示例(附demo)
本文主要介紹了Flutter手勢(shì)密碼,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08
monkeyrunner之安卓開(kāi)發(fā)環(huán)境搭建教程(1)
這篇文章主要介紹了monkeyrunner之安卓開(kāi)發(fā)環(huán)境搭建教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11
利用Flutter實(shí)現(xiàn)背景圖片毛玻璃效果實(shí)例
Flutter沒(méi)有單獨(dú)的模糊處理容器,需要部件層層疊加實(shí)現(xiàn)模糊效果,下面這篇文章主要給大家介紹了關(guān)于利用Flutter實(shí)現(xiàn)背景圖片毛玻璃效果的相關(guān)資料,需要的朋友可以參考下2022-06-06
Android中對(duì)xml文件解析的3種方式總結(jié)
這篇文章主要給大家介紹了關(guān)于Android中對(duì)xml文件解析的3種方式,分別是 Dom 、 SAX 和 dom4j,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-02-02
深入剖析Android的Volley庫(kù)中的圖片加載功能
這篇文章主要介紹了Android的Volley框架中的圖片加載功能,從源碼剖析了Volley加載圖片時(shí)的請(qǐng)求隊(duì)列處理等方面,需要的朋友可以參考下2016-04-04
Android AsyncTask完全解析 帶你從源碼的角度徹底理解
這篇文章主要是針對(duì)Android AsyncTask進(jìn)行完全解析,帶你從源碼的角度徹底理解,感興趣的小伙伴們可以參考一下2016-04-04
Android入門(mén)教程之ListView的具體使用詳解
列表作為最常用的控件之一,還是有必要好好學(xué)習(xí)的,本章以一個(gè)初學(xué)者的角度來(lái)學(xué)習(xí) ListView,ListView的屬性,以及BaseAdapter簡(jiǎn)單定義,至于ListView優(yōu)化這些, 我們一步步來(lái)2021-10-10
Android Studio配置反混淆的實(shí)現(xiàn)
這篇文章主要介紹了Android Studio如何混淆的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10

