Canvas(Android)に描画されるテキストの高さの測定


132

テキストの高さを測定する簡単な方法はありますか?私が現在行っている方法は、ペイントを使用しmeasureText()て幅を取得し、試行錯誤で値を見つけておおよその高さを取得することです。私もいじくり回してきましたFontMetricsが、これらはすべておかしな近似方法のようです。

私はさまざまな解像度に合わせて物事をスケーリングしようとしています。私はそれを行うことができますが、相対サイズを決定するための多くの計算を含む信じられないほど冗長なコードになってしまいます。私はそれが嫌いです!もっと良い方法があるはずです。

回答:


136

何について)(paint.getTextBounds(オブジェクトメソッド)


1
これは、テキストの高さを評価すると、非常に奇妙な結果になります。短いテキストでは高さが12になり、本当に長いテキストでは高さが16になります(フォントサイズが16の場合)。私には意味がありません(android 2.3.3)
AgentKnopf

35
高さの差異は、テキストに下降線がある場所です。つまり、線より下のgの部分が原因で、「高」は「低」よりも高くなります
FrinkTheBrave

208

必要に応じて、高さを測定するさまざまな方法があります。

getTextBounds

少量の固定テキストを正確に中央揃えするようなことをしている場合は、おそらく必要ですgetTextBounds。あなたはこのように外接する長方形を得ることができます

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

次の画像からわかるように、ストリングが異なると高さが異なります(赤で表示)。

ここに画像の説明を入力してください

これらの異なる高さは、テキストが何であっても一定の高さだけが必要な場合に不利になることがあります。次のセクションを参照してください。

Paint.FontMetrics

フォントメトリックからフォントの高さを計算できます。特定のテキスト文字列ではなく、フォントから取得されるため、高さは常に同じです。

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

ベースラインは、テキストが置かれる行です。ディセントは通常、キャラクターがラインの下に行く最も遠いところにあり、アセントは一般的にキャラクターがラインの上にくる最も遠いところにあります。高さを取得するには、それが負の値であるため、アセントを引く必要があります。(ベースラインは画面であり、画面y=0y減少させます。)

次の画像を見てください。両方のストリングの高さは234.375です。

ここに画像の説明を入力してください

テキストの高さだけでなく、行の高さが必要な場合は、次のようにします。

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

これらはラインのbottomand topです。先頭(行間)は通常ゼロですが、とにかく追加する必要があります。

上の画像はこのプロジェクトからのものです。フォントメトリックがどのように機能するかを確認するために、これをいじってみてください。

StaticLayout

複数行のテキストの高さを測定するには、を使用する必要がありStaticLayoutます。この回答で少し詳しく説明しましたが、この高さを取得する基本的な方法は次のとおりです。

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

いい説明。スクリーンショットはどのアプリからのものですか?
Micheal Johnson

1
@MichealJohnson、アプリをGitHubプロジェクトとしてここに追加しました。
Suragch 2017

1
では、「getTextSize」によって何が得られるのでしょうか。
Android開発者

1
Paint's getTextSize()は、フォントサイズを(sp単位ではなく)ピクセル単位で示します。@androiddeveloper
2018年

2
ピクセル単位のフォントサイズと、測定された高さおよびFontMetricsの寸法との関係は何ですか?それは私がもっと探求したい質問です。
スラグ

85

@brampの答えは正しいです-部分的に、計算された境界は、暗黙の開始座標0、0でテキストを完全に含む最小の長方形になることは言及されていません。

つまり、たとえば「Py」の高さは「py」、「hi」、「oi」、「aw」の高さとは異なります。これは、ピクセル単位で高さが異なるためです。

これは、決して古典的なJavaのFontMetricsと同等ではありません。

テキストの幅はそれほど問題ではありませんが、高さは問題ではありません。

特に、描画されたテキストを垂直方向に中央揃えにする必要がある場合は、描画するテキストを使用する代わりに、テキスト "a"(引用符なし)の境界を取得してみてください。私のために働く...

これが私の意味です:

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

テキストを垂直方向に中央揃えするとは、境界の長方形を垂直方向に中央揃えにすることです。これは、テキスト(大文字、長い文字など)によって異なります。しかし、実際にやりたいのは、レンダリングされたテキストのベースラインを揃えて、高さや溝が表示されないようにすることです。したがって、最小の文字の中心(たとえば "a")がわかっている限り、その配置を残りのテキストに再利用できます。これにより、すべてのテキストが中央揃えになり、ベースライン揃えになります。


25
長い間見x >> 1ていません。そのためだけに賛成:)
keaukraine

61
優れた最新のコンパイラはx / 2、それを表示して最適化しますx >> 1
intrepidis

37
@keaukraine x / 2は、Chrisのコメントを考慮すると、コードを読むときにはるかに友好的です。
d3dave 2013

bufferこの例には何がありますか?それはメソッドにcanvas渡されdraw(Canvas)ますか?
AutonomousApps

@AutonomousAppsはい、それはキャンバスだ
Chisko

16

高さは、Paint変数に設定したテキストサイズです。

高さを確認する別の方法は

mPaint.getTextSize();

3

android.text.StaticLayoutクラスを使用して必要な境界を指定し、を呼び出すことができますgetHeight()draw(Canvas)メソッドを呼び出すことにより、テキスト(レイアウトに含まれる)を描画できます。


2

getTextSize()メソッドを使用すると、Paintオブジェクトのテキストサイズを簡単に取得できます。例えば:

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

どこ24.0fから来たのですか?
エリガミ2015

24.0fこれはテキストサイズの例にすぎません
moondroid

1

代わりにRect.width()and Rect.Height()が返すものを使用する必要がありますgetTextBounds()。それは私にとってはうまくいきます。


2
テキストの複数のセグメントを扱っている場合はそうではありません。その理由は上記の私の答えにあります。
Nar Gar

0

それでも問題が解決しない場合は、これが私のコードです。

正方形(幅=高さ)のカスタムビューがあり、それに文字を割り当てたいです。onDraw()文字の高さを取得する方法を示していますが、使用していません。画面中央にキャラクターが表示されます。

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

3
onDraw()での割り当ての場合は-1:クラスでフィールドを宣言し(コンストラクターで開始)、onDraw()でそれらを再利用すると、パフォーマンスが大幅に向上します。
zoltish、2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.