今天做的功能,提供给有需要的人。
利用省市区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>