Android类似公交站牌的文字竖排效果



公司做一个类似公交站牌的文字竖排效果,自己捉摸了一下,想想估计能实现,就算不能实现,权当学习了。

先看看做出来的效果图:



先说说思路:

整体为绘制圆,绘制间隔线,然后是绘制数字与文字

1.绘制圆和间隔线:

绘制这两个使用了Canvas.drawPath(Path path,Paint paint),因为这样子便于计算,只需要计算Path的路径,其它的就交给canvas可以了,path支持绘制circle(path.addCircle())与绘制line(path.lineto),本来想文本也用path绘制的,不过最后发现绘制文本只有canvas支持,path不支持。剩下的就是计算绘制起始点了

  1. /** 
  2.  * 计算点圆与线轨迹 
  3.  */  
  4. private void computeCircleAndLinePath() {  
  5.     if (data != null || data.size() != 0) {  
  6.   
  7.         circlePoints = new Point[data.size()];  
  8.         circlePaths = new Path[data.size()];  
  9.         linePaths = new Path[data.size() - 1];  
  10.   
  11.         //处理文字显示与圆点显示  
  12.         //绘制起始偏移量,如果文本高度一半大于半径,则取差值,否则取0  
  13.         int offsetX = textHeight / 2 > (circleRadius + circleStokeWidth / 2) ? textHeight / 2 - (circleRadius + circleStokeWidth / 2) : 0;  
  14.         //处理paddingLeft  
  15.         offsetX += getPaddingLeft();  
  16.         for (int i = 0; i < data.size(); i++) {  
  17.             Path circlePath = new Path();  
  18.             //计算每个圆点的中心点  
  19.             int mCircleCenterX = i * lineLength + circleRadius + circleStokeWidth / 2 + offsetX;  
  20.             int mCircleCenterY = circleMarginTop + circleRadius;  
  21.             circlePoints[i] = new Point(mCircleCenterX, mCircleCenterY);  
  22.   
  23.             //计算圆点路径  
  24.             circlePath.addCircle(mCircleCenterX, mCircleCenterY, circleRadius, Path.Direction.CCW);  
  25.             circlePaths[i] = circlePath;  
  26.             if (i == data.size() - 1) {  
  27.                 continue;  
  28.             }  
  29.             //计算线段路径  
  30.             Path linePath = new Path();  
  31.             //计算线段起始点  
  32.             int mlineStartX = mCircleCenterX + circleRadius + circleStokeWidth / 2;  
  33.             int mlineEndX = (i + 1) * lineLength + offsetX;  
  34.             linePath.moveTo(mlineStartX, mCircleCenterY);  
  35.             linePath.lineTo(mlineEndX, mCircleCenterY);  
  36.             linePaths[i] = linePath;  
  37.         }  
  38.     }  
  39. }  
    /**
     * 计算点圆与线轨迹
     */
    private void computeCircleAndLinePath() {
        if (data != null || data.size() != 0) {

            circlePoints = new Point[data.size()];
            circlePaths = new Path[data.size()];
            linePaths = new Path[data.size() - 1];

            //处理文字显示与圆点显示
            //绘制起始偏移量,如果文本高度一半大于半径,则取差值,否则取0
            int offsetX = textHeight / 2 > (circleRadius + circleStokeWidth / 2) ? textHeight / 2 - (circleRadius + circleStokeWidth / 2) : 0;
            //处理paddingLeft
            offsetX += getPaddingLeft();
            for (int i = 0; i < data.size(); i++) {
                Path circlePath = new Path();
                //计算每个圆点的中心点
                int mCircleCenterX = i * lineLength + circleRadius + circleStokeWidth / 2 + offsetX;
                int mCircleCenterY = circleMarginTop + circleRadius;
                circlePoints[i] = new Point(mCircleCenterX, mCircleCenterY);

                //计算圆点路径
                circlePath.addCircle(mCircleCenterX, mCircleCenterY, circleRadius, Path.Direction.CCW);
                circlePaths[i] = circlePath;
                if (i == data.size() - 1) {
                    continue;
                }
                //计算线段路径
                Path linePath = new Path();
                //计算线段起始点
                int mlineStartX = mCircleCenterX + circleRadius + circleStokeWidth / 2;
                int mlineEndX = (i + 1) * lineLength + offsetX;
                linePath.moveTo(mlineStartX, mCircleCenterY);
                linePath.lineTo(mlineEndX, mCircleCenterY);
                linePaths[i] = linePath;
            }
        }
    }
上述代码整体为计算这两个circle与line的起始点,圆的大小,线段的长度都由样式定制来决定,同时记录了每个圆的中心点,每个圆的路径,每个线的路径。

