画布上的Android Center文本


191

我正在尝试使用下面的代码显示文本。问题是文本没有水平居中。当我为设置坐标时drawText,它将在此位置设置文本的底部。我希望绘制文本,以便文本也水平居中。

这是进一步显示我的问题的图片:

屏幕截图

@Override
protected void onDraw(Canvas canvas) {
    // TODO Auto-generated method stub
    super.onDraw(canvas);
    //canvas.drawRGB(2, 2, 200);
    Paint textPaint = new Paint();
    textPaint.setARGB(200, 254, 0, 0);
    textPaint.setTextAlign(Align.CENTER);
    textPaint.setTypeface(font);
    textPaint.setTextSize(300);
    canvas.drawText("Hello", canvas.getWidth()/2, canvas.getHeight()/2  , textPaint);
}

20
我不会在onDraw事件中声明您的绘画对象。每次重绘时都会重新创建。考虑将其设为私有类变量。
Christopher Rathgeb

8
伴侣!请注意,您的图片链接是NSFW!我并不是故意要谨慎,但我不需要在办公室的屏幕上出现裸照女性的广告。
Michael Scheper

5
@MichaelScheper对不起,我已经更新了链接!
塞巴斯蒂安

23
@Sebastian嘿,您仍然知道该链接吗?
S.Lukas

@ S.Lukas hhahaha
BOTJr。

Answers:


375

请尝试以下操作:

 int xPos = (canvas.getWidth() / 2);
 int yPos = (int) ((canvas.getHeight() / 2) - ((textPaint.descent() + textPaint.ascent()) / 2)) ; 
 //((textPaint.descent() + textPaint.ascent()) / 2) is the distance from the baseline to the center.

 canvas.drawText("Hello", xPos, yPos, textPaint);

10
好答案。对我来说,我需要使用以下内容,因为我需要使文本完全水平居中,而不是将文本从中心位置开始:int xPos =(Width-textPaint.TextSize * Math.Abs​​(_text.Length / 2))/ 2; 不知道是否有更好的方法可以完成此操作。
paj7777

可能最好将_text.Length强制转换为浮点数,因为它显然不适用于奇数的文本长度。
paj7777

61
paj7777,如果您设置textPaint.setTextAlign(Align.CENTER);则不需要。
2013年

@ paj7777无论如何,这仅适用于固定宽度的字体。另外,您不需要强制转换为浮点数;如果用浮点数除,结果将是浮点数。例如,float halfLength = text.length() / 2f;这称为类型提升
Michael Scheper

2
我在下面的答案中将这种方法(=和Paint.descent()Paint.ascent())与文本居中的方法进行了比较Paint.getTextBounds()Paint.descent()并且Paint.ascent()不考虑实际文本。(您可以在下面的帖子中的屏幕截图中识别出这种错误。)因此,我建议您反对这种方法。的方法Paint.getTextBounds()似乎更准确。
andreas1724

214

Paint.getTextBounds()为中心:

在此处输入图片说明

private Rect r = new Rect();

private void drawCenter(Canvas canvas, Paint paint, String text) {
    canvas.getClipBounds(r);
    int cHeight = r.height();
    int cWidth = r.width();
    paint.setTextAlign(Paint.Align.LEFT);
    paint.getTextBounds(text, 0, text.length(), r);
    float x = cWidth / 2f - r.width() / 2f - r.left;
    float y = cHeight / 2f + r.height() / 2f - r.bottom;
    canvas.drawText(text, x, y, paint);
}
  • Paint.Align.CENTER并不意味着文本的参考点垂直居中。参考点始终在基线上。那么,为什么不使用Paint.Align.LEFT呢?无论如何,您都必须计算参考点。

  • Paint.descent()的缺点是它不考虑真实文本。Paint.descent()检索相同的值,而不管文本是否包含带有下降字母的字母。这就是为什么我改用r.bottom的原因。

  • 我有一些问题Canvas.getHeight()如果API <16,这就是为什么我使用Canvas.getClipBounds(矩形)来代替。(不要使用Canvas.getClipBounds()。getHeight(),因为它会为Rect分配内存。)

  • 出于性能考虑,应先分配对象,然后再在onDraw()中使用对象。由于将在onDraw()中调用drawCenter(),因此对象Rect r在此处预先分配为字段。


我尝试将两个最重要答案的代码放入自己的代码中(2015年8月),并截图了以比较结果:

文字居中的三个版本

文本应在红色填充矩形内居中。我的代码产生白色文本,其他两个代码总共产生灰色文本(它们实际上是相同的,重叠的)。灰色文字有点太低,右边两个。

这是我进行测试的方式:

import android.app.Activity;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;

class MyView extends View {

    private static String LABEL = "long";
    private static float TEXT_HEIGHT_RATIO = 0.82f;

