Android 省市区的树节点控件

今天做的功能,提供给有需要的人。

利用省市区JSON数据,实现多级树弹出框的显示,效果如下:

用到的 JSON 一部分数据如下:

{
    "result": [
        {
            "BM": "440",
            "Child": [],
            "LX": "1",
            "MC": "外省",
            "SJBM": "0"
        },
        {
            "BM": "36",
            "Child": [
                {
                    "BM": "3606",
                    "Child": [],
                    "LX": "1",
                    "MC": "九江市",
                    "SJBM": "36"
                }
            ],
            "LX": "1",
            "MC": "江西省",
            "SJBM": "0"
        },
        {
            "BM": "42",
            "Child": [],
            "LX": "1",
            "MC": "湖北省",
            "SJBM": "0"
        },
        {
            "BM": "44",
            "Child": [],
            "LX": "1",
            "MC": "广东省",
            "SJBM": "0"
        }
    ],
    "success": true
}

自定义 Node 类,用于封装 JSON 数据,以及绑定 父Node 和 子Node 的关系,代码如下:

public class Node {

    /** 当前节点层级 */
    private int deepLevel;

    /** 当前节点内容 */
    private String label;

    /** 父节点 */
    private Node parentNode;

    /** 是否展开了 */
    private boolean isExpand;
    
    /** 节点的 ID */
    private String nodeId;

    public String getNodeId() {
        return nodeId;
    }

    public void setNodeId(String nodeId) {
        this.nodeId = nodeId;
    }

    public boolean isExpand() {
        return isExpand;
    }

    public void setExpand(boolean isExpand) {
        this.isExpand = isExpand;
    }

    /** 子节点列表 */
    private List<Node> childList;

    public int getChildCount() {
        if (childList == null) {
            return 0;
        } else {
            return childList.size();
        }
    }

    public List<Node> getChildList() {
        return childList;
    }

    /**
     * @param parent 其父节点
     * @param label 本节点的内容
     */
    public Node(Node parent, String label, String nodeId) {
        this.parentNode = parent;
        this.label = label;
        if (parent != null) {
            if (parent.childList == null) {
                parent.childList = new ArrayList<Node>();
            }
            parent.childList.add(this);
            this.deepLevel = parent.deepLevel + 1;
        }
        this.nodeId = nodeId;
    }

    public int getDeepLevel() {
        return deepLevel;
    }

    public void setDeepLevel(int deepLevel) {
        this.deepLevel = deepLevel;
    }

    public String getLabel() {
        return label;
    }

    public void setLabel(String label) {
        this.label = label;
    }

    public Node getParentNode() {
        return parentNode;
    }

    public void setParentNode(Node parentNode) {
        this.parentNode = parentNode;
    }

    @Override
    public String toString() {
        return "Node [deepLevel=" + deepLevel + ", label=" + label
                + ", isExpand=" + isExpand + ", nodeId=" + nodeId
                + ", childList=" + childList + "]";
    }



}

然后自定义用于填充数据和添加子View的 TreeAdapter,代码如下:

public class TreeAdapter extends BaseAdapter {

    private Context context;
    /** 当前需要显示的数据 */
    private List<Node> mDataList;
    /** 节点点击事件 */
    NodeOnClickListener mNodeOnClickListener;

    public TreeAdapter(Context context, List<Node> objects) {
        this.context = context;
        this.mDataList = objects;
        mNodeOnClickListener = new NodeOnClickListener();
    }

    @Override
    public int getCount() {
        return mDataList.size();
    }

    @Override
    public Node getItem(int position) {
        return mDataList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }
    
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if (convertView == null) {
            holder = new ViewHolder();
            convertView = LayoutInflater.from(context).inflate(R.layout.tree_area_item, parent, false);
            holder.icon = (ImageView) convertView.findViewById(R.id.icon);
            holder.label = (TextView) convertView.findViewById(R.id.label);
            convertView.setTag(holder);
            convertView.setOnClickListener(mNodeOnClickListener);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }
        Node node = getItem(position);
        // 设置深度,层级越深距离左边越远
        convertView.setPadding(30 * node.getDeepLevel()+10, 10, 10, 10);
        if (node.getChildCount() == 0) {// 如果沒有子节点说明为叶子节点,去掉icon
            holder.icon.setVisibility(View.INVISIBLE);
        } else {
            holder.icon.setVisibility(View.VISIBLE);
            if (node.isExpand()) {//如果需要显示判断一下是否是需要显示展开的样式
                holder.icon.setImageResource(R.drawable.expand);
            } else {
                holder.icon.setImageResource(R.drawable.unexpand);
            }
        }
        holder.label.setTag(node);//label的tag里面存放Node,方便点击事件处理
        holder.label.setText(node.getLabel());

