本次要实现的是动态编码之三级菜单的实现,在实际应用开发中经常会使用到三级菜单,比如商城项目中的省市区,分类等等。

问题:界面加载是一次性new 大量的控件还是先加载一级菜单,点击一级菜单加载他对应的二级菜单

答:这个很明显是后者更符合开发思维,因为客户不可能每个级别都一一点开,能点开所有级别的只有万恶的测试人员。

界面加载方案:

1.数据进行一次性加载(一次把服务端传递过来的数据解析封装到数据模型)

2.先加载一级菜单,点击一级菜单加载他对应的二级菜单


技术要点:动态编写界面,递归算法,javaBean;


实现过程

1.建立javabean封装数据

package com.xiaoyao.android.mytree;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by Administrator on 2016/6/18.
 */
public class Node {
    private int id;//当前节点的自身的ID;
    private int pId = 0;//根节点pId
    private String name;//菜单的名字
    private int level;//当前的级别
    private boolean isExpand = false;//是否只展开一个子菜单
    private Node parent;//父亲节点
    private List<Node> children=new ArrayList<>();

    public Node() {
        super();
    }
    public Node(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 int getLevel() {
        return parent==null?0:parent.getLevel()+1;
    }

    public void setLevel(int level) {
        this.level = level;
    }

    public boolean isExpand() {
        return isExpand;
    }

    public void setExpand(boolean expand) {
        isExpand = expand;
        if (!isExpand){
            for (Node node:children){
                node.setExpand(isExpand);
            }
        }
    }

    public Node getParent() {
        return parent;
    }

    public void setParent(Node parent) {
        this.parent = parent;
    }

    public List<Node> getChildren() {
        return children;
    }

    public void setChildren(List<Node> children) {
        this.children = children;
    }
    /**
     * 是否为根节点
     */
    public boolean isRoot(){
        return parent==null;
    }
    /**
     * 判断父节点是否展开
     */
    public boolean isParentExpand(){
        if (parent==null){
            return false;
        }
        return parent.isExpand();
    }
    /**
     * 是否为叶子节点
     */
    public boolean isLeaf(){
        return children.size()==0;
    }
}

以上要特别说明的是getLevel()方法在该方法中返回值是当前的级别,在此通过了递归的算法得到当前级别的父级别然后+1从而得到当前的级别

return parent == null ? 0 :parent.getLevel() + 1 ;


2.创建三级列表视图

package com.xiaoyao.android.mytree;

import android.content.Context;
import android.graphics.Color;
import android.text.Layout;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by Administrator on 2016/6/18.
 */
public class MyTree extends LinearLayout {
    List<Node> mDatas;//数据源
    private Context mContext;//上下文
    /**
     * 以下为容器参数
     */
    LayoutParams rootLayoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    LayoutParams itemLayoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
    LayoutParams dividerLayoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, 1);
    public MyTree(Context context) {
        super(context);
        this.mContext=context;
    }
    public void setData(List<Node> mDatas) {
        this.mDatas = mDatas;
        initView();
    }

