Вимірювання висоти тексту для малювання на полотні (Android)


132

Будь-який прямий спосіб вимірювання висоти тексту? Я зараз це роблю, використовуючи Paint, measureText()щоб отримати ширину, а потім шляхом спроб і помилок знайти значення, щоб отримати приблизну висоту. Я теж возився FontMetrics, але все це здається приблизними методами, які смокчуть.

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

Відповіді:


136

Що про paint.getTextBounds () (метод об'єкта)


1
Це дає дуже дивні результати, коли я оцінюю висоту тексту. Короткий текст призводить до висоти 12, тоді як ДІЙСНІ довгий текст призводить до висоти 16 (з урахуванням розміру шрифту 16). Не має сенсу для мене (android 2.3.3)
AgentKnopf

35
Різниця у висоті - це те, де у тексті є
пониження

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=0і ydescreases до екрану.)

Подивіться на наступне зображення. Висоти для обох струн є 234.375.

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

Якщо вам потрібна висота рядка, а не лише висота тексту, ви можете зробити наступне:

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

Це bottomта topлінія. Провідний (міжрядковий інтервал) зазвичай дорівнює нулю, але все одно слід додати його.

Зображення, наведені вище, походять із цього проекту . Ви можете пограти з ним, щоб побачити, як працюють метрики шрифтів.

StaticLayout

Для вимірювання висоти багаторядкового тексту слід використовувати a 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(); 

Приємне пояснення. З якого додатку створюються скріншоти?
Мішель Джонсон

1
@MichealJohnson, я додав додаток як проект GitHub тут .
Сурагч

1
Що тоді дає "getTextSize"?
андроїд розробник

1
Paint's 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віками. Оголошення лише за це :)
кеукрайн

62
Хороший сучасний компілятор побачить x / 2і оптимізує його доx >> 1
intrepidis

37
@keaukraine x / 2набагато привітніше при читанні коду, враховуючи коментар Кріса.
d3dave

що bufferв цьому прикладі? Чи canvasпередано це draw(Canvas)методу?
AutonomousApps

@AutonomousApps так, це полотно
Чіско

16

Висота - це розмір тексту, який ви встановили для змінної Paint.

Ще один спосіб дізнатися висоту

mPaint.getTextSize();

3

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


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

Звідки береться 24.0f?
Ерігамі

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

3
-1 для розподілу в onDraw (): Це дало б тону переваг від продуктивності, якби ви оголосили поля в класі (ініціювати в конструкторі) і повторно використовувати їх у onDraw ().
золтиш
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.