Emoji (эмодзи) в андроид

Все мы любим общение текстом через разнообразные мессенджеры, однако зачастую такой способ не передаёт чувства и эмоции собеседника в отличии от устной беседы. И вот тут к нам на помощь приходят emoji смайлики и переписка оживает 😊. Да и не только переписка, ведь разнообразие пиктограмм столь велико, что ими можно запросто дополнять скучные текстовые сообщения, меняя пользовательский интерфейс в лучшую сторону👍. Но и здесь для разработчиков найдутся подводные камни, о которых и поговорим.

С технической точки зрения, в использовании эмодзи нет ничего сложного, ведь это всего лишь обычный юникод символ, вставляем его в TextView и он отображается как и должен:

android 7.1.2

Однако, так будет не всегда, ведь эмодзи — это вещь меняющаяся. Каждый год (а то и по нескольку раз за год) появляются новые emoji о которых старые андроид устройства разумеется ничего не знают. И при попытке отобразить несуществующие на устройстве эмодзи — вместо них будут квадратики или вообще пустота (тут уж как повезёт).

android 4.4.2

Для решения этой проблемы существует замечательная библиотека под названием EmojiCompat. Благодаря ей, пользователям не надо ждать выпуска обновления операционной системы для своего смартфона, чтоб начать использовать новые emoji. Вероятно, клавиатура Gboard (от google) использует ровно ту же самую библиотеку, а это означает, что пользователь сразу же сможет вводить новые эмодзи, а ваше приложение — показывать их. Существуют две конфигурации данной библиотеки:

  • Со шрифтом в комплекте (bundled fonts configuration)
  • С загружаемыми шрифтами (downloadable fonts configuration)

У первой конфигурации есть большое преимущество: все эмодзи могут быть показаны сразу же после установки приложения и не нужно ждать пока загрузится последняя версия шрифта. Этот вариант идеально подходит для тех приложений, которые должны работать оффлайн (хотя сейчас сложно представить себе смартфон без интернета). Главный минус здесь один: шрифт с эмодзи не обновляется сам по себе без обновления EmojiCompat библиотеки которая используется в вашем приложении. Второй минус, это размер apk файла вашего приложения, благодаря встроенному шрифту, он увеличится примерно на 7 мБайт, что существенно. Оба недостатка можно решить используя конфигурацию с загружаемыми шрифтами, но может произойти так, что на тот момент когда эмодзи вам понадобится — он еще не будет загружен. Допустим, текст с эмодзи должен появляться сразу же после загрузки активити. При первом запуске приложения — шрифта еще нет, он загрузится позднее, и тогда вместо эмодзи будет пустота 😢. Однако стоит заметить, что если какое-нибудь другое приложение на данном устройстве ранее использовало EmojiCompat с загружаемым шрифтом, то шрифт будет мгновенно доступен и вашему приложению без скачивания. Сам шрифт хранится где-то в недрах Font Provider’а, который появился в андроид 8.0, если же андроид более старый, то используется поставщик шрифтов из Google Play Services. И если сервисы гугла не установлены на таком устройстве (или старее 11 версии) — ничего не заработает и придётся использовать конфигурацию укомплектованную шрифтом. Для начала работы с библиотекой, потребуется добавить в build.gradle одну из следующих зависимостей :
Для загружаемых шрифтов: implementation "com.android.support:support-emoji:28.0.0"
Шрифты в комплекте: implementation "com.android.support:support-emoji-bundled:28.0.0" 

1️⃣ Инициализация

Гугл рекомендует инициализировать библиотеку как можно раньше, например в наследнике класса Application, однако это не обязательно. Главное, не пытаться использовать её до инициализации и тогда ничего страшного не произойдёт. Сама инициализация очень простая и немного отличается в зависимости то используемой конфигурации. Для bundled она всего в 2 строчки:

   EmojiCompat.Config cfg = new BundledEmojiCompatConfig(getApplicationContext());
   EmojiCompat.init(cfg);

Создаём конфиг с настройками по-умолчанию и всё готово к использованию. Чуть сложнее обстоят дела с загружаемыми шрифтами, при их использовании, инициализация выглядит так:

      FontRequest fontRequest = new FontRequest(
              "com.google.android.gms.fonts",
              "com.google.android.gms",
              "Noto Color Emoji Compat",
              R.array.com_google_android_gms_fonts_certs);
      EmojiCompat.Config cfg = new FontRequestEmojiCompatConfig(getApplicationContext(), fontRequest);
      EmojiCompat.init(cfg);

Здесь в конструктор конфига передаётся не только контекст приложения, но еще и FontRequest, который используется для получения шрифта от провайдера шрифтов. В конструкторе
FontRequest указываем какой провайдер шрифтов используется и какой шрифт нам от него нужен. Массив com_google_android_gms_fonts_certs необходим для проверки подлинности поставщика шрифтов, заполнить его правильными значениями может сама андроид студия, но для удобства его содержимое приведено ниже:

    <array name="com_google_android_gms_fonts_certs">
        <item>@array/com_google_android_gms_fonts_certs_dev</item>
        <item>@array/com_google_android_gms_fonts_certs_prod</item>
    </array>
    <string-array name="com_google_android_gms_fonts_certs_dev">
        <item>
            MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAeFw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVyxW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8XW8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexAcKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkwHQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0cxb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrPzgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXclaXjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05aIskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+aayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUWEv9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs=
        </item>
    </string-array>
    <string-array name="com_google_android_gms_fonts_certs_prod">
        <item>
            MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK
        </item>
    </string-array>

