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):
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:
The EmojiCompatApplication class has some important setup.
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.
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:
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.