还有一个offsetX,至于这个变量是考虑了因为要定制样式,有字体大于圆的情况,这样的话就会存在文字大小与圆半径之间存在偏移,如果不考虑他,有可能我们的效果会出现第一个条目的文字只显示一半,所以我记录了这个偏移,把他加入了起始点的计算


这里需要注意,android绘制circle时结合了Paint画笔的笔触宽度strokeWidth,为圆半径+strokeWidth/2,所以我们在

Path.addCircle(float x,float y,float radius,Direction dir)时需要考虑传入的半径为设定的圆半径+strokeWidth/2


最后使用canvas绘制即可:

  1. /** 
  2.  * 绘制圆和线段 
  3.  */  
  4. private void drawCircleAndLine(Canvas canvas) {  
  5.     for (int i = 0; i < circlePaths.length; i++) {  
  6.         //改变颜色与风格  
  7.         if (i == currSelectedItem) {  
  8.             circlePaint.setColor(circleSelectColor);  
  9.             circlePaint.setStyle(Paint.Style.FILL_AND_STROKE);  
  10.         } else {  
  11.             circlePaint.setStyle(Paint.Style.STROKE);  
  12.             circlePaint.setColor(circleColor);  
  13.         }  
  14.         canvas.drawPath(circlePaths[i], circlePaint);  
  15.     }  
  16.     for (int i = 0; i < linePaths.length; i++) {  
  17.         canvas.drawPath(linePaths[i], linePaint);  
  18.     }  
  19. }  
    /**
     * 绘制圆和线段
     */
    private void drawCircleAndLine(Canvas canvas) {
        for (int i = 0; i < circlePaths.length; i++) {
            //改变颜色与风格
            if (i == currSelectedItem) {
                circlePaint.setColor(circleSelectColor);
                circlePaint.setStyle(Paint.Style.FILL_AND_STROKE);
            } else {
                circlePaint.setStyle(Paint.Style.STROKE);
                circlePaint.setColor(circleColor);
            }
            canvas.drawPath(circlePaths[i], circlePaint);
        }
        for (int i = 0; i < linePaths.length; i++) {
            canvas.drawPath(linePaths[i], linePaint);
        }
    }

2.绘制数字和文本:

  因为Path不能绘制文本,只能用canvas来绘制,canvas提供了大概三种类型的方法:

 canvas.drawText();

 canvas.drawPostText();(已废弃)

 canvas.drawTextonPath() 该方法不好控制文本的方向以及文本的样式

 所以采用drawText()方法

  1. /** 
  2.  * 绘制数字和文本 
  3.  */  
  4. private void drawNumberAndItemText(Canvas canvas) {  
  5.     for (int i = 0; i < data.size(); i++) {  
  6.         //数字绘制起始点  
  7.         int mNumberStartX = circlePoints[i].x;  
  8.         int mNumberStartY = circlePoints[i].y + circleRadius + numberMarginTop;  
  9.         //测量数字宽度  
  10.         float textWidth = numberPaint.measureText(String.valueOf(i + 1), 0, String.valueOf(i + 1).length());  
  11.         //改变颜色  
  12.         if (i == currSelectedItem) {  
  13.             numberPaint.setColor(numberSelectColor);  
  14.         } else {  
  15.             numberPaint.setColor(numberColor);  
  16.         }  
  17.         canvas.drawText(String.valueOf(i + 1), mNumberStartX - textWidth / 2, mNumberStartY, numberPaint);  
  18.         drawItemText(canvas);  
  19.     }  
  20. }  
    /**
     * 绘制数字和文本
     */
    private void drawNumberAndItemText(Canvas canvas) {
        for (int i = 0; i < data.size(); i++) {
            //数字绘制起始点
            int mNumberStartX = circlePoints[i].x;
            int mNumberStartY = circlePoints[i].y + circleRadius + numberMarginTop;
            //测量数字宽度
            float textWidth = numberPaint.measureText(String.valueOf(i + 1), 0, String.valueOf(i + 1).length());
            //改变颜色
            if (i == currSelectedItem) {
                numberPaint.setColor(numberSelectColor);
            } else {
                numberPaint.setColor(numberColor);
            }
            canvas.drawText(String.valueOf(i + 1), mNumberStartX - textWidth / 2, mNumberStartY, numberPaint);
            drawItemText(canvas);
        }
    }
