Mengukur tinggi teks yang akan digambar di atas Kanvas (Android)

132

Adakah cara langsung untuk mengukur ketinggian teks? Cara saya melakukannya sekarang adalah dengan menggunakan Paint measureText()untuk mendapatkan lebar, kemudian dengan coba-coba menemukan nilai untuk mendapatkan perkiraan ketinggian. Saya juga telah bermain-main dengan FontMetrics, tetapi semua ini tampak seperti metode perkiraan yang menyedot.

Saya mencoba untuk mengukur berbagai hal untuk resolusi yang berbeda. Saya bisa melakukannya, tetapi saya berakhir dengan kode yang sangat verbose dengan banyak perhitungan untuk menentukan ukuran relatif. Saya membencinya! Harus ada cara yang lebih baik.

Danedo
sumber

Jawaban:

136

Bagaimana dengan paint.getTextBounds () (metode objek)

bramp
sumber
1
Ini menghasilkan hasil yang sangat aneh, ketika saya mengevaluasi ketinggian teks. Teks pendek menghasilkan ketinggian 12, sedangkan teks yang BENAR-BENAR menghasilkan tinggi 16 (diberi ukuran font 16). Tidak masuk akal bagi saya (android 2.3.3)
AgentKnopf
35
Varian dalam tinggi adalah di mana Anda memiliki keturunan dalam teks, yaitu 'Tinggi' lebih tinggi dari 'Rendah' ​​karena bagian dari g di bawah garis
FrinkTheBrave
208

Ada berbagai cara untuk mengukur ketinggian tergantung pada apa yang Anda butuhkan.

getTextBounds

Jika Anda melakukan sesuatu seperti tepatnya memusatkan sejumlah kecil teks tetap, Anda mungkin ingin getTextBounds. Anda bisa mendapatkan persegi panjang pembatas seperti ini

Rect bounds = new Rect();
mTextPaint.getTextBounds(mText, 0, mText.length(), bounds);
int height = bounds.height();

Seperti yang dapat Anda lihat untuk gambar berikut, string yang berbeda akan memberikan ketinggian yang berbeda (ditunjukkan dengan warna merah).

masukkan deskripsi gambar di sini

Ketinggian yang berbeda ini bisa menjadi kerugian dalam beberapa situasi ketika Anda hanya membutuhkan ketinggian konstan tidak peduli apa teksnya. Lihat bagian selanjutnya.

Paint.FontMetrics

Anda dapat menghitung tinggi font dari metrik font. Tingginya selalu sama karena diperoleh dari font, bukan string teks tertentu.

Paint.FontMetrics fm = mTextPaint.getFontMetrics();
float height = fm.descent - fm.ascent;

Garis dasar adalah garis yang ditempati teks. Keturunan umumnya karakter terjauh akan pergi di bawah garis dan pendakian umumnya karakter terjauh akan pergi di atas garis. Untuk mendapatkan ketinggian Anda harus mengurangi pendakian karena itu adalah nilai negatif. (Garis dasar adalah y=0dan ymenurunkan layar.)

Lihatlah gambar berikut. Ketinggian untuk kedua string adalah 234.375.

masukkan deskripsi gambar di sini

Jika Anda menginginkan tinggi garis daripada hanya tinggi teks, Anda bisa melakukan hal berikut:

float height = fm.bottom - fm.top + fm.leading; // 265.4297

Ini adalah bottomdan topdari garis. Yang memimpin (interline spacing) biasanya nol, tetapi Anda tetap harus menambahkannya.

Gambar di atas berasal dari proyek ini . Anda dapat bermain-main dengannya untuk melihat cara kerja Metrik Font.

StaticLayout

Untuk mengukur ketinggian teks multi-baris Anda harus menggunakan a StaticLayout. Saya membicarakannya secara terperinci dalam jawaban ini , tetapi cara dasar untuk mendapatkan tinggi ini adalah seperti ini:

String text = "This is some text. This is some text. This is some text. This is some text. This is some text. This is some text.";

