樹形控制元件之思維導圖 Android
阿新 • • 發佈:2019-02-02
之前寫的不好,從新寫了一下。
最近,筆者在實現一個思維導圖的開源控制元件。下面我簡單介紹一下如下打造一個類似思維導圖軟體的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<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) {
//主要是糾正對於標準佈局出現的錯誤,譬如,在圖片糾正中的那種情況
//糾正需要對同層的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<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);
}
}
}
/**
* 繪製兩個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);
}
位置的糾正流程
位置糾正的問題;在對於我之前的位置的演算法探索流程如下圖,關鍵是寫好已知的程式碼,之後糾正。