Android聊天软件的开发(六)--表情

   表情用于聊天对话的输入,实现的原理主要是:在EditText或TextView中,使用SpannableString,将特定字符串替换为图片。

   首先,我们可以规定,表情的字符串为[**],图片名称为smiley_i.png(i为图片编号,从0开始)。

   当我选择表情时,EditText的实际输入为[憨笑],但是可以通过SpannableString,将[憨笑]替换显示为。如下图,EditText的实际输入为“哈哈哈[憨笑][憨笑]”。


   同样,将“哈哈哈[憨笑][憨笑]”在TextView显示,只需要将所有的表情字符串[**]替换为对应的表情图片即可。

   注意:这里只有字符串--》表情的转换,不存在表情--》字符串的转换。


   具体实现:

   1. 表情字符串与图片资源的对应

   表情字符串存储在arrays.xml的smiley_values数组中,其位置下标与对应表情的图片编号一致。比如[憨笑]的数组下标为40,则其表情图片名为smiley_40.png。然后可以根据smiley_40.png获得该图片的资源ID,如0x00000001。我们只要将[憨笑]和0x00000001一一对应,就可以实现表情的显示。


   2. 自定义带表情输入的编辑框

Android聊天软件的开发(六)--表情_第1张图片

   FacialEditLayout.java:带表情输入的自定义编辑框布局。

