1. 程式人生 > >樹形控制元件之思維導圖 Android

樹形控制元件之思維導圖 Android

之前寫的不好,從新寫了一下。

最近,筆者在實現一個思維導圖的開源控制元件。下面我簡單介紹一下如下打造一個類似思維導圖軟體的ViewGroup。

基本結構.png

建立模型

主要模型結構相對簡單: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畫個演示如下:
Paste_Image.png
其中線為一個貝塞爾曲線。程式碼如下:

    @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);
    }

位置的糾正流程

位置糾正的問題;在對於我之前的位置的演算法探索流程如下圖,關鍵是寫好已知的程式碼,之後糾正

糾正.png

標準位置錯誤.png

標準位置錯誤的處理.png

最後