自定义布局之树形布局(五):绘制结点连接线

上一篇以从左到右方向为例讲解了树形布局中子控件的摆放,本篇讲解结点间连接线的绘制。

前期准备

一般情况下布局类控件其实更关注于子控件的测量和摆放(例如LinearLayout和RelativeLayout),而在绘制上做相对较少的事情,因为它们本身其实没有太多需要花的东西,这里不妨深入ViewGroup的draw方法看看它到底绘制了什么。

其实ViewGroup并没有重写draw方法,而是直接沿用View的draw方法。

public void draw(Canvas canvas) {
        ...
        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */

        // Step 1, draw the background, if needed
        ...
        drawBackground(canvas);
		...
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);
            
            ...

            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);

            ...
            // we're done...
            return;
        }

其实注释里已经写得很清楚了,这里总结一下:
drawBackground方法绘制背景 —> 布局的onDraw方法 —> dispatchDraw方法调用子控件draw方法 —> onDrawForeground绘制前景。

Canvas绘制图像有这样的特点,后面绘制的图像会遮住前面绘制的图像。这也就是为什么背景图要最先绘制了。布局的onDraw方法调用比子控件的绘制要早,所以绘制结点连接线不应该在onDraw上进行。如下图,黄色部分的连接线会被遮盖住,非常不美观。
在这里插入图片描述
为了在绘制子控件后才绘制连接线,我们可以重写dispatchDraw方法,在完成View的全部绘制工作后才开始绘制连接线,这样就能避免连接线被遮住了。

重写dispatchDraw方法

代码如下,super.dispatchDraw方法完成了子控件的绘制,接着再绘制连接线就行了。

	@Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        onDrawConnectLine(canvas);
    }

具体实现如下。

	private LineDrawer mLineDrawer;
    private Rect mStartRect;
    private Rect mEndRect;

	/**
     * 绘制结点的连接线
     * @param canvas 绘制的画布
     */
    protected void onDrawConnectLine(Canvas canvas){
        View root = getChildAt(0);
        mStartRect.left = root.getLeft();
        mStartRect.right = root.getRight();
        mStartRect.top = root.getTop();
        mStartRect.bottom = root.getBottom();

        for(int i = 1;i < getChildCount();i++){
            View child = getChildAt(i);
            if(child.getVisibility() == View.GONE){
                continue;
            }
            mEndRect.left = child.getLeft();
            mEndRect.right = child.getRight();
            mEndRect.top = child.getTop();
            mEndRect.bottom = child.getBottom();            
           //1
           mLineDrawer.onDrawLine(canvas,mPaint,mStartRect,mEndRect,mTreeDirection);
        }
    }

mStartRect是指连接线起点所对应的控件,mEndRect是指连接线终点所对应的控件,复用Rect可以减少新对象的产生。

mLineDrawer是连接线绘制器,为了能绘制不同风格的连接线,TreeLayout提供了连接线绘制器的抽象类。

public static abstract class LineDrawer {
        /**
         * 连接线绘制器抽象类
         * @param canvas 绘制连接线的画布
         * @param paint 绘制连接线的画笔
         * @param start 连接线的起点控件的区域,即父结点控件所在区域
         * @param end 连接线的终点控件的区域,即子结点控件所在区域
         * @param direction 树的方向
         *                  参考{@link #DIRECTION_LEFT_TO_RIGHT,
         *                      @link #DIRECTION_RIGHT_TO_LEFT,
         *                      @link #DIRECTION_UP_TO_DOWN,
         *                      @link #DIRECTION_DOWN_TO_UP}
         */
        protected abstract void onDrawLine(Canvas canvas, Paint paint, Rect start, Rect end, int direction);
    }

版本问题

为什么不重写onDrawFroground方法,在这上面实现连接线绘制呢?其实上文提供的draw方法代码是Android sdk23的。

public void draw(Canvas canvas) {
        ...
        if (!verticalEdges && !horizontalEdges) {
           
            onDraw(canvas);

            dispatchDraw(canvas);
            ...
            //1
            onDrawForeground(canvas);
            ...
            return;
        }

再来看看Android sdk22的draw方法。

public void draw(Canvas canvas) {
        ...
        if (!dirtyOpaque) {
            drawBackground(canvas);
        }
		...
        if (!verticalEdges && !horizontalEdges) {
            if (!dirtyOpaque) onDraw(canvas);

            dispatchDraw(canvas);
			
			//2
            onDrawScrollBars(canvas);
            ...
            return;
        }

可以看到23版的调用了onDrawForeground方法,而22版并没有定义onDrawForeground这个方法。

代码2的onDrawScrollBars方法,在23版里换成了onDrawForeground方法,里面调用了onDrawScrollBars方法。

既然onDrawScrollBars在22和23版本都有调用,重写它行不行呢?很遗憾,不行:(

因为它被final修饰了,是不能被重写的。

protected final void onDrawScrollBars(Canvas canvas) {
	...
}

最后

上面说到LineDrawer 连接线绘制器是一个抽象类,它有两个实现类分别叫 DirectLineDrawer 和 DocumentLineDrawer,效果如下:
在这里插入图片描述
感兴趣的朋友可以到Github项目看看代码实现。

下一篇讲解事件拦截与拖拽效果的实现。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值