因为为竖直排列,所以要判断每个字的宽度,高度,同时还的考虑每个字之间的间隔,所以确定每个字的范围很有必要

  1. /** 
  2.  * 绘制文本 
  3.  */  
  4. private void drawItemText(Canvas canvas) {  
  5.     //记录每个条目区域  
  6.     textRegions = new Region[data.size()];  
  7.     for (int i = 0; i < data.size(); i++) {  
  8.         Region region = new Region();  
  9.         int mItemStartX = circlePoints[i].x;  
  10.         int mItemStartY = circlePoints[i].y + circleRadius + numberMarginTop + textMarginTop;  
  11.         String text = data.get(i);  
  12.         char[] chars = text.toCharArray();  
  13.         //每个条目区域的计算  
  14.         Rect textArea = new Rect();  
  15.         textArea.left = mItemStartX - textHeight / 2;  
  16.         textArea.right = textArea.left + textHeight;  
  17.         //减去一个文字高度,因为绘制文字是在baseline上方绘制,基线位置为drawText(text,x,y,paint)的y位置  
  18.         textArea.top = mItemStartY - textHeight;  
  19.         int lastTextHeight = 0;  
  20.         //改变颜色  
  21.         if (i == currSelectedItem) {  
  22.             textPaint.setColor(textSelectColor);  
  23.         } else {  
  24.             textPaint.setColor(textColor);  
  25.         }  
  26.         for (int j = 0; j < chars.length; j++) {  
  27.             canvas.drawText(String.valueOf(text.charAt(j)), mItemStartX - textHeight / 2, mItemStartY + lastTextHeight, textPaint);  
  28.             lastTextHeight += textHeight + textSpace;  
  29.         }  
  30.         //此处减去最后一个文字间隔  
  31.         textArea.bottom = textArea.top + lastTextHeight - textSpace;  
  32.         region.set(textArea);  
  33.         textRegions[i] = region;  
  34.   
  35.     }  
  36. }  
    /**
     * 绘制文本
     */
    private void drawItemText(Canvas canvas) {
        //记录每个条目区域
        textRegions = new Region[data.size()];
        for (int i = 0; i < data.size(); i++) {
            Region region = new Region();
            int mItemStartX = circlePoints[i].x;
            int mItemStartY = circlePoints[i].y + circleRadius + numberMarginTop + textMarginTop;
            String text = data.get(i);
            char[] chars = text.toCharArray();
            //每个条目区域的计算
            Rect textArea = new Rect();
            textArea.left = mItemStartX - textHeight / 2;
            textArea.right = textArea.left + textHeight;
            //减去一个文字高度,因为绘制文字是在baseline上方绘制,基线位置为drawText(text,x,y,paint)的y位置
            textArea.top = mItemStartY - textHeight;
            int lastTextHeight = 0;
            //改变颜色
            if (i == currSelectedItem) {
                textPaint.setColor(textSelectColor);
            } else {
                textPaint.setColor(textColor);
            }
            for (int j = 0; j < chars.length; j++) {
                canvas.drawText(String.valueOf(text.charAt(j)), mItemStartX - textHeight / 2, mItemStartY + lastTextHeight, textPaint);
                lastTextHeight += textHeight + textSpace;
            }
            //此处减去最后一个文字间隔
            textArea.bottom = textArea.top + lastTextHeight - textSpace;
            region.set(textArea);
            textRegions[i] = region;

        }
    }

文本的宽度,高度可以采用如下方法获得:

  1. /** 
  2.  * 计算文本信息,包含每个条目的宽度,高度 
  3.  */  
  4. private Rect getTextInfo(String text, Paint paint) {  
  5.     Rect mrect = new Rect();  
  6.     //获得文本的最小矩形大小,也是测量文本高度,宽度的一种方法  
  7.     paint.getTextBounds(text, 0, text.length(), mrect);  
  8.     return mrect;  
  9. }  
    /**
     * 计算文本信息,包含每个条目的宽度,高度
     */
    private Rect getTextInfo(String text, Paint paint) {
        Rect mrect = new Rect();
        //获得文本的最小矩形大小,也是测量文本高度,宽度的一种方法
        paint.getTextBounds(text, 0, text.length(), mrect);
        return mrect;
    }

文本宽度也可以使用Paint.measureText(String text,int start,int end)获得

这里需要注意:

 android绘制文字的时候,使用drawText(String text,float x,float y,Paint paint)时,大家注意y坐标的位置实际上为绘制文字的baseline位置,在基线位置之上开始绘制