        return convertView;
    }

    /** 目录点击事件 */
    class NodeOnClickListener implements OnClickListener {
        @Override
        public void onClick(View v) {
            ViewHolder holder = (ViewHolder) v.getTag();
            Node node = (Node) holder.label.getTag();
            // 如果存在孩子节点就需要做展开或者隐藏操作
            if (node.getChildCount() > 0) {
                TreeUtils.filterNodeList(mDataList, node);
            } else {
                Toast.makeText(context, "你点击了" + node.getLabel(), 0).show();
                return;
            }
            notifyDataSetChanged();
        }
    }
    
    /**
     * 获取所选地址
     * @param lastNode
     * @param address
     * @return
     */
    private String getNodeAddress(Node lastNode, String address) {
        Node parentNode = lastNode.getParentNode();
        if (parentNode != null) {
            return getNodeAddress(parentNode, parentNode.getLabel() + address);
        } else {
            return address;
        }
    }

    private static class ViewHolder {
        private ImageView icon;
        private TextView label;
    }

}

然后写一个对树节点进行操作的工具类,代码如下:

public class TreeUtils {

    /** 每次单击的item位置 */
    private static int clickPosition;
    
    /** 增加或者删除clickItem里面的子列表 */
    public static void filterNodeList(List<Node> showList, Node clickItem) {
        if (!clickItem.isExpand()) {// 需要展示,添加
            for (int i = 0; i < showList.size(); i++) {// 找到对应位置插入
                if (showList.get(i) == clickItem) {
                    clickPosition = i;
                    showChildNode(showList, clickItem);
                    clickItem.setExpand(true);
                    break;
                }
            }
        } else {// 需要隐藏,删除
            hideChildNode(showList, clickItem);
            clickItem.setExpand(false);
        }
    }

    /** 递归隐藏(删除)某一个节点下的所有子node */
    private static void hideChildNode(List<Node> showList, Node nodeItem) {
        for (Node item : nodeItem.getChildList()) {
            // item.setExpand(false);//还原每个item状态
            showList.remove(item);
            // 如果当前item存在孩子节点,递归删除
            if (item.getChildCount() > 0) {
                hideChildNode(showList, item);
            }
        }
    }


    /** 递归获取之前状态,如果之前已经展开过就一并加入到List中 */
    private static void showChildNode(List<Node> showList, Node nodeItem) {
        for (int i = 0; i < nodeItem.getChildCount(); i++) {
            // 如果添加的节点是展开的就递归去加入他所有的子节点
            showList.add(++clickPosition, nodeItem.getChildList().get(i));
            if (nodeItem.getChildList().get(i).isExpand()) {
                showChildNode(showList, nodeItem.getChildList().get(i));
            }
        }
    }
}

最后在MainActivity中解析JSON并填充到Adapter中,进行功能的实现,代码如下:

    protected void initTreeAreaNodes(String jsonString) {// 通过 JsonArray 将 JSON 数据转化成 List 集合数据

       List<Map> treeAreas= new ArrayList<>();
        try {
           JsonArray array = new JsonParser().parse(jsonString)
               .getAsJsonArray();
          for (final JsonElement elem : array) {
          treeAreas.add(gson.fromJson(elem, cls));
         }
       } catch (Exception e) {
       }

        List<Node> mTreeAreaNodes = new ArrayList<>();
        getNodes(treeAreas, null);
    }

    

// 利用 递归 解析 JSON 数据,并得到 List<Node>集合

    @SuppressWarnings({ "unchecked", "rawtypes" })
    private void getNodes(List<Map> treeAreas, Node node) {
        for (int i = 0; i < treeAreas.size(); i++) {
            Map treeMap = treeAreas.get(i);
            List<Map> treeChilds = (List<Map>) treeMap.get("Child");         
            Node nodeChild = new Node(node, treeMap.get("MC").toString().trim(), 
                    treeMap.get("BM").toString().trim());
            getNodes(treeChilds, nodeChild);
            if(node == null){
                mTreeAreaNodes.add(nodeChild);
            }
        }
    }

    
    private void showTreeAreas() {
        View view = LayoutInflater.from(SignManageActivity.this).inflate(R.layout.tree_area_layout, null);
        ListView treeListView = (ListView) view.findViewById(R.id.tree_area_layout_listview);
        treeListView.setAdapter(new TreeAdapter(SignManageActivity.this, mTreeAreaNodes));
        AlertDialog.Builder builder = new AlertDialog.Builder(SignManageActivity.this, AlertDialog.THEME_HOLO_LIGHT);
        builder.setView(view);
        AlertDialog dialog = builder.create();
        dialog.setCancelable(true);
        dialog.setCanceledOnTouchOutside(true);
        dialog.show();
    }
所用到的布局tree_area_layout和tree_area_item代码如下:

tree_area_layout:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    
    <ListView
        android:id="@+id/tree_area_layout_listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="5dp" />

</LinearLayout>
tree_area_item:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:paddingBottom="5dp"
    android:paddingTop="5dp" >

    <ImageView
        android:id="@+id/icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="5dp"
        android:src="@drawable/unexpand" />

    <TextView
        android:id="@+id/label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_toRightOf="@id/icon"
        android:text="@string/app_name"
        android:textSize="18dip" />

</RelativeLayout>


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值