在做一些社会化APP时,用户总是青睐使用表情,下面就探究一下如何在APP中添加表情。在支持输入表情时,一般要涉及到表情框&&键盘的切换,需要有一个按钮,来触发事件!这里仅仅是一个雏形,存在一些问题,在这里暴露一下:
1.表情&&键盘切换时,会发生跳动;
2.这里的输入框,仅仅支持一个。
在AndroidManifest,activity标签中添加windowSoftInputMode=adjustResize,键盘弹出时,整个页面就会向上移动,这样咱们放置在页面底部的键盘&&表情切换的按钮,就一直可见了。
<activity
android:windowSoftInputMode="adjustResize"
android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
intent-filter>
activity>
<com.util.emotions.IMEBar
android:id="@+id/ime_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/btn"
android:orientation="vertical">
<EditText
android:id="@+id/et_input"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="left|top"
android:hint="分享新鲜事..."
android:inputType="textMultiLine"
android:minHeight="200dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#eeeeee"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="8dp">
<ImageView
android:id="@+id/iv_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="null"
android:src="@drawable/icon_icon" />
LinearLayout>
<FrameLayout
android:id="@+id/fl_content"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
com.util.emotions.IMEBar>
R.id.fl_content就是为表情框留下的位置。当键盘不可见的时候,R.id.fl_content就填充表情;当键盘可见时,R.id.fl_content GONE.
他俩不协调就出现了跳动的问题。
package com.util.emotions;
import android.content.Context;
import android.text.Editable;
import android.util.AttributeSet;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import com.hang.emojidemo.R;
public class IMEBar extends LinearLayout implements OnClickListener,
OnEmojiClickListener {
public IMEBar(Context context) {
super(context);
init();
}
public IMEBar(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public IMEBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public void init() {
/*在构造方法中,IMEBar实例化尚未完成,下面的操作,必须延迟执行;new Thread? No
only the original thread that created a view hierarchy can touch its views
这个和只有UI线程才能修改UI,是一样的
通过下面的方法还是比较好的
*/
this.postDelayed(new Runnable() {
@Override
public void run() {
getEditText(IMEBar.this);
if (mEditText == null) {
throw new RuntimeException(
"must contain a EditText for putting content in");
}
mEditText.setOnClickListener(IMEBar.this);
setup();
}
}, 100);
}
/*通过递归的方法,获取EditText
*/
public void getEditText(ViewGroup viewGroup) {
for (int j = 0; j < viewGroup.getChildCount(); j++) {
View childView = viewGroup.getChildAt(j);
if (childView instanceof ViewGroup) {
getEditText((ViewGroup) childView);
} else {
if (childView instanceof EditText) {
mEditText = (EditText) childView;
}
}
}
}
private InputMethodManager mInputMethodManager;
private EmojiUtil mEmojiUtil;
private EditText mEditText;
/**
* 放置弹出内容的区域,需要一个id为fl_content的FrameLayout
*/
private FrameLayout mFrameContent;
/**
* 表情栏
*/
private EmojiPanel mEmotionsPanel;
private ImageView mSwitchView;
public void setup() throws RuntimeException {
/**
* 1.表情管理工具
* 2.表情容器
* 3.表情开关按钮
*/
// IME manager
mInputMethodManager = (InputMethodManager) getContext()
.getSystemService(Context.INPUT_METHOD_SERVICE);
// 表情管理
mEmojiUtil = EmojiUtil.getInstance(getContext().getApplicationContext());
// 放置弹出的容器
mFrameContent = (FrameLayout) findViewById(R.id.fl_content);
if (mFrameContent == null) {
throw new RuntimeException(
"must specify a FrameLayout with id \"fl_content\" for putting content in");
}
//进行keyboard&&icon 切换
mSwitchView = (ImageView) findViewById(R.id.iv_switch);
if (mSwitchView == null) {
throw new RuntimeException(
"must specify a View with id \"btn_emojis\"");
}
// bind listener
mSwitchView.setOnClickListener(this);
// 表情众面板
mEmotionsPanel = new EmojiPanel(getContext());
mEmotionsPanel.setOnEmotionClickListener(this);
// 面板加入布局中,并默认不可见
mEmotionsPanel.setVisibility(View.GONE);
mFrameContent.addView(mEmotionsPanel);
}
public void onShowEmotionsPanel() {
mEmotionsPanel.resetToFirstPage();
mEmotionsPanel.setVisibility(View.VISIBLE);
}
public boolean isEmotionsPanelShown() {
return mEmotionsPanel != null
&& mEmotionsPanel.getVisibility() == View.VISIBLE;
}
public void hideEmotionsPanel() {
mEmotionsPanel.setVisibility(View.GONE);
}
private void showKeyboard() {
if (mInputMethodManager != null && mEditText != null) {
mInputMethodManager.showSoftInput(mEditText,
InputMethodManager.SHOW_IMPLICIT);
}
}
private void hideSoftKeyInput() {
if (mInputMethodManager != null && mEditText != null) {
mInputMethodManager.hideSoftInputFromWindow(
mEditText.getApplicationWindowToken(), 0);
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.iv_switch: {
if (isEmotionsPanelShown()) {
hideEmotionsPanel();
showKeyboard();
mSwitchView.setImageResource(R.drawable.icon_icon);
} else {
hideSoftKeyInput();
onShowEmotionsPanel();
mSwitchView.setImageResource(R.drawable.keyboard);
}
break;
}
case R.id.et_input: {
// 点击输入框->隐藏表情
hideEmotionsPanel();
break;
}
default:
break;
}
}
@Override
public void onEmotionClick(final String emojiFile) {
if (mEditText == null) {
return;
}
if (EmojiUtil.BACKSPACE.equals(emojiFile)) {
Editable editable = mEditText.getText();
// 删除光标所在的前一个字符或表情
int index = mEditText.getSelectionStart();
if (index <= 0) {
// 光标在最前部,不需要删除
return;
}
char c = editable.charAt(index - 1);
if (']' == c) {
String text = editable.toString();
// 排除"[开心]A]"这种情况
int nextOpenBracket = text.lastIndexOf('[', index - 2);
int nextCloseBracket = text.lastIndexOf(']', index - 2);
if (nextCloseBracket < nextOpenBracket) {
// 删除一对儿
editable.delete(nextOpenBracket, index);
return;
}
}
// 正常删除
editable.delete(index - 1, index);
} else {
/*
* 点击了一个表情->在光标处插入表情
*/
// 将SpannableString插入到光标处
int index = mEditText.getSelectionStart();
mEditText.getText().insert(
index,
mEmojiUtil
.getSpannableByEmojiName(getContext(), emojiFile));
}
}
}
public class EmojiPanel extends FrameLayout {...}
键盘面板就是一个自定义组件,在里面放入ViewPager&&添加一个小圆点,和常规的APP首页的轮播图是一个显示效果(这里还不需要自动滚动)。
讲一下小难点:
1.如果咱们将emoji表情都放在assets下面的emojis文件夹下面,如何遍历所有的文件呢?
2.如果获取assets文件夹中的某一文件呢?
这就涉及到assets文件的读写,请参考
读取assets文件
3.在每一页表情的最后一个是——删除按钮!也就是在遍历assets/emojis文件夹中的图片时,要将其过滤掉,最后直接放到最后一个按钮上。在计算将有多少页表情的时候
//mEmojiUtils.getEmojiNameArray()是不包含删除按钮的所有图片对应的图片名
int pages = mEmojiUtils.getEmojiNameArray().length / (EMOJIS_PER_PAGE - 1);
return mEmojiUtils.getEmojiNameArray().length % (EMOJIS_PER_PAGE - 1) == 0 ? pages : pages + 1;
在对每一个页面内的表情进行赋值时
private String[] getArrayItems(int position) {
String[] emojiNameList = mEmojiUtils.getEmojiNameArray();
/* 判断当前页面要显示多少个表情
position * (EMOJIS_PER_PAGE - 1): 是前面所有的表情页面已经显示的表情数目
*/
String[] array = new String[Math.min(EMOJIS_PER_PAGE, emojiNameList.length - position * (EMOJIS_PER_PAGE - 1))];
for (int j = 0; j < array.length - 1; j++) {
array[j] = emojiNameList[(position * (EMOJIS_PER_PAGE - 1) + j)];
}
/*
array数组最后一个下标为 array.length - 1
对于最后一个面板并不是 EMOJIS_PER_PAGE-1
*/
array[array.length - 1] = EmojiUtil.BACKSPACE;
return array;
}
@Override
public void onEmotionClick(final String emojiFile) {
if (mEditText == null) {
return;
}
//通过文件名进行判断,如果为删除图标,执行删除操作
if (EmojiUtil.BACKSPACE.equals(emojiFile)) {
Editable editable = mEditText.getText();
// 删除光标所在的前一个字符或表情
int index = mEditText.getSelectionStart();
if (index <= 0) {
// 光标在最前部,不需要删除
return;
}
char c = editable.charAt(index - 1);
/*判断要删除的是不是表情
为了将用户的正常输入与咱们的表情区分开来,这里使用[]对表情进行了包含(eg.emoji_001对应在文本框中的文本为"[emoji_001]")
*/
if (']' == c) {
String text = editable.toString();
// 排除"[开心]A]"这种情况
int nextOpenBracket = text.lastIndexOf('[', index - 2);
int nextCloseBracket = text.lastIndexOf(']', index - 2);
if (nextCloseBracket < nextOpenBracket) {
// 删除一对儿
editable.delete(nextOpenBracket, index);
return;
}
}
//如果为正常文本,删除一个字符 editable.delete(index - 1, index);
} else {
/*
* 点击了一个表情->在光标处插入表情
*/
// 将SpannableString插入到光标处
int index = mEditText.getSelectionStart();
mEditText.getText().insert(
index,
mEmojiUtil
.getSpannableByEmojiName(getContext(), emojiFile));
}
}
这里不但要让用户点击的文件名添加到文本上,记得要给它添加包装——[],
/**
* 根据emoji名字(如:emj_001)
*/
public SpannableString getSpannableByEmojiName(Context context,
String iconName) {
if (iconName.contains("[") && (iconName.contains("]"))) {
iconName = iconName.substring(iconName.indexOf("[") + 1, iconName.indexOf("]"));
}
Bitmap emojiBmp = getEmojiIcon(iconName);
SpannableString ss = new SpannableString("[" + iconName + "]");
Drawable d = new BitmapDrawable(context.getResources(), emojiBmp);
int emojiSize =
Util.dip2px(context, 24);
d.setBounds(0, 0, emojiSize, emojiSize);
ImageSpan imgSpan = new ImageSpan(d);
ss.setSpan(imgSpan, 0, iconName.length() + 2,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return ss;
}
/**
* 将纯文本,转换成带表情的Spannable
*/
public SpannableStringBuilder convert(Context context, CharSequence text) {
if (TextUtils.isEmpty(text)) {
return null;
}
SpannableStringBuilder builder = new SpannableStringBuilder(text);
//通过正则表达式对emoji表情进行过滤
Pattern p = Pattern.compile("\\[\\S+?\\]");
Matcher m = p.matcher(builder);
while (m.find()) {
int start = m.start();
String emojiName = m.group();
// 用表情替换原名字
builder.replace(start, start + emojiName.length(),
getSpannableByEmojiName(context, emojiName));
}
return builder;
}
http://download.csdn.net/detail/guchuanhang/9564278