最后因为在布局文件使用了scrollView,torizontalScrollView,所以在View.onMeasure()计算了整个view占据的最小空间大小,如果不这样计算,在绘制view的时候会无法显示,因为scrollview传递给子布局的大小为0

  1. @Override  
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  3.     //此处为与HorizontalScrollView搭配使用,达到滑动目的,别的滑动方式请修改此处代码或者删除  
  4.     int mViewWidth = getPaddingLeft() + getPaddingRight() + computeMinViewWidth();  
  5.     int mViewHeight = getPaddingBottom() + getPaddingTop() + computeMinViewHeight();  
  6.     setMeasuredDimension(mViewWidth, mViewHeight);  
  7. }  
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //此处为与HorizontalScrollView搭配使用,达到滑动目的,别的滑动方式请修改此处代码或者删除
        int mViewWidth = getPaddingLeft() + getPaddingRight() + computeMinViewWidth();
        int mViewHeight = getPaddingBottom() + getPaddingTop() + computeMinViewHeight();
        setMeasuredDimension(mViewWidth, mViewHeight);
    }
  1. /** 
  2.  * 计算View最小宽度 
  3.  */  
  4. private int computeMinViewWidth() {  
  5.     int viewWidth = 0;  
  6.     if (data != null || data.size() != 0) {  
  7.         //计算文本高度,起始不管第一条是否为空,高度都会与其它文本保持一致  
  8.         Rect textRect = getTextInfo(data.get(0), textPaint);  
  9.         textHeight = textRect.height();  
  10.         viewWidth = (data.size() - 1) * lineLength + 2 * circleRadius + circleStokeWidth;  
  11.         if (textHeight > (2 * circleRadius + circleStokeWidth)) {  
  12.             int offsetX = textHeight / 2 - (circleRadius + circleStokeWidth / 2);  
  13.             viewWidth = viewWidth + 2 * offsetX;  
  14.         }  
  15.     }  
  16.     return viewWidth;  
  17. }  
  18.   
  19. /** 
  20.  * 计算View最小高度 
  21.  */  
  22. private int computeMinViewHeight() {  
  23.     int viewHeight = 0;  
  24.     if (data != null || data.size() != 0) {  
  25.         String maxText = "";  
  26.         for (int i = 0; i < data.size(); i++) {  
  27.             //计算文本宽度  
  28.             maxText = data.get(i).length() > maxText.length() ? data.get(i) : maxText;  
  29.         }  
  30.         //获得总文本间距  
  31.         int textSpaceWidth = textSpace * (maxText.length() - 1);  
  32.         Rect textRect = getTextInfo(maxText, textPaint);  
  33.         //最大文本高度  
  34.         int textMaxHeight = textRect.width() + textSpaceWidth;  
  35.         //计算ViewHeight  
  36.         viewHeight = textMaxHeight + textMarginTop + numberMarginTop + (2 * circleRadius + circleStokeWidth) + circleMarginTop;  
  37.     }  
  38.     return viewHeight;  
  39. }  
    /**
     * 计算View最小宽度
     */
    private int computeMinViewWidth() {
        int viewWidth = 0;
        if (data != null || data.size() != 0) {
            //计算文本高度,起始不管第一条是否为空,高度都会与其它文本保持一致
            Rect textRect = getTextInfo(data.get(0), textPaint);
            textHeight = textRect.height();
            viewWidth = (data.size() - 1) * lineLength + 2 * circleRadius + circleStokeWidth;
            if (textHeight > (2 * circleRadius + circleStokeWidth)) {
                int offsetX = textHeight / 2 - (circleRadius + circleStokeWidth / 2);
                viewWidth = viewWidth + 2 * offsetX;
            }
        }
        return viewWidth;
    }

    /**
     * 计算View最小高度
     */
    private int computeMinViewHeight() {
        int viewHeight = 0;
        if (data != null || data.size() != 0) {
            String maxText = "";
            for (int i = 0; i < data.size(); i++) {
                //计算文本宽度
                maxText = data.get(i).length() > maxText.length() ? data.get(i) : maxText;
            }
            //获得总文本间距
            int textSpaceWidth = textSpace * (maxText.length() - 1);
            Rect textRect = getTextInfo(maxText, textPaint);
            //最大文本高度
            int textMaxHeight = textRect.width() + textSpaceWidth;
            //计算ViewHeight
            viewHeight = textMaxHeight + textMarginTop + numberMarginTop + (2 * circleRadius + circleStokeWidth) + circleMarginTop;
        }
        return viewHeight;
    }

大概就是这样子,基本上都是一点点绘制的,暂时没有想到别的办法,如有好的思路,请提供

下面附上源码下载地址:


http://download.csdn.net/download/kongzuoding/9403338


大家可以下载看看效果,如有错误,或者更好的建议,请指出

猜你喜欢

转载自blog.csdn.net/qq_29586601/article/details/79599614