public class FacialEditLayout extends LinearLayout implements
		OnItemClickListener, OnClickListener {

	private Context mContext;

	/** 发送按钮的监听事件 */
	private OnSendBtnClickListener mSendBtnListener;

	/** 显示表情页的viewpager */
	private ViewPager mFacialViewPager;
	/** 表情页界面集合 */
	private ArrayList mPageViews;
	/** 游标显示布局 */
	private LinearLayout mCursorLayout;
	/** 游标点集合 */
	private ArrayList mCursorViews;
	/** 表情集合 页+个 */
	private List> smileys;
	/** 表情区域 */
	private View mFacialLayout;
	/** 打开或关闭表情的按钮 */
	private ImageView mFacialEnable;
	/** 输入框 */
	private EditText mMsgEdit;
	/** 发送按钮 */
	private Button mSendBtn;
	/** 表情数据填充器 */
	private List mFacialAdapters;
	/** 当前表情页 */
	private int current = 0;
	/** 表情的列数 */
	private static final int COLUMNS = 7;
	/** 表情的行数 */
	private static final int ROWS = 3;

	/** 游标的宽高 */
	private static final int CURSOR_DIMEN = 12;
	/** 整个表情布局的高度,包含表情框,游标 */
	private int mFacialLayoutHeight;
	/** 表情ViewPager的高度 */
	private int mFacialViewPagerHeight;
	/** 每个表情的高度 */
	private int mFacialHeight;
	/** 每个表情的宽度 */
	private int mFacialWidth;

	public FacialEditLayout(Context context) {
		super(context);
		mContext = context;
		LayoutInflater.from(mContext)
				.inflate(R.layout.facial_edit_layout, this);
	}

	public FacialEditLayout(Context context, AttributeSet attrs) {
		super(context, attrs);
		mContext = context;
		LayoutInflater.from(mContext)
				.inflate(R.layout.facial_edit_layout, this);
	}

	/***************************** 对外接口 start *********************************/
	/** 发送按钮点击监听 */
	public interface OnSendBtnClickListener {
		/**
		 * @param input
		 *            编辑框的输入
		 */
		void onBtnClicked(String input);
	}

	/** 设置按钮点击监听器 */
	public void setOnSendBtnClickListener(OnSendBtnClickListener listener) {
		mSendBtnListener = listener;
	}

	/** 获得编辑框的输入字串 */
	public String getMsgEditTxt() {
		return mMsgEdit.getText().toString();
	}

	/** 清空编辑框 */
	public void clearMsgEdit() {
		mMsgEdit.setText("");
	}

	/**
	 * 判断表情框是否显示
* 一般用于重写返回按键时调用 */ public boolean isFacialShow() { return (mFacialLayout.getVisibility() == View.VISIBLE); } /** * 隐藏表情选择框
* 一般用于重写返回按键时调用 */ public void hideFacialView() { if (mFacialLayout.getVisibility() == View.VISIBLE) { mFacialEnable.setImageDrawable(getResources().getDrawable( R.drawable.facial_btn_normal)); mFacialLayout.setVisibility(View.GONE); } } /***************************** 对外接口 end *********************************/ @Override protected void onFinishInflate() { super.onFinishInflate(); onCreate(); } private void onCreate() { initView(); initViewPager(); initCursor(); initData(); } /** * 初始化控件 */ private void initView() { mFacialEnable = (ImageView) findViewById(R.id.facial_enable); mMsgEdit = (EditText) findViewById(R.id.facial_edit); mSendBtn = (Button) findViewById(R.id.facial_sendBtn); mFacialLayout = findViewById(R.id.facial_facialLayout); mFacialViewPager = (ViewPager) findViewById(R.id.facial_viewPager); mCursorLayout = (LinearLayout) findViewById(R.id.facial_cursor); mMsgEdit.setOnClickListener(this); mFacialEnable.setOnClickListener(this); mSendBtn.setOnClickListener(this); // 获得屏幕宽度 WindowManager wm = (WindowManager) getContext().getSystemService( Context.WINDOW_SERVICE); int screenWidth = wm.getDefaultDisplay().getWidth(); // 每个表情的宽高 mFacialWidth = screenWidth / COLUMNS - 1; mFacialHeight = mFacialWidth; // 表情框高度 mFacialViewPagerHeight = mFacialHeight * ROWS + 20; // 整个表情布局高度 mFacialLayoutHeight = mFacialViewPagerHeight + CURSOR_DIMEN + 30; RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) mFacialViewPager .getLayoutParams(); lp.height = mFacialViewPagerHeight; mFacialViewPager.setLayoutParams(lp); LinearLayout.LayoutParams lp1 = (LayoutParams) mFacialLayout .getLayoutParams(); lp1.height = mFacialLayoutHeight; mFacialLayout.setLayoutParams(lp1); smileys = FacialUtils.getInstace().getFacialData(); } /** * 初始化显示表情的viewpager */ private void initViewPager() { mPageViews = new ArrayList(); // 左侧添加空页 View nullView1 = new View(mContext); // 设置透明背景 nullView1.setBackgroundColor(Color.TRANSPARENT); mPageViews.add(nullView1); // 中间添加表情页 mFacialAdapters = new ArrayList(); for (int i = 0; i < smileys.size(); i++) { GridView view = new GridView(mContext); FacialAdapter adapter = new FacialAdapter(mContext, smileys.get(i), mFacialWidth, mFacialWidth); view.setAdapter(adapter); mFacialAdapters.add(adapter); view.setOnItemClickListener(this); view.setNumColumns(COLUMNS);// 列数 view.setBackgroundColor(Color.TRANSPARENT); view.setHorizontalSpacing(1); view.setVerticalSpacing(1); view.setStretchMode(GridView.STRETCH_COLUMN_WIDTH); view.setCacheColorHint(0); view.setPadding(5, 0, 5, 0); view.setSelector(new ColorDrawable(Color.TRANSPARENT)); view.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); view.setGravity(Gravity.CENTER); mPageViews.add(view); } // 右侧添加空页面 View nullView2 = new View(mContext); // 设置透明背景 nullView2.setBackgroundColor(Color.TRANSPARENT); mPageViews.add(nullView2); } /** * 初始化游标 */ private void initCursor() { mCursorViews = new ArrayList(); ImageView imageView; for (int i = 0; i < mPageViews.size(); i++) { imageView = new ImageView(mContext); imageView.setBackgroundResource(R.drawable.facial_cursor_normal); LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( new ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); layoutParams.leftMargin = 10; layoutParams.rightMargin = 10; layoutParams.width = CURSOR_DIMEN; layoutParams.height = CURSOR_DIMEN; mCursorLayout.addView(imageView, layoutParams); if (i == 0 || i == mPageViews.size() - 1) { imageView.setVisibility(View.GONE); } if (i == 1) { imageView .setBackgroundResource(R.drawable.facial_cursor_selected); } mCursorViews.add(imageView); } } /** * 填充数据 */ private void initData() { mFacialViewPager.setAdapter(new ViewPagerAdapter(mPageViews)); mFacialViewPager.setCurrentItem(1); current = 0; mFacialViewPager.setOnPageChangeListener(new OnPageChangeListener() { @Override public void onPageSelected(int position) { current = position - 1; // 描绘分页点 drawCursor(position); // 如果是第一屏或者是最后一屏禁止滑动,其实这里实现的是如果滑动的是第一屏则跳转至第二屏,如果是最后一屏则跳转到倒数第二屏. if (position == mCursorViews.size() - 1 || position == 0) { if (position == 0) { mFacialViewPager.setCurrentItem(position + 1);// 第二屏 // 会再次实现该回调方法实现跳转. mCursorViews.get(1).setBackgroundResource( R.drawable.facial_cursor_selected); } else { mFacialViewPager.setCurrentItem(position - 1);// 倒数第二屏 mCursorViews.get(position - 1).setBackgroundResource( R.drawable.facial_cursor_selected); } } } @Override public void onPageScrolled(int arg0, float arg1, int arg2) { } @Override public void onPageScrollStateChanged(int arg0) { } }); } /** * 绘制游标背景 */ private void drawCursor(int index) { for (int i = 1; i < mCursorViews.size(); i++) { if (index == i) { mCursorViews.get(i).setBackgroundResource( R.drawable.facial_cursor_selected); } else { mCursorViews.get(i).setBackgroundResource( R.drawable.facial_cursor_normal); } } } /** 隐藏软键盘 */ private void hideKeyBoard() { if (mContext != null) { ((InputMethodManager) mContext .getSystemService(Context.INPUT_METHOD_SERVICE)) .hideSoftInputFromWindow(mMsgEdit.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); } } @Override public void onClick(View v) { switch (v.getId()) { case R.id.facial_enable:// 表情选择 /* * 让编辑框同时获得焦点 否则,如果先点击表情框,需要两次点击编辑框,表情框才会消失。 * 因为第一次点击,让编辑框获得焦点,然后才能监听点击操作 */ mMsgEdit.setFocusable(true); mMsgEdit.setFocusableInTouchMode(true); mMsgEdit.requestFocus(); // 显示或隐藏表情选择框 if (mFacialLayout.getVisibility() == View.VISIBLE) { mFacialEnable.setImageDrawable(getResources().getDrawable( R.drawable.facial_btn_normal)); mFacialLayout.setVisibility(View.GONE); } else { mFacialEnable.setImageDrawable(getResources().getDrawable( R.drawable.facial_btn_enable)); // 隐藏软键盘 hideKeyBoard(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } mFacialLayout.setVisibility(View.VISIBLE); } break; case R.id.facial_edit:// 编辑框 // 隐藏表情选择框 if (mFacialLayout.getVisibility() == View.VISIBLE) { mFacialEnable.setImageDrawable(getResources().getDrawable( R.drawable.facial_btn_normal)); mFacialLayout.setVisibility(View.GONE); } break; case R.id.facial_sendBtn:// 发送按钮 String input = mMsgEdit.getText().toString(); if (input.isEmpty()) return; if (mSendBtnListener != null) { // 调用按钮监听 mSendBtnListener.onBtnClicked(input); } break; } } /** * 监听表情选择 */ @Override public void onItemClick(AdapterView arg0, View view, int position, long arg3) { SmileyFacial smiley = (SmileyFacial) mFacialAdapters.get(current) .getItem(position); if (smiley.getResId() == R.drawable.facial_del_selector) {// 删除按钮 int selection = mMsgEdit.getSelectionStart(); String text = mMsgEdit.getText().toString(); if (selection > 0) { String str = text.substring(selection - 1); if ("]".equals(str)) {// 光标以前的字符串中,最后一个是表情符 int start = text.lastIndexOf("["); int end = selection; mMsgEdit.getText().delete(start, end); } else// 正常字符 { mMsgEdit.getText().delete(selection - 1, selection); } } } if (!TextUtils.isEmpty(smiley.getName())) {// 选择表情 SpannableString spannableString = FacialUtils.getInstace() .addFacial(getContext(), smiley.getResId(), smiley.getName()); // 将表情显示在编辑框中 mMsgEdit.append(spannableString); } } }

   FacialUtil.java:初始化数据,和添加,显示表情的工具类

public class FacialUtils {
	/** 每一页表情的个数,不包含最后一个删除键 */
	private int pageSize = 20;

	private static FacialUtils mFacialUtil;

	/** <表情对应字符串,资源ID> 比如:<[哈哈],0x7f040000> */
	private HashMap smileyMap = new HashMap();

	/** 表情集合 */
	private List smileys = new ArrayList();

	/** 表情分页的结果集合 */
	private List> smileyLists = new ArrayList>();

	private FacialUtils() {
	}

	public static FacialUtils getInstace() {
		if (mFacialUtil == null) {
			mFacialUtil = new FacialUtils();
		}
		return mFacialUtil;
	}

	/**
	 * 初始化表情和对应字符串
	 */
	public void InitFacialData(Context context) {
		// 得到所有表情的字符串
		String[] data = context.getResources().getStringArray(
				R.array.smiley_values);
		if (data == null) {
			return;
		}
		SmileyFacial smileyEentry;
		try {
			for (int i = 0; i < data.length; i++) {
				String pngName = "smiley_" + i;
				// 根据图片名获得资源ID
				int resID = context.getResources().getIdentifier(pngName,
						"drawable", context.getPackageName());
				smileyMap.put(data[i], resID);

				if (resID != 0) {
					smileyEentry = new SmileyFacial();
					smileyEentry.setResId(resID);
					smileyEentry.setName(data[i]);
					smileys.add(smileyEentry);
				}
			}
			// 表情页的页数,向上取整
			int pageCount = (int) Math.ceil(smileys.size() / pageSize + 0.1);

			for (int i = 0; i < pageCount; i++) {
				smileyLists.add(getPageFacials(i));
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 获得分页后的表情集合
	 */
	public List> getFacialData() {
		return smileyLists;
	}

	/**
	 * 将字符串进行正则匹配,用于显示表情
	 * 
	 * @param str
	 *            含有[**]表情标识的字串
	 * @return 可通过TextView或EditText的setText方法直接显示表情的SpannableString
	 */
	public SpannableString showFacial(Context context, String str) {
		SpannableString spannableString = new SpannableString(str);
		// 正则表达式比配字符串里是否含有表情,如: 我好[开心]啊
		String regex = "\\[[^\\]]+\\]";
		// 通过传入的正则表达式来生成一个pattern
		Pattern sinaPatten = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
		try {
			dealExpression(context, spannableString, sinaPatten, 0);
		} catch (Exception e) {
			Log.e("dealExpression", e.getMessage());
		}
		return spannableString;
	}

	/**
	 * 添加表情
	 */
	public SpannableString addFacial(Context context, int imgId, String str) {
		if (TextUtils.isEmpty(str)) {
			return null;
		}
		Drawable drawable = context.getResources().getDrawable(imgId);
		drawable.setBounds(0, 0, 30, 30);
		ImageSpan imageSpan = new ImageSpan(drawable);
		SpannableString spannable = new SpannableString(str);
		spannable.setSpan(imageSpan, 0, str.length(),
				Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
		return spannable;
	}

	/**
	 * 对spanableString进行正则判断,如果符合要求,则以表情图片代替
	 */
	private void dealExpression(Context context,
			SpannableString spannableString, Pattern patten, int start)
			throws Exception {
		Matcher matcher = patten.matcher(spannableString);
		while (matcher.find()) {
			String key = matcher.group();
			// 返回第一个字符的索引的文本匹配整个正则表达式,ture 则继续递归
			if (matcher.start() < start) {
				continue;
			}
			int resId = smileyMap.get(key);
			if (resId != 0) {
				Drawable drawable = context.getResources().getDrawable(resId);
				drawable.setBounds(0, 0, 30, 30);
				// 通过图片资源id来得到drawable,用一个ImageSpan来包装
				ImageSpan imageSpan = new ImageSpan(drawable);
				// 计算该图片名字的长度,也就是要替换的字符串的长度
				int end = matcher.start() + key.length();
				// 将该图片替换字符串中规定的位置中
				spannableString.setSpan(imageSpan, matcher.start(), end,
						Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
				if (end < spannableString.length()) {
					// 如果整个字符串还未验证完,则继续。。
					dealExpression(context, spannableString, patten, end);
				}
				break;
			}
		}
	}

	/**
	 * 获取分页数据
	 */
	private List getPageFacials(int page) {
		int startIndex = page * pageSize;
		int endIndex = startIndex + pageSize;

		if (endIndex > smileys.size()) {
			endIndex = smileys.size();
		}
		List list = new ArrayList();
		list.addAll(smileys.subList(startIndex, endIndex));
		if (list.size() < pageSize) {
			// 填充空白区域
			for (int i = list.size(); i < pageSize; i++) {
				SmileyFacial object = new SmileyFacial();
				list.add(object);
			}
		}
		if (list.size() == pageSize) {
			// 最后一个为删除键
			SmileyFacial object = new SmileyFacial();
			object.setResId(R.drawable.facial_del_selector);
			list.add(object);
		}
		return list;
	}
}

   facial_edit_layout.xml:自定义View的布局文件




    

        

        

        


   3. 表情输入框的使用
   在聊天界面中,需要使用表情输入框。使用比较简单,只要实现发送按钮的点击函数(setOnSendBtnClickListener)即可。同时,可以使用getMsgEditTxt获得消息内容,clearMsgEdit清空文本,isFacialShow判断表情框是否显示,以及hideFacialView隐藏表情框。

   ChattingActivity.java:聊天界面Activity。主要贴出使用表情输入框的代码。

public class ChattingActivity extends ActionBarActivity{
	private ListView mListView;
	private FacialEditLayout mFacialEditLayout;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.layout_chatting);
		
		findViews();
		setListener();
	}
	
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		if(keyCode == KeyEvent.KEYCODE_BACK)
		{
			if(mFacialEditLayout.isFacialShow())
			{
				//隐藏表情框
				mFacialEditLayout.hideFacialView();
				return true;
			}
		}
		return super.onKeyDown(keyCode, event);
	}

	private void setListener()
	{
		//点击聊天消息的界面,隐藏键盘或表情框
		mListView.setOnTouchListener(new View.OnTouchListener() {
			@Override
			public boolean onTouch(View v, MotionEvent event) {
				hideKeyBoard();
				mFacialEditLayout.hideFacialView();
				return false;
			}
		});
		
		//设置发送按钮点击监听
		mFacialEditLayout.setOnSendBtnClickListener(new FacialEditLayout.OnSendBtnClickListener() {
			@Override
			public void onBtnClicked(String input) {
				//清空编辑框
				mFacialEditLayout.clearMsgEdit();
				
				//发送消息
			}
		});
		
	}

	private void findViews()
	{
		mListView = (ListView) findViewById(R.id.chatting_listView);
		mFacialEditLayout = (FacialEditLayout) findViewById(R.id.chatting_facialLayout);
	}
	
	/** 隐藏软键盘 */
	private void hideKeyBoard() {
		((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE))
				.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(),
						InputMethodManager.HIDE_NOT_ALWAYS);
	}
}


参考网址:http://blog.csdn.net/lnb333666/article/details/8546497


首页Android聊天软件的开发



   

你可能感兴趣的:(Android软件开发)