输入框添加Emoje表情demo

前言

再次写聊天的时候才发现,代码积累是一件非常重要的事情,就如这篇博客的意图其实就是代码积累的目的,其实没什么难度,但是一件很琐碎的事情真的也需要时间去完成和调试,所以,获取你在写一个功能的时候会觉得并没有多难,但是如果可以最好把代码整理/积累下来。


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:


<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:


<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:


<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 mLists;

    public EmojiPageAdapter(Context context, ArrayList 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 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 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 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,能读代码,有没有讲解其实都还好,也不用怕自己之后看不懂了。
源码下载

你可能感兴趣的:(开发日常)