Android输入框添加emoje表情图标的实现代码
作者:jia-huan
前言
再次写聊天的时候才发现,代码积累是一件非常重要的事情,就如这篇博客的意图其实就是代码积累的目的,其实没什么难度,但是一件很琐碎的事情真的也需要时间去完成和调试,所以,获取你在写一个功能的时候会觉得并没有多难,但是如果可以最好把代码整理/积累下来。
demo描述
demo的功能其实就是仿照微信的 聊天 emoje 选择,采用了 viewpager+gridView 的方案,不过有空我会补上 recyclerView 的方案,目前还是先把功能实现了再说。另外在 TextView 和 EditText 中添加 emoje ,可以看看这篇博客:Android中使用TextView及EditText来实现表情图标的显示及插入功能 ,这篇博客中介绍了两种方法:
方法一:使用Html.fromHtml解析, 方法二:使用Bitmap直接画出来,我采用了第二种方法,使用bitmap画出来。
Read the fucking code
思路:既然是 viewpager + gridview 那么,先从大方向入手,完成 viewpager,再去完成 gridview。PS:代码里面使用了 RxJava、lambda、ButterKnife、EventBus、Glide。
这里将整个底部布局写成了一个组合的ViewGroup – ChatBottomBar,先从布局开始。
ChatBottomBar 的 XML – chat_bottom.xml:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:animateLayoutChanges="true" android:orientation="vertical"> <include layout="@layout/chat_bottom_input"></include> <include layout="@layout/chat_bottom_function1"></include> </LinearLayout>
以下分别是 输入框的 xml 和 Emoji 的 xml:
chat_bottom_input:
<?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <RelativeLayout android:id="@+id/rl_input" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#f0f0f0"> <ImageView android:id="@+id/showMore" android:layout_width="42dp" android:layout_height="60dp" android:paddingBottom="5dp" android:paddingLeft="9dp" android:paddingTop="9dp" android:src="@mipmap/ic_launcher" /> <LinearLayout android:layout_width="match_parent" android:layout_height="35dp" android:layout_centerVertical="true" android:layout_marginRight="15dp" android:layout_toRightOf="@+id/showMore" android:background="@drawable/shape_white_corner" android:gravity="center_vertical" android:orientation="horizontal"> <ImageView android:layout_width="45dp" android:layout_height="40dp" android:paddingBottom="10dp" android:paddingLeft="10dp" android:paddingRight="5dp" android:paddingTop="10dp" android:src="@mipmap/ic_launcher" /> <EditText android:id="@+id/editText" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginRight="10dp" android:background="@null" android:gravity="center_vertical" android:hint="说点什么" android:maxLines="3" android:textColor="#999999" android:textColorHint="#dddddd" android:textSize="13sp" /> </LinearLayout> </RelativeLayout> </merge>
chat_bottom_function1:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#ffffff" android:orientation="vertical"> <android.support.v4.view.ViewPager android:id="@+id/emojes" android:layout_width="match_parent" android:layout_height="110dp"></android.support.v4.view.ViewPager> </LinearLayout>
首先是 viewpager 填充 gridView,从 PageAdapter 看起,看看需要哪些数据:
package cjh.emojicondemo; import android.content.Context; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; import android.view.View; import android.widget.GridView; import java.util.ArrayList; /** * Created by cjh on 16-11-8. */ public class EmojiPageAdapter extends PagerAdapter { private ArrayList<GridView> mLists; public EmojiPageAdapter(Context context, ArrayList<GridView> array) { this.mLists = array; } @Override public int getCount() { return mLists.size(); } @Override public boolean isViewFromObject(View arg0, Object arg1) { return arg0 == arg1; } @Override public Object instantiateItem(View arg0, int arg1) { ((ViewPager) arg0).addView(mLists.get(arg1)); return mLists.get(arg1); } @Override public void destroyItem(View arg0, int arg1, Object arg2) { ((ViewPager) arg0).removeView((View) arg2); } }
其实基本就是PagerAdapter的模板代码,需要的仅仅只是 gridView,看下在ChatbottomBar中的代码:
@BindView(R.id.emojes) android.support.v4.view.ViewPager emojes; .... //每一页有24个表情,然后使用Math的ceil函数,计算出我们需要的最小页数 private void initEmoje() { int pageCount = (int) Math.ceil(EmojiUtils.emojis.length / 24.0f); ArrayList<GridView> pageData = new ArrayList<>(); for (int i = 0; i < pageCount; i++) { GridView gv = getGridView(i); pageData.add(gv); } emojes.setAdapter(new EmojiPageAdapter(context, pageData)); }
大结构基本就是这样了,接着就是小细节了,比如gridView的创建和展示:
@NonNull private GridView getGridView(int i) { GridView gv = new GridView(context); gv.setVerticalScrollBarEnabled(false); gv.setAdapter(new EmojiGridAdapter(context, i)); gv.setGravity(Gravity.CENTER); gv.setClickable(true); gv.setFocusable(true); gv.setNumColumns(8); return gv; }
adapter:
package cjh.emojicondemo; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ImageView; import org.greenrobot.eventbus.EventBus; /** * Created by cjh on 16-11-8. */ public class EmojiGridAdapter extends BaseAdapter { private Context context; private int page; public EmojiGridAdapter(Context context, int page) { this.context = context; this.page = page; } @Override public int getCount() { return 24; } @Override public Object getItem(int i) { return null; } @Override public long getItemId(int i) { return 0; } @Override public View getView(int i, View view, ViewGroup viewGroup) { ViewHolder holder = null; if (view == null) { view = LayoutInflater.from(context).inflate(R.layout.chat_emoji, null); holder = new ViewHolder(); holder.image = (ImageView) view.findViewById(R.id.image); view.setTag(holder); } holder = (ViewHolder) view.getTag(); int position = page * 23 + i; if (position < EmojiUtils.emojis.length) ImageLoader.load(context, EmojiUtils.icons[position], holder.image); else holder.image.setVisibility(View.GONE); holder.image.setOnClickListener(view1 -> EventBus.getDefault().post(new EmojiEvent(EmojiUtils.emojis[page * 23 + i]))); return view; } static class ViewHolder { public ImageView image; } }
在这里,点击时间的传递我使用的是EventBus。
大结构基本已经OK了,接着就要看比较核心的部分,Emoji 的处理,在接收到Event事件时,调用了chatBottomBar.appandEmoje(emojiEvent.s)
@Subscribe public void onEmojiEvent(EmojiEvent emojiEvent) { chatBottomBar.appandEmoje(emojiEvent.s); }
那么来看看ChatBottomBar的代码:
public void appandEmoje(String s) { rx.Observable .just(s) .subscribeOn(Schedulers.io()) .map(s1 -> { SpannableString emojeText = EmojiUtils.getEmojiText(editText.getText().toString() + s1); return emojeText; }) .unsubscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(s2 -> { editText.setText(""); editText.append(s2); }); }
上面代码使用了RXJAVA,可以看到真正的核心是在
EmojiUtils.getEmojiText(editText.getText().toString() + s1);
return emojeText;这行代码里面。
那么就来看看 EmojiUtils 的代码吧:
package cjh.emojicondemo; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.drawable.Drawable; import android.net.Uri; import android.text.Spannable; import android.text.SpannableString; import android.text.TextUtils; import android.text.style.ImageSpan; import android.text.style.RelativeSizeSpan; import android.util.SparseArray; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.Inflater; /** * Created by cjh on 16-11-7. */ public class EmojiUtils { private static HashMap<Pattern, Integer> emoMap = new HashMap<>(); public static final String DELETE_KEY = "em_delete_delete_expression"; public static String[] emojis = new String[]{ "[微笑]", "[撇嘴]", "[色]", "[发呆]", "[得意]", "[流泪]", "[害羞]", "[闭嘴]", "[睡]", "[大哭]", "[尴尬]", "[发怒]", "[调皮]", "[呲牙]", "[惊讶]", "[难过]", "[酷]", "[冷汗]", "[抓狂]", "[吐]", "[偷笑]", "[愉快]", "[白眼]", "[傲慢]", "[饥饿]", "[困]", "[惊恐]", "[流汗]", "[憨笑]", "[悠闲]", "[奋斗]", "[咒骂]", "[疑问]", "[嘘]", "[晕]", "[疯了]", "[衰]", "[骷髅]", "[敲打]", "[再见]", "[擦汗]", "[抠鼻]", "[鼓掌]", "[糗大了]", "[坏笑]", "[左哼哼]", "[右哼哼]", "[哈欠]", "[鄙视]", "[委屈]", "[快哭了]", "[阴险]", "[亲亲]", "[吓]", "[可怜]", "[菜刀]", "[西瓜]", "[啤酒]", "[篮球]", "[乒乓]", "[咖啡]", "[饭]", "[猪头]", "[玫瑰]", "[凋谢]", "[嘴唇]", "[爱心]", "[心碎]", "[蛋糕]", "[闪电]", "[炸弹]", "[刀]", "[足球]", "[瓢虫]", "[便便]", "[月亮]", "[太阳]", "[礼物]", "[拥抱]", "[强]", "[弱]", "[握手]", "[胜利]", "[抱拳]", "[勾引]", "[拳头]", "[差劲]", "[爱你]", "[NO]", "[OK]" }; public static int[] icons = new int[]{ R.drawable.ee_1, R.drawable.ee_2, R.drawable.ee_3, R.drawable.ee_4, R.drawable.ee_5, R.drawable.ee_6, R.drawable.ee_7, R.drawable.ee_8, R.drawable.ee_9, R.drawable.ee_10, R.drawable.ee_11, R.drawable.ee_12, R.drawable.ee_13, R.drawable.ee_14, R.drawable.ee_15, R.drawable.ee_16, R.drawable.ee_17, R.drawable.ee_18, R.drawable.ee_19, R.drawable.ee_20, R.drawable.ee_21, R.drawable.ee_22, R.drawable.ee_23, R.drawable.ee_24, R.drawable.ee_25, R.drawable.ee_26, R.drawable.ee_27, R.drawable.ee_28, R.drawable.ee_29, R.drawable.ee_30, R.drawable.ee_31, R.drawable.ee_32, R.drawable.ee_33, R.drawable.ee_34, R.drawable.ee_35, R.drawable.ee_36, R.drawable.ee_37, R.drawable.ee_38, R.drawable.ee_39, R.drawable.ee_40, R.drawable.ee_41, R.drawable.ee_42, R.drawable.ee_43, R.drawable.ee_44, R.drawable.ee_45, R.drawable.ee_46, R.drawable.ee_47, R.drawable.ee_48, R.drawable.ee_49, R.drawable.ee_50, R.drawable.ee_51, R.drawable.ee_52, R.drawable.ee_53, R.drawable.ee_54, R.drawable.ee_55, R.drawable.ee_56, R.drawable.ee_57, R.drawable.ee_58, R.drawable.ee_59, R.drawable.ee_60, R.drawable.ee_61, R.drawable.ee_62, R.drawable.ee_63, R.drawable.ee_64, R.drawable.ee_65, R.drawable.ee_66, R.drawable.ee_67, R.drawable.ee_68, R.drawable.ee_69, R.drawable.ee_70, R.drawable.ee_71, R.drawable.ee_72, R.drawable.ee_73, R.drawable.ee_74, R.drawable.ee_75, R.drawable.ee_76, R.drawable.ee_77, R.drawable.ee_78, R.drawable.ee_79, R.drawable.ee_80, R.drawable.ee_81, R.drawable.ee_82, R.drawable.ee_83, R.drawable.ee_84, R.drawable.ee_85, R.drawable.ee_86, R.drawable.ee_87, R.drawable.ee_88, R.drawable.ee_89, R.drawable.ee_90, }; static { for (int i = 0; i < emojis.length; i++) { emoMap.put(Pattern.compile(Pattern.quote(emojis[i])), icons[i]); } } public static SpannableString getEmojiText(String s) { SpannableString spannable = new SpannableString(s); for (Map.Entry<Pattern, Integer> entry : emoMap.entrySet()) { Matcher matcher = entry.getKey().matcher(spannable); while (matcher.find()) { for (ImageSpan span : spannable.getSpans(matcher.start(), matcher.end(), ImageSpan.class)) if (spannable.getSpanStart(span) >= matcher.start() && spannable.getSpanEnd(span) <= matcher.end()) spannable.removeSpan(span); else break; Drawable drawable = MainActivity.context.getResources().getDrawable(entry.getValue()); drawable.setBounds(0, 0, 60, 60); ImageSpan imageSpan = new ImageSpan(drawable); spannable.setSpan(imageSpan, matcher.start(), matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } } return spannable; } }
这里为了方便知道插入表情的位置,我将emoji对应的中文转化成了Pattern对象,在getEmojiText里面做了遍历查询比对,这也就是为什么我会使用RX来异步操作。
基本就到这里了,回过来看写的内容,自己都懒得吐槽,不过,好在只要有具体的demo,能读代码,有没有讲解其实都还好,也不用怕自己之后看不懂了。
源码下载:https://github.com/cjhandroid/EmojiInputDemo
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。