Android 自定义角标View

作者: SheHuan | 来源:发表于2017-06-29 13:58 被阅读628次
    0

    我们要实现的角标View的效果如上图所示。

    怎么去做呢?当然需要自定义View了,角标的背景(三角形、梯形)可以先通过Path连接每一个边,然后绘制Path到画布上,最后再把文字绘制到画布上,接下来一步步看。

    我们的角标View在画布上占据一个正方形区域(图1虚线区域),初始状态如下图:


    1

    可以看到,初始状态下画布的坐标系原点在View的左上角,但是这样不方便进行数据计算,所以我们先将坐标系原点移动到画布正中心:


    2
    对应代码如下:
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            //角标View最小边长的一半
            mHalfWidth = Math.min(w, h) / 2;
        }
    
    @Override
    protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            //将原点移动到画布中心
            canvas.translate(mHalfWidth, mHalfWidth);
        }
    

    将原点移动到画布中心后,就可以愉快的绘制角标的背景区域了,我们以角标在右上角的情况为例:


    3

    只需要连接A、B、C、D四个点就可以了,剩下的事情就是计算四个点的坐标了,还是跟简单的嘛,只需要知道AB的长度,即角标的边长sideLength,还有正方形的边长即可,这两个值可以自行配置,关键代码如下

    @Override
    protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            //将原点移动到画布中心
            canvas.translate(mHalfWidth, mHalfWidth);
            //绘制角标背景
            mPath.moveTo(-mHalfWidth, -mHalfWidth); //将Path的起点移动到A
            mPath.lineTo(sideLength - mHalfWidth, -mHalfWidth); //连接AB
            mPath.lineTo(mHalfWidth, mHalfWidth - sideLength); //连接BC
            mPath.lineTo(mHalfWidth, mHalfWidth); //连接CD
            mPath.close(); //闭合Path,即连接DA
            canvas.drawPath(mPath, mPaint);
        }
    

    这样就完成了角标背景的绘制,即图3的红线区域,其实是一个红色的实心区域,这里只是为了方便解释。

    接下来我们看文字的绘制,直接把文字绘制成倾斜的难度略大,所以我们将画布顺时针旋转45度(为什么是45度应该很明显吧),旋转后如下图所示:

    4
    这样就相当于在 x轴 水平方向防线绘制文字了。默认情况下,我们将文字绘制在角标背景(图中的梯形)的中心,如果AB的长度大于正方形View的边长,文字默认绘制在AB的长度等于正方形View的边长一半时对应的梯形中心(仅仅是为了美观默认操作,你可以更改它)。看一下文字绘制的代码:
    @Override
    protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            .......
            if (sideLength > mHalfWidth) {
                    sideLength = mHalfWidth;
                }
            //文字绘制的坐标
            int x = (int) -mTextPaint.measureText(text) / 2;
            int y = (int) (-Math.sqrt(2) / 2.0 * sideLength -(mTextPaint.ascent() + mTextPaint.descent())) / 2;
            //绘制文字
            canvas.drawText(text, x, y, mTextPaint);
        }
    

    简单的说明一下,x坐标保证文字水平居中,y标准保证文字垂直居中,其中sideLength代表AB边长,默认情况下当AB的长度大于正方形View的边长一半时,更改sideLength的值为正方形View的边长一半,但不会影响角标背景的显示,因为背景是先绘制的。到这里角标在右上角的情况就分析完了。

    如果角标在右下角显示呢?其实和角标在右上角的情况类似,还是按照上边先画质角标背景再绘制文字的流程分析。通过观察发现将图3绕坐标原点顺时针旋转90度,就可以实现角标在右下角显示的效果。如下图:


    5

    类似的,如果旋转180度就是角标在左下角的显示效果、旋转270度就是角标在左上角的显示效果,图就不贴了,大家可以结合图3体会下。我们在自定义属性中用枚举值(position)0、1、2、3代表右上、右下、左下、左上的情况,则对应的画布旋转代如下:

    @Override
    protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            //将原点移动到画布中心
            canvas.translate(mHalfWidth, mHalfWidth);
            //根据角标位置旋转画布
            canvas.rotate(position * 90);
    
            //绘制角标背景
            ......
        }
    

    这样就完全解决了角标背景绘制的问题,但是文字绘制呢?问题来了,如果角标在右下角,按照之前的方式将画布顺时针旋转45度再绘制,得到的是如下的效果:

    6
    文字反了,看起来好别扭,角标在左下角显示时也存在同样的问题。解决方案也不难,以图6为基础,忽略显示异常的文字,将画布向y轴负方向平移梯形的高度,再在x、y方向翻转画布,对应的代码如下:
    //如果角标在右下、左下则进行画布平移、翻转,解决绘制的文字显示问题
    if (position == 1 || position == 2) {
                canvas.translate(0, (float) (-Math.sqrt(2) / 2.0 * sideLength));
                canvas.scale(-1, -1);
            }
    

    然后再绘制文字,对应的图如下:


    7

    这样显示效果就正常了。

    最后贴一下onDraw()方法的代码:

    @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            //将原点移动到画布中心
            canvas.translate(mHalfWidth, mHalfWidth);
            //根据角标位置旋转画布
            canvas.rotate(position * 90);
    
            if (sideLength > mHalfWidth * 2) {
                sideLength = mHalfWidth * 2;
            }
    
            //绘制角标背景
            mPath.moveTo(-mHalfWidth, -mHalfWidth);
            mPath.lineTo(sideLength - mHalfWidth, -mHalfWidth);
            mPath.lineTo(mHalfWidth, mHalfWidth - sideLength);
            mPath.lineTo(mHalfWidth, mHalfWidth);
            mPath.close();
            canvas.drawPath(mPath, mPaint);
    
            //绘制文字前画布旋转45度
            canvas.rotate(45);
            //角标实际高度
            int h1 = (int) (Math.sqrt(2) / 2.0 * sideLength);
            int h2 = (int) -(mTextPaint.ascent() + mTextPaint.descent());
            //文字绘制坐标
            int x = (int) -mTextPaint.measureText(text) / 2;
            int y;
            if (marginLeanSide >= 0) { //使用clv:margin_lean_side属性时
                if (position == 1 || position == 2) {
                    if (h1 - (marginLeanSide - mTextPaint.ascent()) < (h1 - h2) / 2) {
                        y = -(h1 - h2) / 2;
                    } else {
                        y = (int) -(h1 - (marginLeanSide - mTextPaint.ascent()));
                    }
                } else {
                    if (marginLeanSide < mTextPaint.descent()) {
                        marginLeanSide = (int) mTextPaint.descent();
                    }
    
                    if (marginLeanSide > (h1 - h2) / 2) {
                        marginLeanSide = (h1 - h2) / 2;
                    }
                    y = -marginLeanSide;
                }
            } else { //默认情况下
                if (sideLength > mHalfWidth) {
                    sideLength = mHalfWidth;
                }
                y = (int) (-Math.sqrt(2) / 2.0 * sideLength + h2) / 2;
            }
    
            //如果角标在右下、左下则进行画布平移、翻转,已解决绘制的文字显示问题
            if (position == 1 || position == 2) {
                canvas.translate(0, (float) (-Math.sqrt(2) / 2.0 * sideLength));
                canvas.scale(-1, -1);
            }
            //绘制文字
            canvas.drawText(text, x, y, mTextPaint);
        }
    

    其中if (marginLeanSide >= 0) { }的这部分代码在这里说明一下,前边我们提到过默认情况下,我们将文字绘制在角标背景(图中的梯形)的中心,如果AB的长度大于正方形View的边长,文字默认绘制在AB的长度等于正方形View的边长一半时对应的梯形中心,如果我们设置了clv:margin_lean_side属性,则不采用默认操作,而是根据clv:margin_lean_side属性值计算文字到角标斜边(CD边)的距离,但是最大距离为文字在角标中心时到斜边的距离。

    整个实现过程还是比较简单的,关键是要正确的理解画布平移、旋转操作,记住一点,画布的平移、旋转并不影响之前的绘制效果,只对之后的操作有影响。这里我们只分析了核心的部分,其它就是自定义属性、View的测量,有兴趣的可看源码。

    源码地址:https://github.com/Othershe/CornerLabelView

    相关文章

      网友评论

        本文标题:Android 自定义角标View

        本文链接:https://www.haomeiwen.com/subject/grtpcxtx.html