最近,小弟在實(shí)現(xiàn)一個(gè)思維導(dǎo)圖的開源控件藐石。下面我簡單介紹一下如下打造一個(gè)類似思維導(dǎo)圖軟件的ViewGroup滞诺。
建立模型
主要模型結(jié)構(gòu)相對簡單:TreeModel,NoteModel婉烟,NoteView,TreeView暇屋。
核心實(shí)現(xiàn)分布如下:
- TreeModel:樹形結(jié)構(gòu)的存儲(chǔ)似袁,樹形結(jié)構(gòu)的遍歷,添加咐刨、刪除節(jié)點(diǎn);
- NoteModel:節(jié)點(diǎn)關(guān)聯(lián)的指向,和Parent的指向;
- TreeView :繪制樹形結(jié)構(gòu)昙衅,對樹形結(jié)構(gòu)位置的糾正,實(shí)現(xiàn)View層的添加定鸟,刪除而涉,note關(guān)聯(lián)繪制;
- NoteView:顯示text;
編寫位置計(jì)算核心代碼
在核心代碼中,我想和大家分享的是TreeView如何對多種Style(樹形形狀)進(jìn)行適配的問題联予。因?yàn)槲覀兊臉湫谓Y(jié)構(gòu)的表達(dá)多種的啼县,有的是一個(gè)半樹形圖,有點(diǎn)是圓形展開的等沸久。對于這個(gè)問題季眷,作為程序員如何進(jìn)行解耦能,采用Interface進(jìn)行解構(gòu)適配卷胯,統(tǒng)一行為子刮。所以在這里我寫了一個(gè)TreeLayoutManager進(jìn)行管理樹形的位置表達(dá)。這里我實(shí)現(xiàn)了一個(gè)RightTreeLayoutManager窑睁。代碼概況如下:
接口
public interface TreeLayoutManager {
/**
* 進(jìn)行樹形結(jié)構(gòu)的位置計(jì)算
*/
void onTreeLayout(TreeView treeView);
/**
* 位置分布好后的回調(diào),用于確認(rèn)ViewGroup的大小
*/
ViewBox onTreeLayoutCallBack();
/**
* 修正位置
*
* @param treeView
* @param next
*/
void correctLayout(TreeView treeView, NodeView next);
}
實(shí)現(xiàn)
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<String> mTreeModel = treeView.getTreeModel();
if (mTreeModel != null) {
View rootView = treeView.findNodeViewFromNodeModel(mTreeModel.getRootNode());
if (rootView != null) {
rootTreeViewLayout((NodeView) rootView);
}
mTreeModel.addForTreeItem(new ForTreeItem<NodeModel<String>>() {
@Override
public void next(int msg, NodeModel<String> 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) {
//主要是糾正對于標(biāo)準(zhǔn)布局出現(xiàn)的錯(cuò)誤话告,譬如,在圖片糾正中的那種情況
//糾正需要對同層的Note進(jìn)行拉伸
}
/**
* 標(biāo)準(zhǔn)分布
*
* @param treeView
* @param rootView
*/
private void standardLayout(TreeView treeView, NodeView rootView) {
//標(biāo)準(zhǔn)分布主要是在基于root節(jié)點(diǎn)進(jìn)行排開
//對于奇數(shù)和偶數(shù)不同的情況進(jìn)行排開
//中間向外計(jì)算位置
}
/**
* 移動(dòng)
*
* @param rootView
* @param dy
*/
private void moveNodeLayout(TreeView superTreeView, NodeView rootView, int dy) {
//如果一個(gè)note節(jié)點(diǎn)進(jìn)行了移動(dòng)卵慰,那么它
//會(huì)影響到它的子節(jié)點(diǎn)的位置沙郭。
//所以要進(jìn)行重新計(jì)算,把它的所有的Note位置進(jìn)行位移
}
/**
* root節(jié)點(diǎn)的定位
*
* @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的連線
要實(shí)現(xiàn)對View和View的連線裳朋,只要在View的位置定了之后病线,就進(jìn)行畫線即可。用Sketch畫個(gè)演示如下:
其中線為一個(gè)貝塞爾曲線鲤嫡。代碼如下:
@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<String> root) {
NodeView fatherView = (NodeView) findNodeViewFromNodeModel(root);
if (fatherView != null) {
LinkedList<NodeModel<String>> childNodes = root.getChildNodes();
for (NodeModel<String> node : childNodes) {
//連線
drawLineToView(canvas, fatherView, findNodeViewFromNodeModel(node));
//遞歸
drawTreeLine(canvas, node);
}
}
}
/**
* 繪制兩個(gè)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);
}
位置的糾正流程
位置糾正的問題送挑;在對于我之前的位置的算法探索流程如下圖,關(guān)鍵是寫好已知的代碼暖眼,之后糾正惕耕。
觀察發(fā)現(xiàn),所有的移動(dòng)都是基于被操作的點(diǎn)開始的上下之分的上下移動(dòng)诫肠。
最后
記得在GitHub上給我一個(gè)星吧司澎。https://github.com/owant/ThinkMap
百度應(yīng)用市場:http://shouji.baidu.com/software/11238419.html