zoukankan      html  css  js  c++  java
  • Android Canvas drawText实现中文垂直居中

    目标:

    把中文字符绘制到目标矩形的居中位置。

    问题:

    Android的Canvas绘图,drawText里的origin是以baseline为基准的,直接以目标矩形的bottom传进drawText,字符位置会偏下。这样写代码:

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. @Override  
    2. public void onDraw (Canvas canvas) {  
    3.     Rect targetRect = new Rect(50, 50, 1000, 200);  
    4.     Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);  
    5.     paint.setStrokeWidth(3);  
    6.     paint.setTextSize(80);  
    7.     String testString = "测试:ijkJQKA:1234";  
    8.     paint.setColor(Color.CYAN);  
    9.     canvas.drawRect(targetRect, paint);  
    10.     paint.setColor(Color.RED);  
    11.     canvas.drawText(testString, targetRect.left, targetRect.bottom, paint);  
    12. }  

    会得到难看的结果:

    找方案:

    首先自己动手做实验,自己定一个baseline,然后把文字画上去,再画上FontMetrics的几条线。FontMetrics里是字体图样的信息,有float型int型的版本,都可以从Paint中获取。它的每个成员数值都是以baseline为基准计算的,所以负值表示在baseline之上。实验代码:

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. @Override  
    2. public void onDraw (Canvas canvas) {  
    3.     Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);  
    4.     paint.setStrokeWidth(3);  
    5.     paint.setTextSize(80);  
    6.     FontMetricsInt fmi = paint.getFontMetricsInt();  
    7.     String testString = "测试:ijkJQKA:1234";  
    8.     Rect bounds1 = new Rect();  
    9.     paint.getTextBounds("测", 0, 1, bounds1);  
    10.     Rect bounds2 = new Rect();  
    11.     paint.getTextBounds("测试:ijk", 0, 6, bounds2);  
    12.     // 随意设一个位置作为baseline  
    13.     int x = 200;  
    14.     int y = 400;  
    15.     // 把testString画在baseline上  
    16.     canvas.drawText(testString, x, y, paint);  
    17.     // bounds1  
    18.     paint.setStyle(Style.STROKE);  // 画空心矩形  
    19.     canvas.save();  
    20.     canvas.translate(x, y);  // 注意这里有translate。getTextBounds得到的矩形也是以baseline为基准的  
    21.     paint.setColor(Color.GREEN);          
    22.     canvas.drawRect(bounds1, paint);  
    23.     canvas.restore();  
    24.     // bounds2  
    25.     canvas.save();  
    26.     paint.setColor(Color.MAGENTA);  
    27.     canvas.translate(x, y);  
    28.     canvas.drawRect(bounds2, paint);  
    29.     canvas.restore();  
    30.     // baseline  
    31.     paint.setColor(Color.RED);  
    32.     canvas.drawLine(x, y, 1024, y, paint);  
    33.     // ascent  
    34.     paint.setColor(Color.YELLOW);  
    35.     canvas.drawLine(x, y+fmi.ascent, 1024, y+fmi.ascent, paint);  
    36.     // descent  
    37.     paint.setColor(Color.BLUE);  
    38.     canvas.drawLine(x, y+fmi.descent, 1024, y+fmi.descent, paint);  
    39.     // top  
    40.     paint.setColor(Color.DKGRAY);  
    41.     canvas.drawLine(x, y+fmi.top, 1024, y+fmi.top, paint);  
    42.     // bottom  
    43.     paint.setColor(Color.GREEN);  
    44.     canvas.drawLine(x, y+fmi.bottom, 1024, y+fmi.bottom, paint);  
    45. }  

    获得结果:

    红线是baseline,最上面的灰线是FontMetrics.top,最下面的绿线是FontMetrics.bottom。(绿色的bottom和蓝色的descent非常接近)

    从图中可知,字符本身是在灰线和绿线之间居中的,知道这个就好办了。网上说的使用paint.getTextBounds的方法都不靠谱,可以看到对一个“测”字和6个字得到的bounds是不同的,图中的矩形能很好地表示这个函数得到的是字符的边界,而不是字体的边界。

    FontMetrics.top的数值是个负数,其绝对值就是字体绘制边界到baseline的距离。
    所以如果是把文字画在 FontMetrics高度的矩形中, drawText就应该传入 -FontMetrics.top。
    要画在targetRect的居中位置,baseline的计算公式就是:

    targetRect.top + (targetRect.bottom - targetRect.top) / 2 - (FontMetrics.bottom - FontMetrics.top) / 2 - FontMetrics.top

    解决:

    所以最开始的代码应该改成(顺便加入水平居中):

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. @Override  
    2. public void onDraw (Canvas canvas) {  
    3.     Rect targetRect = new Rect(50, 50, 1000, 200);  
    4.     Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);  
    5.     paint.setStrokeWidth(3);  
    6.     paint.setTextSize(80);  
    7.     String testString = "测试:ijkJQKA:1234";  
    8.     paint.setColor(Color.CYAN);  
    9.     canvas.drawRect(targetRect, paint);  
    10.     paint.setColor(Color.RED);  
    11.     FontMetricsInt fontMetrics = paint.getFontMetricsInt();  
    12.         // 转载请注明出处:http://blog.csdn.net/hursing  
    13.     int baseline = targetRect.top + (targetRect.bottom - targetRect.top - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top;  
    14.     // 下面这行是实现水平居中,drawText对应改为传入targetRect.centerX()  
    15.     paint.setTextAlign(Paint.Align.CENTER);  
    16.     canvas.drawText(testString, targetRect.centerX(), baseline, paint);  
    17. }  

    效果(点击查看大图):



    还可以去看看android sdk源码,

    $android4.2/frameworks/base/corej/ava/android/text/BoringLayout.java是TextView画文字的算法

    转载请注明出处:http://blog.csdn.net/hursing

  • 相关阅读:
    Cocos2d-x 3.0 事件系统【转】
    cocos2d-x中false,setSwallowTouches,stopPropagation的区别
    类成员函数指针 ->*语法剖析
    cocos2d-lua 3.5 ios搭建步骤
    cocos2d-lua 3.5 android搭建步骤
    cocos2d-lua 3.5 android搭建常见错误
    结构体
    乒乓球(0)<P2003_1>
    不高兴的津津(0)<P2004_1>
    陶陶摘苹果(0)<P2005_1>
  • 原文地址:https://www.cnblogs.com/exmyth/p/4726831.html
Copyright © 2011-2022 走看看