您的位置:首页 > 移动开发 > Android开发

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

2014-06-20 17:49 686 查看
表情用于聊天对话的输入,实现的原理主要是:在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. 自定义带表情输入的编辑框



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

public class FacialEditLayout extends LinearLayout implements
		OnItemClickListener, OnClickListener {

	private Context mContext;

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

	/** 显示表情页的viewpager */
	private ViewPager mFacialViewPager;
	/** 表情页界面集合 */
	private ArrayList<View> mPageViews;
	/** 游标显示布局 */
	private LinearLayout mCursorLayout;
	/** 游标点集合 */
	private ArrayList<ImageView> mCursorViews;
	/** 表情集合 页+个 */
	private List<List<SmileyFacial>> smileys;
	/** 表情区域 */
	private View mFacialLayout;
	/** 打开或关闭表情的按钮 */
	private ImageView mFacialEnable;
	/** 输入框 */
	private EditText mMsgEdit;
	/** 发送按钮 */
	private Button mSendBtn;
	/** 表情数据填充器 */
	private List<FacialAdapter> 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("");
	}

	/**
	 * 判断表情框是否显示<BR/>
	 * 一般用于重写返回按键时调用
	 */
	public boolean isFacialShow() {
		return (mFacialLayout.getVisibility() == View.VISIBLE);
	}

	/**
	 * 隐藏表情选择框<BR/>
	 * 一般用于重写返回按键时调用
	 */
	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>();
		// 左侧添加空页
		View nullView1 = new View(mContext);
		// 设置透明背景
		nullView1.setBackgroundColor(Color.TRANSPARENT);
		mPageViews.add(nullView1);

		// 中间添加表情页
		mFacialAdapters = new ArrayList<FacialAdapter>();
		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 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<String, Integer> smileyMap = new HashMap<String, Integer>();

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

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

	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<List<SmileyFacial>> 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<SmileyFacial> getPageFacials(int page) {
		int startIndex = page * pageSize;
		int endIndex = startIndex + pageSize;

		if (endIndex > smileys.size()) {
			endIndex = smileys.size();
		}
		List<SmileyFacial> list = new ArrayList<SmileyFacial>();
		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的布局文件

<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/facialEditLayout"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical" >

    <RelativeLayout
        android:id="@+id/facial_editLayout"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/chatting_bottom_bg"
        android:padding="5dp" >

        <ImageView
            android:id="@+id/facial_enable"
            android:layout_width="35dp"
            android:layout_height="35dp"
            android:layout_alignBottom="@+id/facial_edit"
            android:layout_alignParentLeft="true"
            android:padding="5dp"
            android:src="@drawable/facial_btn_normal" />

        <EditText
            android:id="@+id/facial_edit"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:layout_toLeftOf="@+id/facial_sendBtn"
            android:layout_toRightOf="@+id/facial_enable"
            android:background="@drawable/facial_edit_selector"
            android:inputType="textMultiLine"
            android:maxLines="3"
            android:textSize="16sp" />

        <Button
            android:id="@+id/facial_sendBtn"
            android:layout_width="50dp"
            android:layout_height="35dp"
            android:layout_alignBottom="@+id/facial_edit"
            android:layout_alignParentRight="true"
            android:background="@drawable/facial_send_btn_selector"
            android:text="@string/btn_send" />
    </RelativeLayout>
    <!-- 表情区域 -->

    <RelativeLayout
        android:id="@+id/facial_facialLayout"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/facial_editLayout"
        android:visibility="gone"
        android:windowSoftInputMode="adjustPan" >

        <android.support.v4.view.ViewPager
            android:id="@+id/facial_viewPager"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_above="@+id/facial_cursor"
            android:layout_marginTop="10dp" >
        </android.support.v4.view.ViewPager>

        <LinearLayout
            android:id="@+id/facial_cursor"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_marginBottom="10dp"
            android:layout_marginTop="5dp"
            android:gravity="center"
            android:orientation="horizontal" />
    </RelativeLayout>

</LinearLayout>


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聊天软件的开发
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: