Implementations of Emoji (Emoticon) View/Keyboard Layouts

I am trying to figure out how the emoji (emoticon) selections are implemented on the Facebook app and the Google Hangouts app. I looked into the SoftKeyboard Demo app in the Android API Samples but the display of these emoji views does not look like a SoftKeyboard. It looks and behaves more like a custom Dialog view. Does anyone have an idea of how these are implemented?

Facebook App

Facebook

Google Hangouts app

Hangouts

Also, is Unicode the best way to send emoticons or is there an alternative? I noticed that some Unicode sequences like \u1F601 don't render the corresponding emoticon and instead that sequence just shows up as 1 :

EditText messageInput = (EditText) findViewById(R.id.message_input);
messageInput.getText().append("\u1F601");

Solution 1:

I found a very useful Emoticon Keyboard. This keyboard is not using Unicode sequences but rather just local image assets. I am thinking that this type of keyboard can only be useful within this app and not with other apps or Operating Systems.

So instead I am replacing the ImageView containing an asset with a TextView containing a Unicode sequence.

After cross referencing Supported Unicode Sequences as well as the Visual Unicode Database I realized that \u1F601 was a 32 bit Unicode representation, and the 16bit representation can be set like :

EditText messageInput = (EditText) findViewById(R.id.message_input);
messageInput.getText().append("\ud83d\ude01");

Solution 2:

You can use this library based on Hieu Rocker's library: https://github.com/ankushsachdeva/emojicon

This is how it looks

Screenshot

Solution 3:

