本次要实现的是动态编码之三级菜单的实现,在实际应用开发中经常会使用到三级菜单,比如商城项目中的省市区,分类等等。
问题:界面加载是一次性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已经为刚才的问题做了伏笔。