    private FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(0, 0);
    private Rect r = new Rect();
    private Paint paint = new Paint();
    private Paint rectPaint = new Paint();

    public MyView(Context context) {
        super(context);
    }

    private void drawTextBounds(Canvas canvas, Rect rect, int x, int y) {
        rectPaint.setColor(Color.rgb(0, 0, 0));
        rectPaint.setStyle(Paint.Style.STROKE);
        rectPaint.setStrokeWidth(3f);
        rect.offset(x, y);
        canvas.drawRect(rect, rectPaint);
    }

    // andreas1724 (white color):
    private void draw1(Canvas canvas, Paint paint, String text) {
        paint.setTextAlign(Paint.Align.LEFT);
        paint.setColor(Color.rgb(255, 255, 255));
        canvas.getClipBounds(r);
        int cHeight = r.height();
        int cWidth = r.width();
        paint.getTextBounds(text, 0, text.length(), r);
        float x = cWidth / 2f - r.width() / 2f - r.left;
        float y = cHeight / 2f + r.height() / 2f - r.bottom;
        canvas.drawText(text, x, y, paint);
        drawTextBounds(canvas, r, (int) x, (int) y);
    }

    // Arun George (light green color):
    private void draw2(Canvas canvas, Paint textPaint, String text) {
        textPaint.setTextAlign(Paint.Align.CENTER);
        textPaint.setColor(Color.argb(100, 0, 255, 0));
        int xPos = (canvas.getWidth() / 2);
        int yPos = (int) ((canvas.getHeight() / 2) - ((textPaint.descent() + textPaint.ascent()) / 2));
        canvas.drawText(text, xPos, yPos, textPaint);
    }

    // VinceStyling (light blue color):
    private void draw3(Canvas yourCanvas, Paint mPaint, String pageTitle) {
        mPaint.setTextAlign(Paint.Align.LEFT);
        mPaint.setColor(Color.argb(100, 0, 0, 255));
        r = yourCanvas.getClipBounds();
        RectF bounds = new RectF(r);
        bounds.right = mPaint.measureText(pageTitle, 0, pageTitle.length());
        bounds.bottom = mPaint.descent() - mPaint.ascent();
        bounds.left += (r.width() - bounds.right) / 2.0f;
        bounds.top += (r.height() - bounds.bottom) / 2.0f;
        yourCanvas.drawText(pageTitle, bounds.left, bounds.top - mPaint.ascent(), mPaint);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        int margin = 10;
        int width = w - 2 * margin;
        int height = h - 2 * margin;
        params.width = width;
        params.height = height;
        params.leftMargin = margin;
        params.topMargin = margin;
        setLayoutParams(params);
        paint.setTextSize(height * TEXT_HEIGHT_RATIO);
        paint.setAntiAlias(true);
        paint.setTypeface(Typeface.create(Typeface.SERIF, Typeface.BOLD_ITALIC));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.rgb(255, 0, 0));
        draw1(canvas, paint, LABEL);
        draw2(canvas, paint, LABEL);
        draw3(canvas, paint, LABEL);
    }
}

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRequestedOrientation (ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        FrameLayout container = new FrameLayout(this);
        container.setLayoutParams(new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT));
        container.addView(new MyView(this));
        setContentView(container);
    }
}

1
非常感谢您。.您的逻辑确实对我有很大帮助。
Sujay UN

非常感谢您。.您的逻辑对我也有很大帮助!
刘文斌

63

垂直对齐很困难,因为发生了文本下降和上升,很多人都使用Paint.getTextBounds()来检索TextWidth和TextHeight,但这并不能使文本居中。在这里,我们可以使用Paint.measureText()来计算TextWidth,我们只需使用下降和上升来减去TextHeight即可,然后我们得到了最多的TextSize方法,彼此之间的以下工作相当容易。

// the Paint instance(should be assign as a field of class).
Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setTextSize(getResources().getDimension(R.dimen.btn_textsize));

// the display area.
Rect areaRect = new Rect(0, 0, 240, 60);

// draw the background style (pure color or image)
mPaint.setColor(Color.BLACK);
yourCanvas.drawRect(areaRect, mPaint);

String pageTitle = "文字小说";

RectF bounds = new RectF(areaRect);
// measure text width
bounds.right = mPaint.measureText(pageTitle, 0, pageTitle.length());
// measure text height
bounds.bottom = mPaint.descent() - mPaint.ascent();

bounds.left += (areaRect.width() - bounds.right) / 2.0f;
bounds.top += (areaRect.height() - bounds.bottom) / 2.0f;

mPaint.setColor(Color.WHITE);
yourCanvas.drawText(pageTitle, bounds.left, bounds.top - mPaint.ascent(), mPaint);

代码截屏

