- PVSM.RU - https://www.pvsm.ru -

Рисуем знак рубля в Android приложении

В последнее время перед разработчиками все чаще ставится задача использовать символ рубля в тексте. Однако, символ рубля был утвержден относительно недавно [1], символ получил свой код в стандате Unicode еще позже [2]. Естественно, гарнитура Roboto на текущих платформах еще не содержит знака рубля.

Идея

Создать гарнитуру состоящую из одного символа рубля с кодом U+20BD [3], и при отрисовке текста для символов рубля использовать эту гарнитуру.

Немного о Span

В ОС Android существует механизм маркировки строк специальными объектами влияющими на рендеринг текста TextView. Например, существуют объекты для переопределения цвета заливки [4]/фона [5], стиля [6] или всего сразу [7] (полный список [8] встроенных в Android маркеров). Об этом механизме уже писале на хабре здесь [9] и здесь [10].

Создание нужной гарнитуры

За основу будущей гарнитуры я взял готовое решение от Артемия Лебедева, о котором писали [11] некоторое время назад на Хабре. Эта гарнитура состоит из различных начертаний символа рубля закрепленных за символами латинского алфавита (от строчной a до s).

Список всех глифов

Я выбрал глиф, который закреплен за буквой i (Символ рубля). Как мне кажется, он наиболее подходит для использования с гарнитурой Roboto.
Для редактирования гарнитуры, я использовал замечательное приложение Glyphs [12]. Я удалил все глифы из гарнитуры и оставил только выбранный. Ему я назначил код U+20BD. Следующий шаг — экспорт в ttf.
В итоге у меня получилось так [13].
Конечно, можно было бы отредактировать глиф заглавной Р, дорисовав палочку, однако, я не чувствую в себе уверенности в этом деле. Уж больно много нюансов. Если кто-то может сделать это, с удовольствием приму ваш pull request.

Реализация

Для начала создадим проект из шаблона. Я использовал шаблон с Blank Activity из комплекта Android Studio, activity назвал MainActivity.
Разметка для MainActivity выглядит так выглядит так:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/main_price_input"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="numberDecimal"
        tools:hint="@string/main_price_hint"
        />

</LinearLayout>

Тут все просто, создаем LinearLayout с одним потомком EditText. Свойство hint у EditText мы задаем с namespace tools, для того что бы подсказка отображалась только в предпросмотре. В программе мы будем задавать подсказку программно.

В MainActivity#onCreate мы должны составить spanned [14] string, в котором все знаки рубля будут маркированны TypefaceSpan [15], который позволяет изменить гарнитуру для отрисовки заданных символов. Тут нас ожидает маленькая неприятность: TypefaceSpan можно создать только с font-family — названием шрифта из набора системы. К счастью, судя по исходному коду TypefaceSpan [16], этот подход не обусловлен техническими возможностями системы рендеринга текста, что позволяет нам создать собственную версию TypefaceSpan, который поддерживает задание гарнитуры непосредственно объектом Typeface [17]. Копируем

package me.pepyakin.roublesign;

import android.graphics.Paint;
import android.graphics.Typeface;
import android.text.TextPaint;
import android.text.style.MetricAffectingSpan;

public class TypefaceSpan2 extends MetricAffectingSpan {
    private final Typeface mTypeface;

    public TypefaceSpan2(Typeface typeface) {
        mTypeface = typeface;
    }

    @Override
    public void updateDrawState(TextPaint ds) {
        apply(ds, mTypeface);
    }

    @Override
    public void updateMeasureState(TextPaint paint) {
        apply(paint, mTypeface);
    }

    private static void apply(Paint paint, Typeface tf) {
        int oldStyle;

        Typeface old = paint.getTypeface();
        if (old == null) {
            oldStyle = 0;
        } else {
            oldStyle = old.getStyle();
        }

        int fake = oldStyle & ~tf.getStyle();

        if ((fake & Typeface.BOLD) != 0) {
            paint.setFakeBoldText(true);
        }

        if ((fake & Typeface.ITALIC) != 0) {
            paint.setTextSkewX(-0.25f);
        }

        paint.setTypeface(tf);
    }
}

Осталось только сделать маркировку текста с помощью объявленного выше TypefaceSpan2, с необходимой нам гарнитурой.

String priceHint = getString(R.string.main_price_hint);

final Typeface roubleSupportedTypeface =
        Typeface.createFromAsset(getAssets(), "fonts/rouble2.ttf");

SpannableStringBuilder resultSpan = new SpannableStringBuilder(priceHint);
for (int i = 0; i < resultSpan.length(); i++) {
    if (resultSpan.charAt(i) == 'u20BD') {
        TypefaceSpan2 roubleTypefaceSpan = new TypefaceSpan2(roubleSupportedTypeface);
        resultSpan.setSpan(roubleTypefaceSpan, i, i + 1, 0);
    }
}

Здесь мы загружаем шаблон нашей строки, содержащей символ рубля и гарнитуру с глифом рубля.
Затем создаем SpannableStringBuilder [18] на основе шаблона и с помощью setSpan [19] расставляем маркеры для смены гарнитуры.

Назначаем hint,

priceInput.setHint(resultSpan); 

и готово!
Готовый результат

Ссылка на исходники [20]

Автор: knott

Источник [21]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/razrabotka-pod-android/66014

Ссылки в тексте:

[1] был утвержден относительно недавно: http://ru.wikinews.org/wiki/%D0%91%D0%B0%D0%BD%D0%BA_%D0%A0%D0%BE%D1%81%D1%81%D0%B8%D0%B8_%D1%83%D1%82%D0%B2%D0%B5%D1%80%D0%B4%D0%B8%D0%BB_%D1%81%D0%B8%D0%BC%D0%B2%D0%BE%D0%BB_%D1%80%D1%83%D0%B1%D0%BB%D1%8F

[2] свой код в стандате Unicode еще позже: https://twitter.com/ken_lunde/status/430761051290759168

[3] U+20BD: http://www.fileformat.info/info/unicode/char/20bd/index.htm

[4] заливки: http://developer.android.com/reference/android/text/style/ForegroundColorSpan.html

[5] фона: http://developer.android.com/reference/android/text/style/BackgroundColorSpan.html

[6] стиля : http://developer.android.com/reference/android/text/style/StyleSpan.html

[7] всего сразу: http://developer.android.com/reference/android/text/style/TextAppearanceSpan.html

[8] полный список: http://developer.android.com/reference/android/text/style/package-summary.html

[9] здесь: http://habrahabr.ru/post/166351/

[10] здесь: http://habrahabr.ru/post/167343/

[11] писали: http://habrahabr.ru/post/48084/

[12] Glyphs: http://www.glyphsapp.com/

[13] так: https://github.com/pepyakin/roublesign/blob/master/app/src/main/assets/fonts/rouble2.ttf

[14] spanned: http://developer.android.com/reference/android/text/Spanned.html

[15] TypefaceSpan: http://developer.android.com/reference/android/text/style/TypefaceSpan.html

[16] исходному коду TypefaceSpan: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/text/style/TypefaceSpan.java

[17] Typeface: https://developer.android.com/reference/android/graphics/Typeface.html

[18] SpannableStringBuilder: https://developer.android.com/reference/android/text/SpannableStringBuilder.html

[19] setSpan: https://developer.android.com/reference/android/text/SpannableStringBuilder.html#setSpan(java.lang.Object, int, int, int)

[20] Ссылка на исходники: https://github.com/pepyakin/roublesign

[21] Источник: http://habrahabr.ru/post/231203/