ข้อความของศูนย์ Android บนแคนวาส


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
Mate! โปรดระบุว่าลิงค์รูปภาพของคุณคือ NSFW! ฉันไม่ได้ตั้งใจจะหยาบคาย แต่ฉันไม่ต้องการโฆษณาที่มีผู้หญิงที่ไม่มีส่วนบนปรากฏบนหน้าจอในสำนักงาน
Michael Scheper

5
@MichaelScheper ขออภัยฉันได้อัปเดตลิงก์แล้ว!
เซบาสเตียน

23
@ เซบาสเตียนเฮ้คุณยังมีลิงค์อยู่เหรอ?
S.Lukas

@ S.Lukas hhahaha
BOTJr

คำตอบ:


378

ลองทำสิ่งต่อไปนี้:

 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 = (ความกว้าง - textPaint.TextSize * Math.Abs ​​(_text.Length / 2)) / 2; ไม่แน่ใจว่ามีวิธีที่ดีกว่านี้หรือไม่
paj7777

และอาจจะดีที่สุดในการคัดเลือก _text.Length ไปลอยเนื่องจากเห็นได้ชัดว่ามันใช้ไม่ได้กับความยาวของข้อความ
paj7777

61
paj7777 นั่นไม่จำเป็นถ้าคุณตั้งค่า textPaint.setTextAlign (Align.CENTER);
Costi Muraru

@ paj7777 นั่นจะใช้ได้กับฟอนต์ที่มีความกว้างคงที่เท่านั้น นอกจากนี้คุณไม่จำเป็นต้องโยนให้ลอย หากคุณหารด้วยทุ่นผลลัพธ์จะเป็นทุ่น เช่นfloat halfLength = text.length() / 2f;นี้เรียกว่าประเภทโปรโมชั่น
Michael Scheper

2
ฉันเปรียบเทียบวิธีนี้ (= กึ่งกลางด้วยPaint.descent()และPaint.ascent()) กับวิธีการจัดกึ่งกลางข้อความด้วยPaint.getTextBounds()ในคำตอบของฉันด้านล่าง Paint.descent()และPaint.ascent()อย่าคำนึงถึงข้อความจริง (คุณสามารถรับรู้ถึงความไม่ถูกต้องนี้ในภาพหน้าจอในโพสต์ของฉันด้านล่าง) นั่นคือเหตุผลที่ฉันจะแนะนำกับวิธีนี้ วิธีการที่Paint.getTextBounds()ดูเหมือนว่าจะทำงานแม่นยำยิ่งขึ้น
andreas1724

217

จัดกึ่งกลางด้วย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 (Rect)แทน (อย่าใช้Canvas.getClipBounds (). getHeight ()ตามที่จัดสรรหน่วยความจำสำหรับRect )

  • สำหรับเหตุผลของประสิทธิภาพการทำงานคุณควรจัดสรรวัตถุก่อนที่พวกเขาจะใช้ในOnDraw () ในฐานะที่เป็นdrawCenter ()จะถูกเรียกภายในonDraw ()วัตถุRect rถูกจัดสรรล่วงหน้าเป็นเขตข้อมูลที่นี่


ฉันพยายามใส่รหัสของคำตอบสองข้อแรกลงในรหัสของฉันเอง (สิงหาคม 2015) และสร้างภาพหน้าจอเพื่อเปรียบเทียบผลลัพธ์:

ข้อความกึ่งกลางสามเวอร์ชัน

ข้อความควรอยู่กึ่งกลางภายในสี่เหลี่ยมเติมสีแดง รหัสของฉันสร้างข้อความสีขาวส่วนอีกสองรหัสจะสร้างข้อความสีเทาทั้งหมด (จริง ๆ แล้วเป็นรหัสเดียวกันซ้อนทับกัน) ข้อความสีเทาต่ำเกินไปเล็กน้อยและอยู่ทางด้านขวาสองรายการ

นี่คือวิธีที่ฉันทำแบบทดสอบ:

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

ขอบคุณมาก .. ลอจิกของคุณช่วยฉันได้มากเช่นกัน!
LiuWenbin_NO

นี่เป็นคำตอบเดียวที่ทำให้ข้อความอยู่ตรงกลาง
Joery

64

จัดแนวในแนวตั้งเป็นเรื่องยากเนื่องจากการสืบเชื้อสายของข้อความและการขึ้นเกิดขึ้นผู้ชายหลายคนใช้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 จะมีอีกสองค่า


ตามที่เขียนไว้ var "Paint" นั้นสร้างความสับสน มันได้รับมอบหมายอย่างไร? มันรู้ได้อย่างไรว่ามีการขึ้นและลงของเนื้อหาสำหรับขอบเขต
RoundSparrow hilltx

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ถูกใช้ในการวาดข้อความ
William Reed

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.CENTERกับtextPaint.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

1

ในกรณีของฉันฉันไม่ต้องวางข้อความไว้ตรงกลางผืนผ้าใบ แต่ในวงล้อที่หมุน แม้ว่าฉันจะต้องใช้รหัสนี้เพื่อประสบความสำเร็จ:

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)
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.