本次要实现的是动态编码之三级菜单的实现,在实际应用开发中经常会使用到三级菜单,比如商城项目中的省市区,分类等等。
问题:界面加载是一次性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以上要特别说明的是getLevel()方法在该方法中返回值是当前的级别,在此通过了递归的算法得到当前级别的父级别然后+1从而得到当前的级别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 getChildren() { return children; } public void setChildren(List 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; } }
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 { ListmDatas;//数据源 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 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 rootNodes=getListByPid(0); for (int i=0;i ;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 childNodes=getListByPid(id); if (childNodes.size()>0){ for (int i=0;i ;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 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 getListByPid(int pid) { List 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 ListmDatas=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已经为刚才的问题做了伏笔。