顺便说一句,我们强烈建议您使用RectF而不是Rect,因为这些位置需要更准确的值,以我的经验,RectF在xhdpi设备上仅上下偏移一个像素,而Rect还要再偏移两个像素。


如所写,变量“ Paint”令人困惑。如何分配?它如何知道bounds.bottom文本的上升和下降?
RoundSparrow hilltx 2015年

1
是的,我为此优化了答案,请看看。
VinceStyling

感谢您节省我宝贵的时间))
Andrey

16

您的代码正在视图的中心绘制文本基线的中心。为了使文本在x,y的某个点居中,您需要计算文本的中心并将放在该点上。

此方法将绘制以点x,y为中心的文本。如果将其传递到视图的中心,它将以居中位置绘制文本。

private void drawTextCentered(String text, int x, int y, Paint paint, Canvas canvas) {
    int xPos = x - (int)(paint.measureText(text)/2);
    int yPos = (int) (y - ((textPaint.descent() + textPaint.ascent()) / 2)) ;

    canvas.drawText(text, xPos, yPos, textPaint);
}

@MarcioGranzotto它android.graphics.Paint被用来绘制文本
威廉·里德

4

我发现使文本居中的最佳解决方案如下:

textPaint.setTextAlign(Paint.Align.CENTER);
//textPaint is the Paint object being used to draw the text (it must be initialized beforehand)
float textY=center.y;
float textX=center.x; 
// in this case, center.x and center.y represent the coordinates of the center of the rectangle in which the text is being placed
canvas.drawText(text,textX,textY,textPaint);    `

这看起来完全像发问者试图将文本居中的方式。文本不会垂直居中,因为Paint.Align.Center并不意味着文本的参考点垂直居中。
andreas1724

3

适合我使用: textPaint.textAlign = Paint.Align.CENTERtextPaint.getTextBounds

private fun drawNumber(i: Int, canvas: Canvas, translate: Float) {
            val text = "$i"
            textPaint.textAlign = Paint.Align.CENTER
            textPaint.getTextBounds(text, 0, text.length, textBound)

            canvas.drawText(
                    "$i",
                    translate + circleRadius,
                    (height / 2 + textBound.height() / 2).toFloat(),
                    textPaint
            )
        }

结果是:

在此处输入图片说明


1

我创建了一种方法来简化此操作:

    public static void drawCenterText(String text, RectF rectF, Canvas canvas, Paint paint) {
    Paint.Align align = paint.getTextAlign();
    float x;
    float y;
    //x
    if (align == Paint.Align.LEFT) {
        x = rectF.centerX() - paint.measureText(text) / 2;
    } else if (align == Paint.Align.CENTER) {
        x = rectF.centerX();
    } else {
        x = rectF.centerX() + paint.measureText(text) / 2;
    }
    //y
    metrics = paint.getFontMetrics();
    float acent = Math.abs(metrics.ascent);
    float descent = Math.abs(metrics.descent);
    y = rectF.centerY() + (acent - descent) / 2f;
    canvas.drawText(text, x, y, paint);

    Log.e("ghui", "top:" + metrics.top + ",ascent:" + metrics.ascent
            + ",dscent:" + metrics.descent + ",leading:" + metrics.leading + ",bottom" + metrics.bottom);
}

rectF是您要绘制文本的区域,仅此而已。 细节


1

如果我们使用静态布局

mStaticLayout = new StaticLayout(mText, mTextPaint, mTextWidth,
                Layout.Alignment.ALIGN_CENTER, 1.0f, 0, true);

Layout.Alignment.ALIGN_CENTER可以解决问题。静态布局还具有许多其他优点。

参考:Android文档


1

这为我工作:

 paint.setTextAlign(Paint.Align.CENTER);
        int xPos = (newWidth / 2);
        int yPos = (newHeight / 2);
        canvas.drawText("Hello", xPos, yPos, paint);

如果有人发现任何问题,请告诉我


它将开始从中心点开始写入文本,但整个文本将不在中心位置
M Shaban Ali

0

就我而言,我不必将文本放在画布的中间,而是放在旋转的轮子上。尽管我必须使用以下代码才能成功:

fun getTextRect(textSize: Float, textPaint: TextPaint, string: String) : PointF {
    val rect = RectF(left, top, right, bottom)
    val rectHeight = Rect()
    val cx = rect.centerX()
    val cy = rect.centerY()

    textPaint.getTextBounds(string, 0, string.length, rectHeight)
    val y = cy + rectHeight.height()/2
    val x = cx - textPaint.measureText(string)/2

    return PointF(x, y)
}

然后,我从View类中调用此方法:

private fun drawText(canvas: Canvas, paint: TextPaint, text: String, string: String) {
    val pointF = getTextRect(paint.textSize, textPaint, string)
    canvas.drawText(text, pointF!!.x, pointF.y, paint)
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.