Измерение высоты текста для рисования на холсте (Android)

132

Любой простой способ измерить высоту текста? Я делаю это сейчас, используя Paint measureText()для получения ширины, а затем методом проб и ошибок нахожу значение для получения приблизительной высоты. Я тоже возился FontMetrics, но все это похоже на приблизительные методы, которые отстой.

Я пытаюсь масштабировать вещи для разных разрешений. Я могу это сделать, но в итоге получаю невероятно подробный код с большим количеством вычислений для определения относительных размеров. Я ненавижу это! Там должен быть лучший способ.

Danedo
источник

Ответы:

136

А как насчет paint.getTextBounds () (метод объекта)

bramp
источник
1
Это дает очень странные результаты, когда я оцениваю высоту текста. Короткий текст дает высоту 12, тогда как ДЕЙСТВИТЕЛЬНО длинный текст дает высоту 16 (при размере шрифта 16). Для меня это не имеет смысла (android 2.3.3)
AgentKnopf
36
Разница в высоте - это то, где у вас есть
нижние элементы
209

Есть разные способы измерить рост в зависимости от того, что вам нужно.

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=0и yуменьшает экран.)

Посмотрите на следующее изображение. Высоты для обеих струн 234.375.

введите описание изображения здесь

Если вы хотите высоту строки, а не просто высоту текста, вы можете сделать следующее:

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

Это bottomи 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(); 
Suragch
источник
Хорошее объяснение. Из какого приложения скриншоты?
Майкл Джонсон
1
@MichealJohnson, я добавил приложение в качестве проекта GitHub здесь .
Сурагч
1
Что же дает вам getTextSize?
Android-разработчик
1
Paint getTextSize()дает вам размер шрифта в пиксельных единицах (в отличие от spединиц). @androiddeveloper
Сурагч,
2
Каково отношение размера шрифта в пиксельных единицах к измеренной высоте и измерениям FontMetrics ? Это вопрос, который я хотел бы изучить подробнее.
Сурагч,
85

Ответ @ bramp правильный - частично, в нем не упоминается, что вычисленные границы будут минимальным прямоугольником, который содержит текст полностью с неявными начальными координатами 0, 0.

Это означает, что высота, например, «Py» будет отличаться от высоты «py», «hi», «oi» или «aw», поскольку для каждого пикселя требуется разная высота.

Это ни в коем случае не эквивалентно FontMetrics в классической Java.

В то время как ширина текста не очень большая боль, высота -.

В частности, если вам нужно выровнять по центру нарисованный текст по вертикали, попробуйте получить границы текста «а» (без кавычек) вместо использования текста, который вы собираетесь рисовать. Работает для меня...

Вот что я имею в виду:

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

Выравнивание текста по центру по вертикали означает выравнивание по вертикали ограничивающего прямоугольника по центру, что отличается для разных текстов (заглавные буквы, длинные буквы и т. Д.). Но то, что мы действительно хотим сделать, это также выровнять базовые линии визуализированных текстов, чтобы они не выглядели приподнятыми или рифлеными. Таким образом, пока мы знаем центр самой маленькой буквы (например, «а»), мы можем повторно использовать ее выравнивание для остальных текстов. Это будет выравнивать по центру все тексты, а также выравнивать их по базовой линии.

Нар Гар
источник
25
Давно не виделись x >> 1. Голосование только за это :)
keaukraine
62
Хороший современный компилятор увидит x / 2и оптимизирует егоx >> 1
intrepidis
37
@keaukraine x / 2гораздо более дружелюбен при чтении кода, учитывая комментарии Криса.
d3dave
что bufferв этом примере? Это canvasпередано в draw(Canvas)метод?
Автономные приложения
@AutpendentApps да, это холст
Чиско
16

Высота - это размер текста, который вы установили в переменной Paint.

Еще один способ узнать высоту

mPaint.getTextSize();
kimnod
источник
3

Вы можете использовать android.text.StaticLayoutкласс, чтобы указать необходимые границы, а затем вызвать getHeight(). Вы можете нарисовать текст (содержащийся в макете), вызвав его draw(Canvas)метод.

intrepidis
источник
2

Вы можете просто получить размер текста для объекта Paint, используя метод getTextSize (). Например:

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
источник
Откуда 24.0fвзялся?
Erigami
24.0f это просто пример размера текста
moondroid
1

Вы должны использовать Rect.width()и Rect.Height()который вернулся getTextBounds()вместо. Это подходит для меня.

Путти
источник
2
Нет, если вы имеете дело с несколькими сегментами текстов. Причина в моем ответе выше.
Нар Гар
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();
    }
}
Hesam
источник
3
-1 для выделений в onDraw (): это принесло бы массу преимуществ в производительности, если бы вы объявили поля в классе (initiate в конструкторе) и повторно использовали их в onDraw ().
Золтиш