If you don't want to get into details of how to implement the Emoji-Keyboard functionality, you can try these libraries:

  • Rockerhieu / emojicon: implements the Emoji-Keyboard using fragments (you will need to handle the display of it using a DialogFragment. It doesn't have support for changing the layout and the default one is Hole Dark theme;

  • Android Emoji Keyboard: Based on Rockerhieu work I've built another lib that provides the emoji-keyboard as closer to what we've seen in apps such as WhatsApp or Telegram. You can handle the layout as a LinearLayout and, therefore, handle the interaction with the soft keyboard yourself (as described below) or choose to use Telegram Panel or WhatsApp Panel, both provided by the library, that does it for you.

PS1: Both libraries are Apache License

Final Result should look like that

Part 01: Building the layout

  • Create a GridView for each Emoji-Page you want in your keyboard. For instance:

  • Bind the created views in Fragments:

    public class FragmentEmojiNature extends FragmentEmoji {
    
    public static final String TAG = "FragmentEmojiNature";
    
    private View mRootView;
    private Emoji[] mData;
    private boolean mUseSystemDefault = false;
    
    private static final String USE_SYSTEM_DEFAULT_KEY = "useSystemDefaults";
    private static final String EMOJI_KEY = "emojic";
    
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        this.mRootView = inflater.inflate(R.layout.frag_emoji_nature, container, false);
        return this.mRootView;
    }
    
    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        GridView gridView = (GridView) view.findViewById(R.id.Emoji_GridView);
        Bundle bundle = getArguments();
        if (bundle == null) {
            mData = Nature.DATA;
            mUseSystemDefault = false;
        } else {
            Parcelable[] parcels = bundle.getParcelableArray(EMOJI_KEY);
            mData = new Emoji[parcels.length];
            for (int i = 0; i < parcels.length; i++) {
                mData[i] = (Emoji) parcels[i];
            }
            mUseSystemDefault = bundle.getBoolean(USE_SYSTEM_DEFAULT_KEY);
        }
        gridView.setAdapter(new EmojiAdapter(view.getContext(), mData, mUseSystemDefault));
        gridView.setOnItemClickListener(this);
    }
    

    }

  • Create a new layout containing a view pager and some component to control the view pager transition (in my case I've used a third part library called SmartTabLayout as shown below:

    <com.ogaclejapan.smarttablayout.SmartTabLayout
        android:id="@+id/emoji_tabs"
        android:layout_width="0dip"
        android:layout_height="40dip"
        android:layout_weight="1"
        app:stl_clickable="true"
        app:stl_defaultTabBackground="@color/rsc_emoji_tab_bkg"
        app:stl_defaultTabTextAllCaps="true"
        app:stl_defaultTabTextColor="#000"
        app:stl_defaultTabTextHorizontalPadding="0dip"
        app:stl_defaultTabTextMinWidth="0dp"
        app:stl_defaultTabTextSize="14sp"
        app:stl_distributeEvenly="true"
        app:stl_dividerColor="@color/rsc_emoji_tab_bkg"
        app:stl_drawDecorationAfterTab="true"
        app:stl_indicatorColor="@color/rsc_emoji_tab_indicator"
        app:stl_indicatorGravity="bottom"
        app:stl_indicatorInFront="false"
        app:stl_indicatorInterpolation="smart"
        app:stl_indicatorThickness="2dp"
        app:stl_overlineThickness="0dp"
        app:stl_titleOffset="24dp"
        app:stl_underlineThickness="0dp"/>
    
    <ImageButton
        android:id="@+id/backspace"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:background="@color/rsc_emoji_tab_bkg"
        android:paddingLeft="10dip"
        android:paddingRight="10dip"
        android:src="@drawable/sym_keyboard_delete_holo_dark"/>
    

PS2: the button above exists in order to provide the backspace functionality

Part 02: Controller layer

  • Create an adapter to control the insertion of the emojis in the GridView, for example:

    public class EmojiAdapter extends ArrayAdapter<Emoji> {
    
        private boolean mUseSystemDefault = Boolean.FALSE;
    
        // CONSTRUCTOR
        public EmojiAdapter(Context context, Emoji[] data) {
            super(context, R.layout.rsc_emoji_item, data);
        }
    
        public EmojiAdapter(Context context, List<Emoji> data) {
            super(context, R.layout.rsc_emoji_item, data);
        }
    
        public EmojiAdapter(Context context, List<Emoji> data, boolean useSystemDefault) {
            super(context, R.layout.rsc_emoji_item, data);
            this.mUseSystemDefault = useSystemDefault;
        }
    
        public EmojiAdapter(Context context, Emoji[] data, boolean useSystemDefault) {
            super(context, R.layout.rsc_emoji_item, data);
            this.mUseSystemDefault = useSystemDefault;
        }
    
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View view = convertView;
    
            if (view == null) {
                view = View.inflate(getContext(), R.layout.rsc_emoji_item, null);
                view.setTag(new ViewHolder(view, this.mUseSystemDefault));
            }
    
            Emoji emoji = this.getItem(position);
            ViewHolder holder = (ViewHolder) view.getTag();
            holder.icon.setText(emoji.getEmoji());
    
            return view;
        }
    
        static class ViewHolder {
            EmojiTextView icon;
    
            public ViewHolder(View view, Boolean useSystemDefault) {
                this.icon = (EmojiTextView) view.findViewById(R.id.emoji_icon);
                this.icon.setUseSystemDefault(useSystemDefault);
            }
        }
    }
    
  • Create classes that will inflate each one of the emoji-pages passing the emojis (following the Unicode pattern) to the GridView. i.e.:

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        GridView gridView = (GridView) view.findViewById(R.id.Emoji_GridView);
        Bundle bundle = getArguments();
        if (bundle == null) {
            mData = Nature.DATA;
            mUseSystemDefault = false;
        } else {
            Parcelable[] parcels = bundle.getParcelableArray(EMOJI_KEY);
            mData = new Emoji[parcels.length];
            for (int i = 0; i < parcels.length; i++) {
                mData[i] = (Emoji) parcels[i];
            }
            mUseSystemDefault = bundle.getBoolean(USE_SYSTEM_DEFAULT_KEY);
        }
        gridView.setAdapter(new EmojiAdapter(view.getContext(), mData, mUseSystemDefault));
        gridView.setOnItemClickListener(this);
    }
    
  • Create two classes: one that extends from EditText and another that extends from TextView. Intercept the input of each one of them to identify when the typed element is an Emoji, if so, add a spannable in order to display the icon (mainly need if you want to override the system default emojis to have something that looks like whats-app or telegram, for instance);

  • Handle the interaction with soft-keyboard. It can be done in two ways:

    1. Draw a Dialog over the soft keyboard;
    2. Disable the interaction of the soft keyboard with the activity and handle the screen draw yourself;

PS3: I had a hard time trying to format the code and still some of the XML is not shown, if someone can fix it I would be thankfull

Solution 4:

A viewgroup be visible or gone also can relize that,It doesn't neet the dialog. Every time initialize the emotion , I always user a key value like [happy]=R.drawable.happy. this is the text to emotion while content like [happy]

public SpannableString textToImage(String content,Context c){
    SpannableString ss = new SpannableString(content);
    int starts = 0;
    int end = 0;
    if(content.indexOf("[", starts) != -1 && content.indexOf("]", end) != -1){
        starts = content.indexOf("[", starts);
        end = content.indexOf("]", end);
         SharedPreferences shared=c.getSharedPreferences("emotion",0);
         int resource=shared.getInt(content,0);
        try {
            Drawable drawable =c.getResources().getDrawable(resource);  
            if (drawable != null) {
                drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); 
                ImageSpan span = new ImageSpan(drawable, ImageSpan.ALIGN_BASELINE);  
                ss.setSpan(span, starts,end + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);  
            }
        } catch (Exception ex){

        }
    }
    return ss;

}