再次写聊天的时候才发现,代码积累是一件非常重要的事情,就如这篇博客的意图其实就是代码积累的目的,其实没什么难度,但是一件很琐碎的事情真的也需要时间去完成和调试,所以,获取你在写一个功能的时候会觉得并没有多难,但是如果可以最好把代码整理/积累下来。
demo的功能其实就是仿照微信的 聊天 emoje 选择,采用了 viewpager+gridView 的方案,不过有空我会补上 recyclerView 的方案,目前还是先把功能实现了再说。另外在 TextView 和 EditText 中添加 emoje ,可以看看这篇博客:Android中使用TextView及EditText来实现表情图标的显示及插入功能 ,这篇博客中介绍了两种方法: 方法一:使用Html.fromHtml解析, 方法二:使用Bitmap直接画出来,我采用了第二种方法,使用bitmap画出来。
思路:既然是 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,能读代码,有没有讲解其实都还好,也不用怕自己之后看不懂了。
源码下载