TextPaint myTextPaint = new TextPaint();
myTextPaint.setAntiAlias(true);
myTextPaint.setTextSize(16 * getResources().getDisplayMetrics().density);
myTextPaint.setColor(0xFF000000);

int width = 200;
Layout.Alignment alignment = Layout.Alignment.ALIGN_NORMAL;
float spacingMultiplier = 1;
float spacingAddition = 0;
boolean includePadding = false;

StaticLayout myStaticLayout = new StaticLayout(text, myTextPaint, width, alignment, spacingMultiplier, spacingAddition, includePadding);

float height = myStaticLayout.getHeight(); 
Suragch
sumber
Penjelasan yang bagus. Dari mana aplikasi screenshot itu?
Micheal Johnson
1
@MichealJohnson, saya menambahkan aplikasi sebagai proyek GitHub di sini .
Suragch
1
Apa yang diberikan "getTextSize" kepada Anda?
pengembang android
1
Paint getTextSize()memberi Anda ukuran font dalam satuan piksel (berbeda dengan spsatuan). @androiddeveloper
Suragch
2
Apa hubungan ukuran font dalam satuan piksel dengan tinggi yang diukur dan dimensi FontMetrics ? Itu adalah pertanyaan yang ingin saya jelajahi lebih lanjut.
Suragch
85

Jawaban @ bramp benar - sebagian, karena tidak menyebutkan batas yang dihitung akan menjadi persegi panjang minimum yang berisi teks sepenuhnya dengan koordinat awal implisit 0, 0.

Ini berarti, bahwa ketinggian, misalnya "Py" akan berbeda dari ketinggian "py" atau "hi" atau "oi" atau "aw" karena piksel-bijaksana mereka memerlukan ketinggian yang berbeda.

Ini sama sekali tidak setara dengan FontMetrics di java klasik.

Meskipun lebar teks tidak terlalu menyebalkan, tinggi adalah.

Khususnya, jika Anda perlu meluruskan tengah teks yang ditarik secara vertikal, coba dapatkan batas teks "a" (tanpa tanda kutip), alih-alih menggunakan teks yang ingin Anda buat. Bekerja untuk saya ...

Inilah yang saya maksud:

Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.LINEAR_TEXT_FLAG);

paint.setStyle(Paint.Style.FILL);
paint.setColor(color);
paint.setTextAlign(Paint.Align.CENTER);
paint.setTextSize(textSize);

Rect bounds = new Rect();
paint.getTextBounds("a", 0, 1, bounds);

buffer.drawText(this.myText, canvasWidth >> 1, (canvasHeight + bounds.height()) >> 1, paint);
// remember x >> 1 is equivalent to x / 2, but works much much faster

Vertikal tengah menyelaraskan teks berarti secara vertikal tengah menyelaraskan persegi panjang pembatas - yang berbeda untuk teks yang berbeda (huruf besar, huruf panjang, dll). Tetapi apa yang sebenarnya ingin kita lakukan adalah juga menyelaraskan garis dasar teks yang diberikan, sehingga teks tersebut tidak tampak terangkat atau beralur. Jadi, selama kita tahu pusat huruf terkecil ("a" misalnya) kita kemudian dapat menggunakan kembali perataannya untuk sisa teks. Ini akan memusatkan menyelaraskan semua teks dan juga menyelaraskan garis dasar teks.

Nar Gar
sumber
25
Sudah lama tidak terlihat x >> 1. Putuskan hanya untuk itu :)
keaukraine
62
Kompiler modern yang baik akan melihat x / 2dan mengoptimalkannya untukx >> 1
intrepidis
37
@keaukraine x / 2jauh lebih ramah saat membaca kode, mengingat komentar Chris.
d3dave
apa yang ada bufferdalam contoh ini? Apakah ini canvasditeruskan ke draw(Canvas)metode?
AutonomousApps
@ AutonomousApps ya, ini kanvasnya
Chisko
16

