Skip to content
Advertisement

Why are complex emojis not merged but split up when drawn on Android canvas?

I want to implement an emoji selector for my keyboard app Keyboard Designer. To do so I want to draw emojis based on unicodes in hexadecimal format. The emoji “u1F636u200Du1F32BuFE0F” is shown correctly when I write it in the text field (two eyes behind a cloud), but when I draw it on my canvas, it looks like two separate emojis (left top corner of the keyboard):

Emoji is not draw correctly

Hint: The hearts do nothing have to do with the question, they only mark favourite emojis

My source is nested in objects and methods, but to show you how it works, I change it to linear commands:

ArrayList<char[]> utf16Chars = new ArrayList<>();
String hexCode = "\u1F636\u200D\u1F32B\uFE0F";
int distance;
if (hexCode.startsWith("\")) {
    for (int i = 0; i < hexCode.length(); i += distance) {
        distance = hexCode.indexOf("\", i + 1) - i;
        if (distance < 0)
            distance = hexCode.length() - i;
        String utf16Code = hexCode.substring(i, i + distance);
        int decimalCode = Integer.parseInt(utf16Code.length() >= 6 ? utf16Code.substring(2) : utf16Code, 16);
        char[] utf8Chars = Character.toChars(decimalCode);
        utf16Chars.add(utf8Chars);
    }
}
StringBuilder stringBuilder = new StringBuilder();
for(char[] utf8Chars : utf16Chars)
    for(char character : utf8Chars)
        stringBuilder.append(character);

String emoji = stringBuilder.toString();
canvas.drawText(emoji, 0, emoji.length(), x, y, paint);

Does anyone have an idea what I do wrong?

Advertisement

Answer

Update

Also see this bug report.

The Face in Clouds Emoji was added in Emoji Version 13.1 and will probably be generally available in later versions of Android. My answer, below, works for API 31 but not for API 30 or before. There is a backward compatibility issue with that solution.

There is a class EmojiCompat that can supply compatibility that will display the “Face in Clouds” emoji on Android version before API 31.

I have put together a EmojiCompat project based on the EmojiCompat app in Google’s user-interface-samples. A couple of notes on this demo:

  1. The EmojiCompatApplication class has some important setup.

  2. The dependency on version 1.1.0 of androidx.emoji:emoji-bundled was updated to version 1.2.0-alpha03. Without this update, the “Face in Clouds” emoji displays as two emojis and not one. As new emojis are released (yearly, I think), this library will need to be updated. I believe an alternative is to use downloadable emoji fonts, but I do not address downloadable fonts here.

  3. In MainActivity, I left everything as it was in the Google project except that I added processing for “MyView” which creates a StaticLayout and displays the content using Layout.draw(canvas) as specified in my previous solution which is what the OP was requesting. Canvas.drawText() is still discouraged.

Here is the output of the demo app on an emulator running API 24:

enter image description here

This was more involved than I thought at first and I could not find a good tutorial online. Maybe someone knows of one and can suggest it.



I used the following simple code to create the combined emoji.

//    val cloudy = "u1F636u200Du1F32BuFE0F"
val cloudyFace = intArrayOf(0x1F636, 0x200D, 0x1F32B, 0xFE0F)
val sb = StringBuilder()
for (i in 0 until cloudyFace.size) {
    sb.append(getUtf16FromInt(cloudyFace[i]))
}
binding.textView.text = sb.toString()

fun getUtf16FromInt(unicode: Int) = String(Character.toChars(unicode))

Instead of using Canvas.drawText() use layout.draw(canvas) where layout is a StaticLayout. From the documentation:

This is used by widgets to control text layout. You should not need to use this class directly unless you are implementing your own widget or custom display object, or would be tempted to call Canvas.drawText() directly.

Bottom line: Don’t use Canvas.drawText().

You may also use the BoringLayout class if that better suits your needs.

enter image description here

User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement