Pages

Wednesday, October 21, 2015

Android regional indicator symbols, country flags emoji's

So, i had this problem, instead of country flags like on iPhone, my app showed symbols like "US", "GB", etc. I needed to show them in a TextView.

I found out that those regional indicator symbols are supported starting with android 5.0, but genymotion test on a 5.0 device showed that they are not fully working there.

The logic of the workaround is like this:

  1. get those symbols
  2. convert them to normal characters like "us", "gb".
  3. get an appropriate png image from raw folder
  4. show in textview using spannable string
Detailed description (huge thanks to the blogspot code formatting xD):

First i separated the text part and the symbols part of the string. There is this special character '\uD83C'  that comes before each regional indicator symbol, and you can split the string on it. 

Character sc = '\uD83C';
if (input.indexOf(sc) > -1){
    //input has special regional characters    String[] parts = input.split(sc.toString());    String text = parts[0];
    String[] flagchars = new String[parts.length - 1];
    for (int i = 1; i < parts.length; i++){
        flagchars[i - 1] = parts[i];    }
}

So now the flagchars array contains only the regional indicator symbols.

Next step is to parse those symbols to normal characters
String flagLetters = "";for (int i = 0; i < flagchars.length; i++){
    char ch = flagchars[i].charAt(0);    flagLetters = flagLetters + parseRegionalSymbolToLetter(ch);}

private static String parseRegionalSymbolToLetter(char c) {
    String result = "";
    switch (c){
        case '\uDDE6': result = "A"; break;        case '\uDDE7': result = "B"; break;        case '\uDDE8': result = "C"; break;        case '\uDDE9': result = "D"; break;        case '\uDDEA': result = "E"; break;        case '\uDDEB': result = "F"; break;        case '\uDDEC': result = "G"; break;        case '\uDDED': result = "H"; break;        case '\uDDEE': result = "I"; break;        case '\uDDEF': result = "J"; break;        case '\uDDF0': result = "K"; break;        case '\uDDF1': result = "L"; break;        case '\uDDF2': result = "M"; break;        case '\uDDF3': result = "N"; break;        case '\uDDF4': result = "O"; break;        case '\uDDF5': result = "P"; break;        case '\uDDF6': result = "Q"; break;        case '\uDDF7': result = "R"; break;        case '\uDDF8': result = "S"; break;        case '\uDDF9': result = "T"; break;        case '\uDDFA': result = "U"; break;        case '\uDDFB': result = "V"; break;        case '\uDDFC': result = "W"; break;        case '\uDDFD': result = "X"; break;        case '\uDDFE': result = "Y"; break;        case '\uDDFF': result = "Z"; break;    }

    return result;}

Then, i need to return a List of bitmaps to where im setting the text to TextView

List<Bitmap> bl = getFlagIcons(flagLetters, c);

private static List<Bitmap> getFlagIcons(String flagLetters, Context c) {
    List<Bitmap> bl = new ArrayList<>();
    String remainingLetters = flagLetters;
    int i = 1;
    while (!remainingLetters.isEmpty()){
        String letters = remainingLetters.substring(0, i);        String imageName = checkImageName(letters);        Bitmap flag = getBitmapFromRaw(imageName, c);
        if (flag == null){
            i++;        }else{
            remainingLetters = remainingLetters.substring(i, remainingLetters.length());            bl.add(flag);            i = 0;        }
    }

    return bl;}

This method works like this (sample input string "USGBCA"):
  1. takes first char, tries to find a bitmap in raw folder - in first iteration looks for u.png, 2nd - us.png
  2. if there is no such file, adds one more letter and repeats
  3. if a file is found - adds to List of bitmaps and starts over for next letters.
Returns list of bitmaps. Input "USGBCA" will result in a list of 3 bitmaps with flags of US, GB, CA. This method will also locate 1 char and 3 char countries.

The checkImageName method is there to modify file names for countries if needed

private static String checkImageName(String letters) {

    String result = letters.toLowerCase();
    switch (result){
        case "do": result = "_do"; break;    }

    return result;}

Because a do.png file cant exist because do is a keyword.

public static Bitmap getBitmapFromRaw(String filename, Context context) {
    int identifier = context.getResources().getIdentifier(filename, "raw", context.getPackageName());    Bitmap bmp = null;    if (identifier != 0) {
        InputStream is = context.getResources().openRawResource(identifier);        BufferedInputStream bis = new BufferedInputStream(is);        bmp = BitmapFactory.decodeStream(bis);    }
    return bmp;}



So now we have the List of bitmaps that we need to put inside a TextView.

Next method returns a SpannableString.

public static SpannableString getSpannableString(String text, List<Bitmap> images, Context context) {
    int iml = images.size();    String spaces = "";    for (int k = 0; k < iml; k++){
        spaces = spaces + "  ";    }

    SpannableString ss = new SpannableString(text + spaces);    int l = text.length();
    if (!images.isEmpty()) {
        for (int i = 0; i < iml; i++) {
            Bitmap b = images.get(i);
            Drawable d = new BitmapDrawable(context.getResources(), b);            float scale = context.getResources().getDisplayMetrics().density * 1.3f;            float w = d.getIntrinsicWidth() * scale;            float h = d.getIntrinsicHeight() * scale;            d.setBounds(0, 0, (int) w, (int) h);            ImageSpan is = new ImageSpan(d, ImageSpan.ALIGN_BASELINE);            ss.setSpan(is, l + i * 2, l + i * 2 + 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);        }
    }

    return ss;}

Which is then set to a textview

text.setText(ss);

And ofcourse you need to get flag pngs inside of your res/raw folder. I got mine from here http://www.famfamfam.com/lab/icons/flags/