最近,小弟在实现一个思维导图的开源控件。下面我简单介绍一下如下打造一个类似思维导图软件的ViewGroup。
建立模型
主要模型结构相对简单:TreeModel,NoteModel,NoteView,TreeView。
核心实现分布如下:
- TreeModel:树形结构的存储,树形结构的遍历,添加、删除节点;
- NoteModel:节点关联的指向,和Parent的指向;
- TreeView :绘制树形结构,对树形结构位置的纠正,实现View层的添加,删除,note关联绘制;
- NoteView:显示text;
编写位置计算核心代码
在核心代码中,我想和大家分享的是TreeView如何对多种Style(树形形状)进行适配的问题。因为我们的树形结构的表达多种的,有的是一个半树形图,有点是圆形展开的等。对于这个问题,作为程序员如何进行解耦能,采用Interface进行解构适配,统一行为。所以在这里我写了一个TreeLayoutManager进行管理树形的位置表达。这里我实现了一个RightTreeLayoutManager。代码概况如下:
接口
public interface TreeLayoutManager {
/**
* 进行树形结构的位置计算
*/
void onTreeLayout(TreeView treeView);
/**
* 位置分布好后的回调,用于确认ViewGroup的大小
*/
ViewBox onTreeLayoutCallBack();
/**
* 修正位置
*
* @param treeView
* @param next
*/
void correctLayout(TreeView treeView, NodeView next);
}
实现
public class RightTreeLayoutManager implements TreeLayoutManager{
final int msg_standard_layout = 1;
final int msg_correct_layout = 2;
final int msg_box_call_back = 3;
private ViewBox mViewBox;
private int mDy;
private int mDx;
private int mHeight;
public RightTreeLayoutManager(int dx, int dy, int height) {
mViewBox = new ViewBox();
this.mDx = dx;
this.mDy = dy;
this.mHeight = height;
}
@Override
public void onTreeLayout(final TreeView treeView) {
final TreeModel mTreeModel = treeView.getTreeModel();
if (mTreeModel != null) {
View rootView = treeView.findNodeViewFromNodeModel(mTreeModel.getRootNode());
if (rootView != null) {
rootTreeViewLayout((NodeView) rootView);
}
mTreeModel.addForTreeItem(new ForTreeItem>() {
@Override
public void next(int msg, NodeModel next) {
doNext(msg, next, treeView);
}
});
//基本布局
mTreeModel.ergodicTreeInWith(msg_standard_layout);
//纠正
mTreeModel.ergodicTreeInWith(msg_correct_layout);
mViewBox.clear();
mTreeModel.ergodicTreeInDeep(msg_box_call_back);
}
}
@Override
public ViewBox onTreeLayoutCallBack() {
if (mViewBox != null) {
return mViewBox;
} else {
return null;
}
}
/**
* 布局纠正
*
* @param treeView
* @param next
*/
public void correctLayout(TreeView treeView, NodeView next) {
//主要是纠正对于标准布局出现的错误,譬如,在图片纠正中的那种情况
//纠正需要对同层的Note进行拉伸
}
/**
* 标准分布
*
* @param treeView
* @param rootView
*/
private void standardLayout(TreeView treeView, NodeView rootView) {
//标准分布主要是在基于root节点进行排开
//对于奇数和偶数不同的情况进行排开
//中间向外计算位置
}
/**
* 移动
*
* @param rootView
* @param dy
*/
private void moveNodeLayout(TreeView superTreeView, NodeView rootView, int dy) {
//如果一个note节点进行了移动,那么它
//会影响到它的子节点的位置。
//所以要进行重新计算,把它的所有的Note位置进行位移
}
/**
* root节点的定位
*
* @param rootView
*/
private void rootTreeViewLayout(NodeView rootView) {
int lr = mDy;
int tr = mHeight / 2 - rootView.getMeasuredHeight() / 2;
int rr = lr + rootView.getMeasuredWidth();
int br = tr + rootView.getMeasuredHeight();
rootView.layout(lr, tr, rr, br);
}
}
View的连线
要实现对View和View的连线,只要在View的位置定了之后,就进行画线即可。用Sketch画个演示如下:
其中线为一个贝塞尔曲线。代码如下:
@Override
protected void dispatchDraw(Canvas canvas) {
if (mTreeModel != null) {
drawTreeLine(canvas, mTreeModel.getRootNode());
}
super.dispatchDraw(canvas);
}
/**
* 绘制树形的连线
*
* @param canvas
* @param root
*/
private void drawTreeLine(Canvas canvas, NodeModel root) {
NodeView fatherView = (NodeView) findNodeViewFromNodeModel(root);
if (fatherView != null) {
LinkedList> childNodes = root.getChildNodes();
for (NodeModel node : childNodes) {
//连线
drawLineToView(canvas, fatherView, findNodeViewFromNodeModel(node));
//递归
drawTreeLine(canvas, node);
}
}
}
/**
* 绘制两个View直接的连线
*
* @param canvas
* @param from
* @param to
*/
private void drawLineToView(Canvas canvas, View from, View to) {
if (to.getVisibility() == GONE) {
return;
}
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
float width = 2f;
paint.setStrokeWidth(dp2px(mContext, width));
paint.setColor(mContext.getResources().getColor(R.color.chelsea_cucumber));
int top = from.getTop();
int formY = top + from.getMeasuredHeight() / 2;
int formX = from.getRight();
int top1 = to.getTop();
int toY = top1 + to.getMeasuredHeight() / 2;
int toX = to.getLeft();
Path path = new Path();
path.moveTo(formX, formY);
path.quadTo(toX - dp2px(mContext, 15), toY, toX, toY);
canvas.drawPath(path, paint);
}
位置的纠正流程
位置纠正的问题;在对于我之前的位置的算法探索流程如下图,关键是写好已知的代码,之后纠正。
观察发现,所有的移动都是基于被操作的点开始的上下之分的上下移动。
最后
记得在GitHub上给我一个星吧。https://github.com/owant/ThinkMap
百度应用市场:http://shouji.baidu.com/software/11238419.html