    /**
     * 初始化界面
     */
    private void initView(){
        //添加一个滚动视图
        ScrollView scrollView=new ScrollView(mContext);
        scrollView.setLayoutParams(rootLayoutParams);
        scrollView.setBackgroundColor(Color.parseColor("#f0f0f0"));
        //添加一个线性布局作为一级菜单的容器
        LinearLayout rootLayout=new LinearLayout(mContext);
        rootLayout.setLayoutParams(rootLayoutParams);
        rootLayout.setOrientation(LinearLayout.VERTICAL);
        
        //加载一级菜单
        //得到数据源中所有的一级菜单
        List<Node> rootNodes=getListByPid(0);

        for (int i=0;i<rootNodes.size();i++){
            Node rootNode=rootNodes.get(i);
            //初始化一级布局,在这里只是简单的使用TextView作为布局
            TextView firstLevelView=new TextView(mContext);
            firstLevelView.setLayoutParams(itemLayoutParams);
            //添加ID
            firstLevelView.setId(rootNode.getId());
            //设置名字
            firstLevelView.setText(rootNode.getName());
            firstLevelView.setTextColor(Color.WHITE);
            firstLevelView.setBackgroundColor(Color.BLUE);
            firstLevelView.setPadding(30,30,30,30);

            //添加一级菜单到一级菜单的容器中
            rootLayout.addView(firstLevelView);
            //添加分割线,没添加一个一级菜单添加一条横线
            rootLayout.addView(getDivider());
            //准备加载对应的子菜单(2级菜单容器)
            LinearLayout secondLayout=new LinearLayout(mContext);
            secondLayout.setLayoutParams(itemLayoutParams);
            secondLayout.setOrientation(LinearLayout.VERTICAL);
            //在未点击时设置2级菜单容器隐藏不可见
            secondLayout.setVisibility(GONE);
            //让一级菜单和他自己对应的2级菜单有关联,做标记
            firstLevelView.setTag(secondLayout);
            //将2级菜单的容器添加到父一级菜单容器中
            rootLayout.addView(secondLayout);

            //添加完善一级菜单的点击事件
            firstLevelView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    //得到二级菜单的布局容器
                    LinearLayout parentLayout= (LinearLayout) v.getTag();
                    if (parentLayout.getChildCount()==0){
                        //表示没有添加过
                        addMenuChild(parentLayout,v.getId());
                    }else if (parentLayout.isShown()){
                        parentLayout.setVisibility(View.GONE);
                    }else{
                        parentLayout.setVisibility(View.VISIBLE);
                    }

                }
            });
        }
        scrollView.addView(rootLayout);
        addView(scrollView);
    }

    private void addMenuChild(LinearLayout parentLayout, int id) {
        //设置容器显示
        parentLayout.setVisibility(VISIBLE);
        //得到该级别下的猜的的数据源
        List<Node>childNodes=getListByPid(id);
        if (childNodes.size()>0){
            for (int i=0;i<childNodes.size();i++){
                Node childNode=childNodes.get(i);
                //创建2级菜单
                TextView secondLevelView=new TextView(mContext);
                secondLevelView.setId(childNode.getId());
                secondLevelView.setLayoutParams(itemLayoutParams);
                secondLevelView.setText(childNode.getName());
                secondLevelView.setTextColor(Color.BLACK);
                List<Node> grandChildNode=getListByPid(childNode.getpId());
                if (grandChildNode.size() > 0){
                    secondLevelView.setPadding(60, 30, 30, 30);
                    secondLevelView.setTextColor(Color.GRAY);
                    secondLevelView.setBackgroundColor(Color.parseColor("#f0f0f0"));
                }else{
                    secondLevelView.setPadding(120, 30, 30, 30);
                    secondLevelView.setTextColor(Color.BLACK);
                    secondLevelView.setBackgroundColor(Color.WHITE);
                }

                //2级容器将2级菜单的item保存起来
                parentLayout.addView(secondLevelView);
                parentLayout.addView(getDivider());
                //准备下一级子菜单
                LinearLayout thirdLayout=new LinearLayout(mContext);
                thirdLayout.setLayoutParams(itemLayoutParams);
                thirdLayout.setOrientation(LinearLayout.VERTICAL);
                thirdLayout.setVisibility(View.GONE);
                secondLevelView.setTag(thirdLayout);
                parentLayout.addView(thirdLayout);

                //完善子菜单的点击事件
                secondLevelView.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        //得到三级菜单的布局容器
                        LinearLayout parentLayout= (LinearLayout) v.getTag();
                        if (parentLayout.getChildCount()==0){
                            //表示没有加载过
                            addMenuChild(parentLayout,v.getId());
                        }else if (parentLayout.isShown()){
                            parentLayout.setVisibility(GONE);
                        }else {
                            parentLayout.setVisibility(VISIBLE);
                        }

                    }
                });
            }
        }else {
            Toast.makeText(mContext, "没有数据,别点了!",Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * 根据pid得到对象的菜单集合
     * @param pid
     * @return
     */
    private List<Node> getListByPid(int pid) {
        List<Node> resultNodes=new ArrayList<>();
        for (int i=0;i<mDatas.size();i++){
            Node node=mDatas.get(i);
            if (node.getpId()==pid){
                resultNodes.add(node);
            }
        }
        return resultNodes;
    }

    /**
     * 添加横线视图
     * @return
     */
    public View getDivider() {
        View divider=new View(mContext);
        divider.setLayoutParams(dividerLayoutParams);
        divider.setBackgroundColor(Color.GRAY);
        return divider;
    }
}



3.创建数据源并且展示三级菜单

package com.xiaoyao.android.mytree;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import java.util.ArrayList;
import java.util.List;

/**
 * 动态编码实现我的三级菜单
 */
public class MainActivity extends AppCompatActivity {
    public List<Node>mDatas=new ArrayList<>();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initDatas();
        //动态创建我们的三级菜单,递归算法
        MyTree myTree=new MyTree(this);
        myTree.setData(mDatas);
        setContentView(myTree);
    }

    //准备好数据源(开发获取Json  xml数据源解析)
    private void initDatas() {
        // id , pid , label , 其他属性
        mDatas.add(new Node(1, 0, "游戏"));
        mDatas.add(new Node(2, 0, "文档"));
        mDatas.add(new Node(3, 0, "程序"));
        mDatas.add(new Node(4, 0, "视频"));
        mDatas.add(new Node(5, 0, "音乐"));
        mDatas.add(new Node(6, 0, "照片"));
        mDatas.add(new Node(7, 0, "学习"));
        mDatas.add(new Node(8, 0, "娱乐"));
        mDatas.add(new Node(9, 0, "美食"));
        mDatas.add(new Node(10, 0, "备忘录"));

        mDatas.add(new Node(11, 1, "DOTA"));
        mDatas.add(new Node(12, 1, "LOL"));
        mDatas.add(new Node(13, 1, "war3"));

        mDatas.add(new Node(14, 11, "剑圣"));
        mDatas.add(new Node(15, 11, "敌法"));
        mDatas.add(new Node(16, 11, "影魔"));

        mDatas.add(new Node(17, 12, "德玛西亚"));
        mDatas.add(new Node(18, 12, "潘森"));
        mDatas.add(new Node(19, 12, "蛮族之王"));

        mDatas.add(new Node(20, 13, "人族"));
        mDatas.add(new Node(21, 13, "兽族"));
        mDatas.add(new Node(22, 13, "不死族"));

        mDatas.add(new Node(23, 2, "需求文档"));
        mDatas.add(new Node(24, 2, "原型设计"));
        mDatas.add(new Node(25, 2, "详细设计文档"));

        mDatas.add(new Node(26, 23, "需求调研"));
        mDatas.add(new Node(27, 23, "需求规格说明书"));
        mDatas.add(new Node(28, 23, "需求报告"));

        mDatas.add(new Node(29, 24, "QQ原型"));
        mDatas.add(new Node(30, 24, "微信原型"));

        mDatas.add(new Node(31, 25, "刀塔传奇详细设计"));
        mDatas.add(new Node(32, 25, "羽禾直播设计"));
        mDatas.add(new Node(33, 25, "YNedut设计"));
        mDatas.add(new Node(34, 25, "微信详细设计"));

        mDatas.add(new Node(35, 3, "面向对象"));
        mDatas.add(new Node(36, 3, "非面向对象"));

        mDatas.add(new Node(37, 35, "C++"));
        mDatas.add(new Node(38, 35, "JAVA"));
        mDatas.add(new Node(39, 36, "Javascript"));
        mDatas.add(new Node(40, 36, "C"));

        mDatas.add(new Node(41, 4, "电视剧"));
        mDatas.add(new Node(42, 4, "电影"));
        mDatas.add(new Node(43, 4, "综艺"));
        mDatas.add(new Node(44, 4, "动画"));

        mDatas.add(new Node(45, 41, "花千骨"));
        mDatas.add(new Node(46, 41, "三国演义"));
        mDatas.add(new Node(47, 41, "匆匆那年"));
        mDatas.add(new Node(48, 41, "亮剑"));

        mDatas.add(new Node(49, 42, "金刚狼"));
        mDatas.add(new Node(50, 42, "复仇者联盟"));
        mDatas.add(new Node(51, 42, "碟中谍"));
        mDatas.add(new Node(52, 42, "谍影重重"));

        mDatas.add(new Node(53, 43, "极限挑战"));
        mDatas.add(new Node(54, 43, "奔跑吧兄弟"));
        mDatas.add(new Node(55, 43, "我去上学啦"));
        mDatas.add(new Node(56, 43, "中国好声音"));

        mDatas.add(new Node(57, 44, "火影忍者"));
        mDatas.add(new Node(58, 44, "海贼王"));
        mDatas.add(new Node(59, 44, "哆啦A梦"));
        mDatas.add(new Node(60, 44, "蜡笔小新"));
    }
}



以上案例只是做了一个动态布局的一个简单的入门,上面案例的三级菜单有许多可扩展之处,比如能否像expandListView设置开关控制是否只展开一个子菜单之处能,其实是可以,我在javabean已经为刚才的问题做了伏笔。