Tekst Centrum Android na płótnie

191

Próbuję wyświetlić tekst przy użyciu poniższego kodu. Problem polega na tym, że tekst nie jest wyśrodkowany poziomo. Kiedy ustawiam współrzędne drawText, ustawia dolną część tekstu w tej pozycji. Chciałbym, aby tekst został narysowany tak, aby był wyśrodkowany również poziomo.

To zdjęcie pokazuje mój problem dalej:

Zrzut ekranu

@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);
}
Sebastian
źródło
20
Nie zadeklarowałbym twojego obiektu do malowania wewnątrz zdarzenia onDraw. Jest odtwarzany za każdym razem, gdy jest przerysowany. Rozważ uczynienie go zmienną klasy prywatnej.
Christopher Rathgeb,
8
Kumpel! Proszę wspomnieć, że twój link do obrazu to NSFW! Nie chcę być roztropny, ale nie potrzebuję reklam z kobietami topless pojawiającymi się na ekranie w biurze.
Michael Scheper
5
@MichaelScheper Przepraszamy, zaktualizowałem link!
Sebastian
23
@Sebastian Hej, nadal masz ten link?
S.Lukas
@ S.Lukas hhahaha
BOTJr.

Odpowiedzi:

376

Spróbuj wykonać następujące czynności:

 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);
Arun George
źródło
10
Świetna odpowiedź. Dla mnie użyłem następującego, ponieważ musiałem całkowicie wyśrodkować tekst poziomo, a nie tekst, aby rozpocząć od pozycji środkowej: int xPos = (Szerokość - textPaint.TextSize * Math.Abs ​​(_text.Length / 2)) / 2; Nie jestem pewien, czy jest lepszy sposób na osiągnięcie tego.
paj7777
I prawdopodobnie najlepiej rzutować _text.Length na liczbę zmiennoprzecinkową, ponieważ oczywiście nie będzie działać dla nieparzystych długości tekstu.
paj7777
61
paj7777, nie jest to konieczne, jeśli ustawisz textPaint.setTextAlign (Align.CENTER);
Costi Muraru
@ paj7777 To i tak działałoby tylko dla czcionek o stałej szerokości. Ponadto nie musisz rzucać na pływaka; jeśli podzielisz przez liczbę zmiennoprzecinkową, wynikiem będzie liczba zmiennoprzecinkowa. np. float halfLength = text.length() / 2f;Nazywa się to promocją typu .
Michael Scheper
2
Porównałem to podejście (= wyśrodkuj z Paint.descent()i Paint.ascent()) z podejściem do wyśrodkowania tekstu Paint.getTextBounds()w mojej odpowiedzi poniżej. Paint.descent()i Paint.ascent()nie uwzględniają faktycznego tekstu. (Możesz rozpoznać tę niedokładność na zrzucie ekranu w moim poście poniżej.) Dlatego odradzam takie podejście. Podejście z Paint.getTextBounds()wydaje się działać dokładniej.
andreas1724
216

Wyśrodkuj za pomocą Paint.getTextBounds () :

wprowadź opis zdjęcia tutaj

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 nie oznacza, że ​​punkt odniesienia tekstu jest wyśrodkowany pionowo. Punkt odniesienia zawsze znajduje się na linii podstawowej. Dlaczego więc nie użyć Paint.Align.LEFT ? W każdym razie musisz obliczyć punkt odniesienia.

  • Paint.descent () ma tę wadę, że nie uwzględnia prawdziwego tekstu. Paint.descent () pobiera tę samą wartość, niezależnie od tego, czy tekst zawiera litery ze zjazdami, czy nie. Dlatego zamiast tego używam r.bottom .

  • Miałem pewne problemy z Canvas.getHeight (), jeśli API <16. Dlatego zamiast tego używam Canvas.getClipBounds (Rect) . (Nie używaj Canvas.getClipBounds (). GetHeight (), ponieważ alokuje pamięć dla Rect .)

  • Ze względu na wydajność powinieneś przydzielić obiekty, zanim zostaną one użyte w onDraw () . Ponieważ drawCenter () będzie wywoływany w ramach funkcji onDraw (), obiekt Rect r jest tutaj wstępnie przydzielany jako pole.


