Android studio 自定义树状下拉框,且用AlertDialog弹出
目录
一. 简介
1.1 效果图
二. 设计思路
2.1 数据设计
2.2 递归判断层级
三. 开发过程
3.1 创建一个我的节点类
3.2 再创建一个节点NodeVo类
3.3 把下面两张图片放入res/drawable
3.4 创建TreeUtil工具类
3.5 右键创建一个Empty Activity 命名为:AlertTreectivity
3.6 在res/layout创建一个view_tree.xml文件
3.7 在res/layout下创建tree_item.xml文件
3.8 创建一个TreeListViewAdapter适配器类
3.9 创建一个属于自己自定义的MyTreeListViewAdapter适配器类
3.10 修改AlertTreectivity类里代码
3.11 更改AndroidManifest配置文件,使运行的时候访问AlertTreectivity类
四. 总结
五. 代码开源
六. 参考文献
一. 简介
最近接到一个需求,需要开发一个树状的下拉框,于是就开始百度,经过大量的查找和自己一些的改进,终于做出一个实用简单的 Android 树状下拉框。
1.1 效果图
二. 设计思路
2.1 数据设计
树状下拉框第一个想到的就层级的判断 ,例如谁是谁的上一层,谁是谁的下一层,所以数据只需要3个值就行了,分别是节点ID,节点名称,父节点;如下图表,湖南和广东都是中国的,所以他们的父节点都是1,深圳的级别在广东之下,所以父节点对应的广东ID就是3。
设计表如下:
ID 节点名称 父节点
1 中国 0
2 湖南 1
3 广东 1
4 深圳 3
2.2 递归判断层级
数据设计完成,下一步就是如何判断他们层级关系,就要用到递归了。由于本人是萌新,递归的逻辑是在网上参考别人的,所以大致原理是这样,让我们直接进入开发过程。
三. 开发过程
3.1 创建一个我的节点类
package top.xiewenwen.alerttreeview.vo;public class MyNodeVo {/*** 节点Id*/private int id;/*** 节点父id*/private int pId;/*** 节点name*/private String name;/****/private String desc;/*** 节点名字长度*/private long length;public MyNodeVo(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;}
}
3.2 再创建一个节点NodeVo类
package top.xiewenwen.alerttreeview.vo;import java.util.ArrayList;
import java.util.List;public class NodeVo {/*** 节点id*/private int id;/*** 父节点id*/private int pId;/*** 是否展开*/private boolean isExpand = false;private boolean isChecked = false;private boolean isHideChecked = true;/*** 节点名字*/private String name;/*** 节点级别*/private int level;/*** 节点展示图标*/private int icon;/*** 节点所含的子节点*/private List childrenNodeVos = new ArrayList<>();/*** 节点的父节点*/private NodeVo parent;public NodeVo(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 boolean isExpand() {return isExpand;}/*** 当父节点收起,其子节点也收起* @param isExpand*/public void setExpand(boolean isExpand) {this.isExpand = isExpand;if (!isExpand) {for (NodeVo NodeVo : childrenNodeVos) {NodeVo.setExpand(isExpand);}}}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getLevel() {return parent == null ? 0 : parent.getLevel() + 1;}public void setLevel(int level) {this.level = level;}public int getIcon() {return icon;}public void setIcon(int icon) {this.icon = icon;}public List getChildrenNodeVos() {return childrenNodeVos;}public NodeVo getParent() {return parent;}public void setParent(NodeVo parent) {this.parent = parent;}/*** 判断是否是根节点** @return*/public boolean isRoot() {return parent == null;}/*** 判断是否是叶子节点** @return*/public boolean isLeaf() {return childrenNodeVos.size() == 0;}/*** 判断父节点是否展开** @return*/public boolean isParentExpand(){if (parent == null)return false;return parent.isExpand();}public boolean isChecked() {return isChecked;}public void setChecked(boolean isChecked) {this.isChecked = isChecked;}public boolean isHideChecked() {return isHideChecked;}public void setHideChecked(boolean isHideChecked) {this.isHideChecked = isHideChecked;}
}
3.3 把下面两张图片放入res/drawable
命名为:tree_econpand.png 命名为:tree_expand.png
3.4 创建TreeUtil工具类
package top.xiewenwen.alerttreeview.util;import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;import top.xiewenwen.alerttreeview.R;
import top.xiewenwen.alerttreeview.vo.NodeVo;public class TreeUtil {/*** 根据所有节点获取可见节点** @param allNodeVos* @return*/public static List filterVisibleNodeVo(List allNodeVos) {List visibleNodeVos = new ArrayList();for (NodeVo NodeVo : allNodeVos) {// 如果为根节点,或者上层目录为展开状态if (NodeVo.isRoot() || NodeVo.isParentExpand()) {setNodeVoIcon(NodeVo);visibleNodeVos.add(NodeVo);}}return visibleNodeVos;}/*** 获取排序的所有节点** @param datas* @param defaultExpandLevel* @return* @throws IllegalArgumentException* @throws IllegalAccessException*/public static List getSortedNodeVos(List datas,int defaultExpandLevel, boolean isHide)throws IllegalAccessException, IllegalArgumentException {List sortedNodeVos = new ArrayList();// 将用户数据转化为ListList NodeVos = convertData2NodeVos(datas, isHide);// 拿到根节点List rootNodeVos = getRootNodeVos(NodeVos);// 排序以及设置NodeVo间关系for (NodeVo NodeVo : rootNodeVos) {addNodeVo(sortedNodeVos, NodeVo, defaultExpandLevel, 1);}return sortedNodeVos;}/*** 把一个节点上的所有的内容都挂上去*/private static void addNodeVo(List NodeVos, NodeVo NodeVo,int defaultExpandLeval, int currentLevel) {NodeVos.add(NodeVo);if (defaultExpandLeval >= currentLevel) {NodeVo.setExpand(true);}if (NodeVo.isLeaf())return;for (int i = 0; i < NodeVo.getChildrenNodeVos().size(); i++) {addNodeVo(NodeVos, NodeVo.getChildrenNodeVos().get(i), defaultExpandLeval,currentLevel + 1);}}/*** 获取所有的根节点** @param NodeVos* @return*/public static List getRootNodeVos(List NodeVos) {List rootNodeVos = new ArrayList();for (NodeVo NodeVo : NodeVos) {if (NodeVo.isRoot()) {rootNodeVos.add(NodeVo);}}return rootNodeVos;}/*** 将泛型datas转换为NodeVo** @param datas* @return* @throws IllegalArgumentException* @throws IllegalAccessException*/public static List convertData2NodeVos(List datas, boolean isHide)throws IllegalAccessException, IllegalArgumentException {List NodeVos = new ArrayList();NodeVo NodeVo = null;for (T t : datas) {int id = -1;int pId = -1;String name = null;Class extends Object> clazz = t.getClass();Field[] declaredFields = clazz.getDeclaredFields();/*** 与MyNodeVoBean实体一一对应*/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;}}NodeVo = new NodeVo(id, pId, name);NodeVo.setHideChecked(isHide);NodeVos.add(NodeVo);}/*** 比较NodeVos中的所有节点,分别添加children和parent*/for (int i = 0; i < NodeVos.size(); i++) {NodeVo n = NodeVos.get(i);for (int j = i + 1; j < NodeVos.size(); j++) {NodeVo m = NodeVos.get(j);if (n.getId() == m.getpId()) {n.getChildrenNodeVos().add(m);m.setParent(n);} else if (n.getpId() == m.getId()) {n.setParent(m);m.getChildrenNodeVos().add(n);}}}for (NodeVo n : NodeVos) {setNodeVoIcon(n);}return NodeVos;}/*** 设置打开,关闭icon** @param NodeVo*/public static void setNodeVoIcon(NodeVo NodeVo) {if (NodeVo.getChildrenNodeVos().size() > 0 && NodeVo.isExpand()) {NodeVo.setIcon(R.drawable.tree_expand);} else if (NodeVo.getChildrenNodeVos().size() > 0 && !NodeVo.isExpand()) {NodeVo.setIcon(R.drawable.tree_econpand);} elseNodeVo.setIcon(-1);}public static void setNodeVoChecked(NodeVo NodeVo, boolean isChecked) {// 自己设置是否选择NodeVo.setChecked(isChecked);/*** 非叶子节点,子节点处理*/setChildrenNodeVoChecked(NodeVo, isChecked);/** 父节点处理 */setParentNodeVoChecked(NodeVo);}/*** 非叶子节点,子节点处理*/private static void setChildrenNodeVoChecked(NodeVo NodeVo, boolean isChecked) {NodeVo.setChecked(isChecked);if (!NodeVo.isLeaf()) {for (NodeVo n : NodeVo.getChildrenNodeVos()) {// 所有子节点设置是否选择setChildrenNodeVoChecked(n, isChecked);}}}/*** 设置父节点选择** @param NodeVo*/private static void setParentNodeVoChecked(NodeVo NodeVo) {/** 非根节点 */if (!NodeVo.isRoot()) {NodeVo rootNodeVo = NodeVo.getParent();boolean isAllChecked = true;for (NodeVo n : rootNodeVo.getChildrenNodeVos()) {if (!n.isChecked()) {isAllChecked = false;break;}}if (isAllChecked) {rootNodeVo.setChecked(true);} else {rootNodeVo.setChecked(false);}setParentNodeVoChecked(rootNodeVo);}}}
3.5 右键创建一个Empty Activity 命名为:AlertTreectivity
创建Activity会在res/layout自动创建一个activity_alert_treectivity.xml文件,修改这个布局文件里的代码
3.6 在res/layout创建一个view_tree.xml文件
3.7 在res/layout下创建tree_item.xml文件
3.8 创建一个TreeListViewAdapter适配器类
package top.xiewenwen.alerttreeview.Adapter;import java.util.List;import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.RelativeLayout;import top.xiewenwen.alerttreeview.util.TreeUtil;
import top.xiewenwen.alerttreeview.vo.NodeVo;/*** tree适配器** @param */
public abstract class TreeListViewAdapter extends BaseAdapter {protected Context mContext;/*** 存储所有可见的NodeVo*/protected List mNodeVos;protected LayoutInflater mInflater;/*** 存储所有的NodeVo*/protected List mAllNodeVos;/*** 点击的回调接口*/private OnTreeNodeClickListener onTreeNodeVoClickListener;public interface OnTreeNodeClickListener {/*** 处理NodeVo click事件** @param Node 节点对象* @param position 位置*/void onClick(NodeVo Node, int position);}public void setOnTreeNodeClickListener(OnTreeNodeClickListener onTreeNodeVoClickListener) {this.onTreeNodeVoClickListener = onTreeNodeVoClickListener;}/*** @param mTree* @param context* @param datas* @param defaultExpandLevel 默认展开几级树* @throws IllegalArgumentException* @throws IllegalAccessException*/public TreeListViewAdapter(ListView mTree, Context context, List datas, int defaultExpandLevel, boolean isHide)throws IllegalArgumentException, IllegalAccessException {mContext = context;/*** 对所有的Node进行排序*/mAllNodeVos = TreeUtil.getSortedNodeVos(datas, defaultExpandLevel, isHide);/*** 过滤出可见的Node*/mNodeVos = TreeUtil.filterVisibleNodeVo(mAllNodeVos);mInflater = LayoutInflater.from(context);/*** 设置节点点击时,可以展开以及关闭;并且将ItemClick事件继续往外公布*/mTree.setOnItemClickListener((parent, view, position, id) -> {expandOrCollapse(position);if (onTreeNodeVoClickListener != null) {onTreeNodeVoClickListener.onClick(mNodeVos.get(position), position);}});}/*** 相应ListView的点击事件 展开或关闭某节点** @param position 位置*/public void expandOrCollapse(int position) {NodeVo n = mNodeVos.get(position);if (n != null)// 排除传入参数错误异常{if (!n.isLeaf()) {n.setExpand(!n.isExpand());mNodeVos = TreeUtil.filterVisibleNodeVo(mAllNodeVos);notifyDataSetChanged();// 刷新视图}}}@Overridepublic int getCount() {return mNodeVos.size();}@Overridepublic Object getItem(int position) {return mNodeVos.get(position);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(final int position, View convertView, ViewGroup parent) {final NodeVo NodeVo = mNodeVos.get(position);convertView = getConvertView(NodeVo, position, convertView, parent);// 设置内边距convertView.setPadding(NodeVo.getLevel() * 30, 3, 3, 3);if (!NodeVo.isHideChecked()) {//获取各个节点所在的父布局RelativeLayout myView = (RelativeLayout) convertView;//父布局下的CheckBoxmyView.getChildAt(1);}return convertView;}public abstract View getConvertView(NodeVo NodeVo, int position, View convertView, ViewGroup parent);}
3.9 创建一个属于自己自定义的MyTreeListViewAdapter适配器类
package top.xiewenwen.alerttreeview.Adapter;import java.util.List;import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;import top.xiewenwen.alerttreeview.R;
import top.xiewenwen.alerttreeview.vo.NodeVo;public class MyTreeListViewAdapter extends TreeListViewAdapter {public MyTreeListViewAdapter(ListView mTree, Context context,List datas, int defaultExpandLevel, boolean isHide)throws IllegalArgumentException, IllegalAccessException {super(mTree, context, datas, defaultExpandLevel, isHide);}@Overridepublic View getConvertView(NodeVo node, int position, View convertView,ViewGroup parent) {ViewHolder viewHolder;if (convertView == null) {convertView = mInflater.inflate(R.layout.tree_item, parent, false);viewHolder = new ViewHolder();viewHolder.icon = convertView.findViewById(R.id.id_treenode_icon);viewHolder.label = convertView.findViewById(R.id.id_treenode_name);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());}viewHolder.label.setText(node.getName());return convertView;}private static final class ViewHolder {ImageView icon;TextView label;}
}
3.10 修改AlertTreectivity类里代码
package top.xiewenwen.alerttreeview;import androidx.appcompat.app.AppCompatActivity;import android.app.AlertDialog;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;import java.util.ArrayList;
import java.util.List;import top.xiewenwen.alerttreeview.Adapter.MyTreeListViewAdapter;
import top.xiewenwen.alerttreeview.vo.MyNodeVo;public class AlertTreectivity extends AppCompatActivity {private final List mDatas = new ArrayList<>();private AlertDialog mAlert;private Button choice;private MyTreeListViewAdapter adapter;private TextView getTreeIdName;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_alert_treectivity);getTreeIdName=findViewById(R.id.getTreeIdName);choice=findViewById(R.id.choice);initTreeListView();}private void initTreeListView() {// 从布局文件中加载 AlertDialog 需要显示的 viewLayoutInflater inflater = this.getLayoutInflater();View customView = inflater.inflate(R.layout.view_tree, null, false);AlertDialog.Builder builder = new AlertDialog.Builder(this);// 指定 AlertDialog 需要显示的 viewbuilder.setView(customView);// 点击空白处是否自动隐藏对话框(默认值为 true)builder.setCancelable(true);// 创建 AlertDialog 对象mAlert = builder.create();// 弹出自定义对话框choice.setOnClickListener(v -> mAlert.show());// 设置自定义 view 中的显示内容ListView treeLv = customView.findViewById(R.id.tree_lv);initDatas();try {adapter = new MyTreeListViewAdapter<>(treeLv, this,mDatas, 0, true);adapter.setOnTreeNodeClickListener((node, position) -> {if (node.isLeaf()) {getTreeIdName.setText(node.getName());mAlert.dismiss();}});} catch (IllegalArgumentException | IllegalAccessException e) {e.printStackTrace();}treeLv.setAdapter(adapter);// 自定义 view 中的关闭按钮的点击事件customView.findViewById(R.id.cancel).setOnClickListener(v -> mAlert.dismiss());}private void initDatas() {mDatas.add(new MyNodeVo(1, 0, "中国"));mDatas.add(new MyNodeVo(2, 1, "湖南"));mDatas.add(new MyNodeVo(3, 1, "广东"));mDatas.add(new MyNodeVo(4, 3, "深圳"));}}
3.11 更改AndroidManifest配置文件,使运行的时候访问AlertTreectivity类
四. 总结
学习了3种数据可以创建可视化的树状结构,用递归来循环判断他们的子集关系,学会了怎么通过A获取到B的View视图,学会了怎么重写Adapter适配器类。
五. 代码开源
Android TreeListView: 安卓点击按钮弹出树状下拉框,选择后显示在EditText上 (gitee.com)
个人博客:橘子披の博客 (xiewenwen.top)
六. 参考文献
TreeListView: TreeListView (gitee.com)
一手遮天 Android - view(弹出类): PopupWindow 基础 - webabcd - 博客园 (cnblogs.com)
标签:
相关文章
-
无相关信息