2️⃣ Использование

Когда библиотека инициализирована любым из двух способов описанных выше, можно начинать использовать emoji в вашем приложении. Зачастую будет достаточно показывать эмодзи в трёх стандартных компонентах интерфейса: Поле ввода текста (EditText), надпись (TextView) и кнопка (Button). Для упрощения кода, в библиотеке существуют наследники этих трёх классов, которые позволяют без лишних сложностей показывать в них эмодзи и называются они EmojiEditText, EmojiTextView и EmojiButton соответственно. Но в большинстве случаев, программисты используют библиотеку AppCompat, а следовательно наследоваться наследоваться надо уже от AppCompatEditText, AppCompatTextView и AppCompatButton. Для этого добавим еще одну зависимость в build.gradle :

implementation "com.android.support:support-emoji-appcompat:28.0.0"

После этого, станут доступны EmojiAppCompatEditText, EmojiAppCompatTextView и EmojiAppCompatButton которые мы и будем использовать в layout’е:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="10dp"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context=".MainActivity"
    tools:showIn="@layout/activity_main">

    <android.support.text.emoji.widget.EmojiAppCompatTextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="EmojiCompat library demo🛠️"
        android:textColor="@android:color/black"
        android:textSize="20sp"/>

    <android.support.text.emoji.widget.EmojiAppCompatEditText
        android:id="@+id/editText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="💥text💥"/>

    <android.support.text.emoji.widget.EmojiAppCompatButton
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Emoji button🔥"/>
</LinearLayout>

Собственно на этом и заканчиваются все сложности, сразу после запуска активити, эмодзи появляются на своих местах во всех трёх компонентах:

android 4.4.2 теперь показывает эмодзи

Отдельным пунктом нужно рассказать о том, как могут быть представлены emoji в коде андроид приложения. Самый простой способ — скопировать emoji из браузера, например и затем вставить его в layout файл (как в примере выше). Если же требуется добавить emoji программно, а создавать строку в файле ресурсов ради одного символа не хочется, то достаточно будет узнать код нужного эмодзи и вставить его:

      EditText editText = findViewById(R.id.editText);
      String emojiString  = new String(Character.toChars(0x1F60B)); // 😋
      editText.setText(emojiString);

Или более короткий вариант, без преобразования кода в символ и последующего создания строки:

      EditText editText = findViewById(R.id.editText);
      String emojiString  = "\uD83D\uDE0B"; // тот же символ 😋
      editText.setText(emojiString);

Может возникнуть закономерный вопрос: как код 0x1F60B превратился в \uD83D\uDE0B ? На самом деле, это тот же код эмодзи, но представленный в виде surrogate pair. Алгоритм выполняющий преобразование, хорошо описан тут. Есть еще один более простой вариант получить surrogate pair для любого emoji средствами андроид студии: копируем в буфер обмена нужный emoji, а затем просто вставляем его в строку (между кавычек) и преобразование произойдёт автоматически, что удобно. Иногда бывает, что один эмодзи кодируется при помощи сразу нескольких кодов:

      EditText editText = findViewById(R.id.editText);
      String man = "\uD83D\uDC68";   //👨
      String woman = "\uD83D\uDC69"; //👩
      String boy = "\uD83D\uDC66";   //👦
      String joiner = "\u200D";
      String emojiString  = man + joiner + woman + joiner + boy; //👨‍👩‍👦
      editText.setText(emojiString);

Разрозненные emoji мужчины, женщины и мальчика, объединились в один при помощи специального символа (zero-width joiner). Таких комбинаций есть очень много, подробнее можно ознакомиться тут.

3️⃣ Прочее

Если по каким-то причинам вам нужно отображать emoji в стандартных компонентах (вроде TextView) , можно реализовать это программно:

            String text = "Text with emoji \uD83D\uDD25";
            CharSequence result = EmojiCompat.get().process(text);
            plainTextView.setText(result);

Главное не забывать, что инициализация библиотеки занимает время и пока она не произошла — использовать её нельзя. Для этого можно зарегистрировать листенер и отслеживать этот момент:

      EmojiCompat.Config config = ... 
      config.registerInitCallback(new EmojiCompat.InitCallback() {
         @Override
         public void onInitialized() {
            super.onInitialized();
            Log.d("EmojiCompat", "Ready to use");
         }
         @Override
         public void onFailed(@Nullable Throwable throwable) {
            super.onFailed(throwable);
            Log.e("EmojiCompat", "Can't init lib", throwable);
         }
      });
      EmojiCompat.init(config);

Для ускорения инициализации библиотеки (с загружаемым шрифтом), можно добавить в манифест информацию о том, что шрифт содержащий эмодзи необходим приложению. Google Play должен загрузить его вместе с приложением и к моменту первого запуска он уже будет доступен

    <!-- Adding this meta-data will download the font when application is installed from
        Google Play Store. This way the font will be downloaded and ready when your app starts
        for the first time. -->
    <meta-data android:name="fontProviderRequests" android:value="Noto Color Emoji Compat"/>

Изложенной здесь информации вполне достаточно для того, чтобы использовать данную библиотеку. Важно помнить, что в использовании эмодзи важна умеренность и злоупотреблять их количеством уж точно не стоит 🙏 .