Próbowałem umieścić kod dwóch najlepszych odpowiedzi we własnym kodzie (sierpień 2015 r.) I zrobiłem zrzut ekranu, aby porównać wyniki:

tekst wyśrodkowany trzy wersje

Tekst powinien być wyśrodkowany w czerwonym wypełnionym prostokącie. Mój kod tworzy biały tekst, a pozostałe dwa kody tworzą łącznie szary tekst (w rzeczywistości są takie same, nakładają się). Szary tekst jest trochę za niski, a dwa po prawej stronie.

Oto jak wykonałem test:

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);
    }
}
andreas1724
źródło
1
Dziękuję bardzo .. Twoja logika bardzo mi pomogła.
Sujay ONZ
Dziękuję bardzo .. Twoja logika też bardzo mi pomogła!
LiuWenbin_NO.
To jedyna odpowiedź, która naprawdę centruje tekst.
Joery
63

Wyrównanie w pionie jest trudne, ponieważ zdarzyło się zejście i wznoszenie tekstu, wielu facetów używało Paint.getTextBounds () do pobierania TextWidth i TextHeight, ale nie powoduje to, że tekst jest wyśrodkowany. Tutaj możemy użyć Paint.measureText () do obliczenia TextWidth, TextHeight po prostu wykonujemy odejmowanie wraz z opadaniem i wznoszeniem, wtedy otrzymaliśmy najbardziej zbliżone podejście TextSize, następująca praca jest dla siebie dość łatwa.

// 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);

zrzut ekranu według kodu

Nawiasem mówiąc, zdecydowanie zalecamy używanie RectF zamiast Rect, ponieważ pozycje wymagają dokładniejszych wartości, z mojego doświadczenia wynika, że ​​RectF wykonał odchylenie górne i dolne tylko jeden piksel na urządzeniu xhdpi, Rect będzie jeszcze dwa.

VinceStyling
źródło
Jak napisano, var „Paint” jest mylące. Jak to jest przypisane? skąd zna wznoszenie i opadanie tekstu dla bounds.bottom?
RoundSparrow hilltx
1
tak, zoptymalizowałem swoją odpowiedź na to, proszę spojrzeć.
VinceStyling
Dzięki za oszczędność mojego cennego czasu))
Andrey
16

Twój kod rysuje środek linii bazowej tekstu w środku widoku. Aby wyśrodkować tekst w pewnym momencie, X, Y, trzeba obliczyć środek tekstu i umieścić że w punkcie.

Ta metoda narysuje tekst wyśrodkowany w punkcie x, y. Jeśli przejdziesz przez środek widoku, narysuje tekst na środku.

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);
}
Borislav Markov
źródło
@MarcioGranzotto jest to, że android.graphics.Paintjest używany do narysowania tekstu
William Reed
4

Uważam, że najlepsze rozwiązanie do centrowania tekstu jest następujące:

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);    `
Daniel
źródło
To wygląda dokładnie tak, jak pytający próbował wyśrodkować tekst. Tekst nie będzie wyśrodkowany pionowo, ponieważ Paint.Align.Center nie oznacza, że ​​punkt odniesienia tekstu jest wyśrodkowany pionowo.
andreas1724,
3

działa dla mnie do użycia: textPaint.textAlign = Paint.Align.CENTER z 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
            )
        }

wynikiem jest:

wprowadź opis zdjęcia tutaj

Serg Burlaka
źródło
1

Tworzę metodę, aby uprościć to:

    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 to obszar, w którym chcesz narysować tekst, to wszystko. Detale

Seth
źródło
1

Jeśli korzystamy z układu statycznego

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

Layout.Alignment.ALIGN_CENTER to załatwi sprawę . Układ statyczny ma również wiele innych zalet.

Odniesienie: Dokumentacja Androida

hushed_voice
źródło
1

To działało dla mnie:

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

jeśli ktoś znajdzie jakiś problem, proszę o informację

JSONParser
źródło
zacznie pisać tekst od punktu środkowego, ale cały tekst nie będzie w środku
M Shaban Ali
0

W moim przypadku nie musiałem umieszczać tekstu na środku płótna, ale w obracającym się kole. Chociaż musiałem użyć tego kodu, aby odnieść sukces:

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)
}

Następnie wywołuję tę metodę z klasy 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)
}
Mattia Ferigutti
źródło