Ketinggian adalah ukuran teks yang telah Anda tetapkan pada variabel Paint.

Cara lain untuk mengetahui ketinggiannya adalah

mPaint.getTextSize();
Kimnod
sumber
3

Anda bisa menggunakan android.text.StaticLayoutkelas untuk menentukan batasan yang diperlukan dan kemudian menelepon getHeight(). Anda dapat menggambar teks (terkandung dalam tata letak) dengan memanggil draw(Canvas)metodenya.

intrepidis
sumber
2

Anda cukup mendapatkan ukuran teks untuk objek Paint menggunakan metode getTextSize (). Sebagai contoh:

Paint mTextPaint = new Paint (Paint.ANTI_ALIAS_FLAG);
//use densityMultiplier to take into account different pixel densities
final float densityMultiplier = getContext().getResources()
            .getDisplayMetrics().density;  
mTextPaint.setTextSize(24.0f*densityMultiplier);

//...

float size = mTextPaint.getTextSize();
moondroid
sumber
Dari mana datangnya 24.0f?
Erigami
24.0f itu hanya contoh untuk ukuran teks
moondroid
1

Anda harus menggunakan Rect.width()dan dari Rect.Height()mana kembali getTextBounds(). Itu bekerja untuk saya.

Putti
sumber
2
Tidak jika Anda berurusan dengan banyak segmen teks. Alasannya ada pada jawaban saya di atas.
Nar Gar
0

Jika ada yang masih memiliki masalah, ini adalah kode saya.

Saya memiliki tampilan kustom yang persegi (lebar = tinggi) dan saya ingin menetapkan karakter ke dalamnya. onDraw()menunjukkan cara mendapatkan ketinggian karakter, meskipun saya tidak menggunakannya. Karakter akan ditampilkan di tengah tampilan.

public class SideBarPointer extends View {

    private static final String TAG = "SideBarPointer";

    private Context context;
    private String label = "";
    private int width;
    private int height;

    public SideBarPointer(Context context) {
        super(context);
        this.context = context;
        init();
    }

    public SideBarPointer(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        init();
    }

    public SideBarPointer(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        this.context = context;
        init();
    }

    private void init() {
//        setBackgroundColor(0x64FF0000);
    }

    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        height = this.getMeasuredHeight();
        width = this.getMeasuredWidth();

        setMeasuredDimension(width, width);
    }

    protected void onDraw(Canvas canvas) {
        float mDensity = context.getResources().getDisplayMetrics().density;
        float mScaledDensity = context.getResources().getDisplayMetrics().scaledDensity;

        Paint previewPaint = new Paint();
        previewPaint.setColor(0x0C2727);
        previewPaint.setAlpha(200);
        previewPaint.setAntiAlias(true);

        Paint previewTextPaint = new Paint();
        previewTextPaint.setColor(Color.WHITE);
        previewTextPaint.setAntiAlias(true);
        previewTextPaint.setTextSize(90 * mScaledDensity);
        previewTextPaint.setShadowLayer(5, 1, 2, Color.argb(255, 87, 87, 87));

        float previewTextWidth = previewTextPaint.measureText(label);
//        float previewTextHeight = previewTextPaint.descent() - previewTextPaint.ascent();
        RectF previewRect = new RectF(0, 0, width, width);

        canvas.drawRoundRect(previewRect, 5 * mDensity, 5 * mDensity, previewPaint);
        canvas.drawText(label, (width - previewTextWidth)/2, previewRect.top - previewTextPaint.ascent(), previewTextPaint);

        super.onDraw(canvas);
    }

    public void setLabel(String label) {
        this.label = label;
        Log.e(TAG, "Label: " + label);

        this.invalidate();
    }
}
Hesam
sumber
3
-1 untuk alokasi di onDraw (): Ini akan menghasilkan satu ton manfaat kinerja jika Anda mendeklarasikan bidang dalam kelas (inisiasi dalam konstruktor) dan menggunakannya kembali di onDraw ().
Zoltish