默认
打赏 发表评论 4
想开发IM:买成品怕坑?租第3方怕贵?找开源自已撸?尽量别走弯路了... 找站长给点建议
Android聊天界面源码:实现了聊天气泡、表情图标(可翻页) [附件下载]
阅读(103099) | 评论(4 收藏8 淘帖2
微信扫一扫关注!

界面效果预览


如题,这是公司项目的一个功能模块,先上个效果图:

Android聊天界面源码:实现了聊天气泡、表情图标(可翻页) [附件下载]_1359275272_5039.png

其次大致说说原理


首先判断输入的字符,是否包含表情的文字:
比如 ^V^ 这个表情对应的文件名为 emoji_1.png,它对应的文字描述 : [可爱],如果我们在输出的是输出这么一句话:老婆,我想你了。  那么我们对应的根本文字就是:老婆,我想你了[可爱]。

具体的转换过程就是用正则表达式比配文字中是否含有[xxx]这类的文字:
如果有,那么我们就根据拿到的[xxx]找到它对应的资源文件id,当然这其中有一个关系表,看你怎么处理这个关系了。最后将其用SpannableString替换成文字,表面上显示有图片,其实TextView里的text依然是:老婆,我想你了[可爱]。这个过程明白么?

Demo工程的结构


Android聊天界面源码:实现了聊天气泡、表情图标(可翻页) [附件下载]_1359274178_6305.jpg

主要代码摘录


package com.example.facedemo;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.ImageSpan;
import android.util.Log;

/**
 * 
 ****************************************** 
 * @author 廖乃波
 * @文件名称 : FaceConversionUtil.java
 * @创建时间 : 2013-1-27 下午02:34:09
 * @文件描述 : 表情轉換工具
 ****************************************** 
 */
public class FaceConversionUtil {

        /** 每一页表情的个数 */
        private int pageSize = 20;

        private static FaceConversionUtil mFaceConversionUtil;

        /** 保存于内存中的表情HashMap */
        private HashMap<String, String> emojiMap = new HashMap<String, String>();

        /** 保存于内存中的表情集合 */
        private List<ChatEmoji> emojis = new ArrayList<ChatEmoji>();

        /** 表情分页的结果集合 */
        public List<List<ChatEmoji>> emojiLists = new ArrayList<List<ChatEmoji>>();

        private FaceConversionUtil() {

        }

        public static FaceConversionUtil getInstace() {
                if (mFaceConversionUtil == null) {
                        mFaceConversionUtil = new FaceConversionUtil();
                }
                return mFaceConversionUtil;
        }

        /**
         * 得到一个SpanableString对象,通过传入的字符串,并进行正则判断
         * 
         * @param context
         * @param str
         * @return
         */
        public SpannableString getExpressionString(Context context, String str) {
                SpannableString spannableString = new SpannableString(str);
                // 正则表达式比配字符串里是否含有表情,如: 我好[开心]啊
                String zhengze = "\\[[^\\]]+\\]";
                // 通过传入的正则表达式来生成一个pattern
                Pattern sinaPatten = Pattern.compile(zhengze, Pattern.CASE_INSENSITIVE);
                try {
                        dealExpression(context, spannableString, sinaPatten, 0);
                } catch (Exception e) {
                        Log.e("dealExpression", e.getMessage());
                }
                return spannableString;
        }

        /**
         * 添加表情
         * 
         * @param context
         * @param imgId
         * @param spannableString
         * @return
         */
        public SpannableString addFace(Context context, int imgId,
                        String spannableString) {
                if (TextUtils.isEmpty(spannableString)) {
                        return null;
                }
                Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(),
                                imgId);
                bitmap = Bitmap.createScaledBitmap(bitmap, 35, 35, true);
                ImageSpan imageSpan = new ImageSpan(context, bitmap);
                SpannableString spannable = new SpannableString(spannableString);
                spannable.setSpan(imageSpan, 0, spannableString.length(),
                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                return spannable;
        }

        /**
         * 对spanableString进行正则判断,如果符合要求,则以表情图片代替
         * 
         * @param context
         * @param spannableString
         * @param patten
         * @param start
         * @throws Exception
         */
        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;
                        }
                        String value = emojiMap.get(key);
                        if (TextUtils.isEmpty(value)) {
                                continue;
                        }
                        int resId = context.getResources().getIdentifier(value, "drawable",
                                        context.getPackageName());
                        // 通过上面匹配得到的字符串来生成图片资源id,下边的方法可用,但是你工程混淆的时候就有事了,你懂的。不是我介绍的重点
                        // Field field=R.drawable.class.getDeclaredField(value);
                        // int resId=Integer.parseInt(field.get(null).toString());
                        if (resId != 0) {
                                Bitmap bitmap = BitmapFactory.decodeResource(
                                                context.getResources(), resId);
                                bitmap = Bitmap.createScaledBitmap(bitmap, 50, 50, true);
                                // 通过图片资源id来得到bitmap,用一个ImageSpan来包装
                                ImageSpan imageSpan = new ImageSpan(bitmap);
                                // 计算该图片名字的长度,也就是要替换的字符串的长度
                                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;
                        }
                }
        }

        public void getFileText(Context context) {
                ParseData(FileUtils.getEmojiFile(context), context);
        }

        /**
         * 解析字符
         * 
         * @param data
         */
        private void ParseData(List<String> data, Context context) {
                if (data == null) {
                        return;
                }
                ChatEmoji emojEentry;
                try {
                        for (String str : data) {
                                String[] text = str.split(",");
                                String fileName = text[0]
                                                .substring(0, text[0].lastIndexOf("."));
                                emojiMap.put(text[1], fileName);
                                int resID = context.getResources().getIdentifier(fileName,
                                                "drawable", context.getPackageName());

                                if (resID != 0) {
                                        emojEentry = new ChatEmoji();
                                        emojEentry.setId(resID);
                                        emojEentry.setCharacter(text[1]);
                                        emojEentry.setFaceName(fileName);
                                        emojis.add(emojEentry);
                                }
                        }
                        int pageCount = (int) Math.ceil(emojis.size() / 20 + 0.1);

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

        /**
         * 获取分页数据
         * 
         * @param page
         * @return
         */
        private List<ChatEmoji> getData(int page) {
                int startIndex = page * pageSize;
                int endIndex = startIndex + pageSize;

                if (endIndex > emojis.size()) {
                        endIndex = emojis.size();
                }
                // 不这么写,会在viewpager加载中报集合操作异常,我也不知道为什么
                List<ChatEmoji> list = new ArrayList<ChatEmoji>();
                list.addAll(emojis.subList(startIndex, endIndex));
                if (list.size() < pageSize) {
                        for (int i = list.size(); i < pageSize; i++) {
                                ChatEmoji object = new ChatEmoji();
                                list.add(object);
                        }
                }
                if (list.size() == pageSize) {
                        ChatEmoji object = new ChatEmoji();
                        object.setId(R.drawable.face_del_icon);
                        list.add(object);
                }
                return list;
        }
}

下边是表情布局,带输入框的,这样可以多个地方使用,就不不会使用太多多余代码:
package com.example.facedemo;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.text.SpannableString;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.EditText;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;

/**
 * 
 ******************************************
 * @author 廖乃波
 * @文件名称        :  FaceRelativeLayout.java
 * @创建时间        : 2013-1-27 下午02:34:17
 * @文件描述        : 带表情的自定义输入框
 ******************************************
 */
public class FaceRelativeLayout extends RelativeLayout implements
                OnItemClickListener, OnClickListener {

        private Context context;

        /** 表情页的监听事件 */
        private OnCorpusSelectedListener mListener;

        /** 显示表情页的viewpager */
        private ViewPager vp_face;

        /** 表情页界面集合 */
        private ArrayList<View> pageViews;

        /** 游标显示布局 */
        private LinearLayout layout_point;

        /** 游标点集合 */
        private ArrayList<ImageView> pointViews;

        /** 表情集合 */
        private List<List<ChatEmoji>> emojis;

        /** 表情区域 */
        private View view;

        /** 输入框 */
        private EditText et_sendmessage;

        /** 表情数据填充器 */
        private List<FaceAdapter> faceAdapters;

        /** 当前表情页 */
        private int current = 0;

        public FaceRelativeLayout(Context context) {
                super(context);
                this.context = context;
        }

        public FaceRelativeLayout(Context context, AttributeSet attrs) {
                super(context, attrs);
                this.context = context;
        }

        public FaceRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
                super(context, attrs, defStyle);
                this.context = context;
        }

        public void setOnCorpusSelectedListener(OnCorpusSelectedListener listener) {
                mListener = listener;
        }

        /**
         * 表情选择监听
         * 
         * @author naibo-liao
         * @时间: 2013-1-15下午04:32:54
         */
        public interface OnCorpusSelectedListener {

                void onCorpusSelected(ChatEmoji emoji);

                void onCorpusDeleted();
        }

        @Override
        protected void onFinishInflate() {
                super.onFinishInflate();
                emojis = FaceConversionUtil.getInstace().emojiLists;
                onCreate();
        }

        private void onCreate() {
                Init_View();
                Init_viewPager();
                Init_Point();
                Init_Data();
        }

        @Override
        public void onClick(View v) {
                switch (v.getId()) {
                case R.id.btn_face:
                        // 隐藏表情选择框
                        if (view.getVisibility() == View.VISIBLE) {
                                view.setVisibility(View.GONE);
                        } else {
                                view.setVisibility(View.VISIBLE);
                        }
                        break;
                case R.id.et_sendmessage:
                        // 隐藏表情选择框
                        if (view.getVisibility() == View.VISIBLE) {
                                view.setVisibility(View.GONE);
                        }
                        break;

                }
        }

        /**
         * 隐藏表情选择框
         */
        public boolean hideFaceView() {
                // 隐藏表情选择框
                if (view.getVisibility() == View.VISIBLE) {
                        view.setVisibility(View.GONE);
                        return true;
                }
                return false;
        }

        /**
         * 初始化控件
         */
        private void Init_View() {
                vp_face = (ViewPager) findViewById(R.id.vp_contains);
                et_sendmessage = (EditText) findViewById(R.id.et_sendmessage);
                layout_point = (LinearLayout) findViewById(R.id.iv_image);
                et_sendmessage.setOnClickListener(this);
                findViewById(R.id.btn_face).setOnClickListener(this);
                view = findViewById(R.id.ll_facechoose);

        }

        /**
         * 初始化显示表情的viewpager
         */
        private void Init_viewPager() {
                pageViews = new ArrayList<View>();
                // 左侧添加空页
                View nullView1 = new View(context);
                // 设置透明背景
                nullView1.setBackgroundColor(Color.TRANSPARENT);
                pageViews.add(nullView1);

                // 中间添加表情页

                faceAdapters = new ArrayList<FaceAdapter>();
                for (int i = 0; i < emojis.size(); i++) {
                        GridView view = new GridView(context);
                        FaceAdapter adapter = new FaceAdapter(context, emojis.get(i));
                        view.setAdapter(adapter);
                        faceAdapters.add(adapter);
                        view.setOnItemClickListener(this);
                        view.setNumColumns(7);
                        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);
                        pageViews.add(view);
                }

                // 右侧添加空页面
                View nullView2 = new View(context);
                // 设置透明背景
                nullView2.setBackgroundColor(Color.TRANSPARENT);
                pageViews.add(nullView2);
        }

        /**
         * 初始化游标
         */
        private void Init_Point() {

                pointViews = new ArrayList<ImageView>();
                ImageView imageView;
                for (int i = 0; i < pageViews.size(); i++) {
                        imageView = new ImageView(context);
                        imageView.setBackgroundResource(R.drawable.d1);
                        LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
                                        new ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT,
                                                        LayoutParams.WRAP_CONTENT));
                        layoutParams.leftMargin = 10;
                        layoutParams.rightMargin = 10;
                        layoutParams.width = 8;
                        layoutParams.height = 8;
                        layout_point.addView(imageView, layoutParams);
                        if (i == 0 || i == pageViews.size() - 1) {
                                imageView.setVisibility(View.GONE);
                        }
                        if (i == 1) {
                                imageView.setBackgroundResource(R.drawable.d2);
                        }
                        pointViews.add(imageView);

                }
        }

        /**
         * 填充数据
         */
        private void Init_Data() {
                vp_face.setAdapter(new ViewPagerAdapter(pageViews));

                vp_face.setCurrentItem(1);
                current = 0;
                vp_face.setOnPageChangeListener(new OnPageChangeListener() {

                        @Override
                        public void onPageSelected(int arg0) {
                                current = arg0 - 1;
                                // 描绘分页点
                                draw_Point(arg0);
                                // 如果是第一屏或者是最后一屏禁止滑动,其实这里实现的是如果滑动的是第一屏则跳转至第二屏,如果是最后一屏则跳转到倒数第二屏.
                                if (arg0 == pointViews.size() - 1 || arg0 == 0) {
                                        if (arg0 == 0) {
                                                vp_face.setCurrentItem(arg0 + 1);// 第二屏 会再次实现该回调方法实现跳转.
                                                pointViews.get(1).setBackgroundResource(R.drawable.d2);
                                        } else {
                                                vp_face.setCurrentItem(arg0 - 1);// 倒数第二屏
                                                pointViews.get(arg0 - 1).setBackgroundResource(
                                                                R.drawable.d2);
                                        }
                                }
                        }

                        @Override
                        public void onPageScrolled(int arg0, float arg1, int arg2) {

                        }

                        @Override
                        public void onPageScrollStateChanged(int arg0) {

                        }
                });

        }

        /**
         * 绘制游标背景
         */
        public void draw_Point(int index) {
                for (int i = 1; i < pointViews.size(); i++) {
                        if (index == i) {
                                pointViews.get(i).setBackgroundResource(R.drawable.d2);
                        } else {
                                pointViews.get(i).setBackgroundResource(R.drawable.d1);
                        }
                }
        }

        @Override
        public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
                ChatEmoji emoji = (ChatEmoji) faceAdapters.get(current).getItem(arg2);
                if (emoji.getId() == R.drawable.face_del_icon) {
                        int selection = et_sendmessage.getSelectionStart();
                        String text = et_sendmessage.getText().toString();
                        if (selection > 0) {
                                String text2 = text.substring(selection - 1);
                                if ("]".equals(text2)) {
                                        int start = text.lastIndexOf("[");
                                        int end = selection;
                                        et_sendmessage.getText().delete(start, end);
                                        return;
                                }
                                et_sendmessage.getText().delete(selection - 1, selection);
                        }
                }
                if (!TextUtils.isEmpty(emoji.getCharacter())) {
                        if (mListener != null)
                                mListener.onCorpusSelected(emoji);
                        SpannableString spannableString = FaceConversionUtil.getInstace()
                                        .addFace(getContext(), emoji.getId(), emoji.getCharacter());
                        et_sendmessage.append(spannableString);
                }

        }
}

接下来是聊天数据填充器的:
package com.example.facedemo;

import android.content.Context;

import android.text.SpannableString;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import android.widget.BaseAdapter;
import android.widget.TextView;

import java.util.List;

/**
 * 
 ******************************************
 * @author 廖乃波
 * @文件名称        :  ChatMsgAdapter.java
 * @创建时间        : 2013-1-27 下午02:33:16
 * @文件描述        : 消息数据填充起
 ******************************************
 */
public class ChatMsgAdapter extends BaseAdapter {

        public static interface IMsgViewType {
                int IMVT_COM_MSG = 0;
                int IMVT_TO_MSG = 1;
        }

        private List<ChatMsgEntity> coll;
        private LayoutInflater mInflater;
        private Context context;
        public ChatMsgAdapter(Context context, List<ChatMsgEntity> coll) {
                this.coll = coll;
                mInflater = LayoutInflater.from(context);
                this.context = context;
        }

        public int getCount() {
                return coll.size();
        }

        public Object getItem(int position) {
                return coll.get(position);
        }

        public long getItemId(int position) {
                return position;
        }

        public int getItemViewType(int position) {
                ChatMsgEntity entity = coll.get(position);

                if (entity.getMsgType()) {
                        return IMsgViewType.IMVT_COM_MSG;
                } else {
                        return IMsgViewType.IMVT_TO_MSG;
                }

        }

        public int getViewTypeCount() {
                return 2;
        }

        public View getView(int position, View convertView, ViewGroup parent) {

                ChatMsgEntity entity = coll.get(position);
                boolean isComMsg = entity.getMsgType();

                ViewHolder viewHolder = null;
                if (convertView == null) {
                        if (isComMsg) {
                                convertView = mInflater.inflate(
                                                R.layout.chatting_item_msg_text_left, null);
                        } else {
                                convertView = mInflater.inflate(
                                                R.layout.chatting_item_msg_text_right, null);
                        }

                        viewHolder = new ViewHolder();
                        viewHolder.tvSendTime = (TextView) convertView
                                        .findViewById(R.id.tv_sendtime);
                        viewHolder.tvContent = (TextView) convertView
                                        .findViewById(R.id.tv_chatcontent);
                        viewHolder.isComMsg = isComMsg;

                        convertView.setTag(viewHolder);
                } else {
                        viewHolder = (ViewHolder) convertView.getTag();
                }

                viewHolder.tvSendTime.setText(entity.getDate());
                SpannableString spannableString = FaceConversionUtil.getInstace().getExpressionString(context, entity.getText());
                viewHolder.tvContent.setText(spannableString);

                return convertView;
        }

        class ViewHolder {
                public TextView tvSendTime;
                public TextView tvContent;
                public boolean isComMsg = true;
        }
}

最开始要读取的表情配置文件:
package com.example.facedemo;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

import android.content.Context;

/**
 * 
 ******************************************
 * @author 廖乃波
 * @文件名称        :  FileUtils.java
 * @创建时间        : 2013-1-27 下午02:35:09
 * @文件描述        : 文件工具类
 ******************************************
 */
public class FileUtils {
        /**
         * 读取表情配置文件
         * 
         * @param context
         * @return
         */
        public static List<String> getEmojiFile(Context context) {
                try {
                        List<String> list = new ArrayList<String>();
                        InputStream in = context.getResources().getAssets().open("emoji");
                        BufferedReader br = new BufferedReader(new InputStreamReader(in,
                                        "UTF-8"));
                        String str = null;
                        while ((str = br.readLine()) != null) {
                                list.add(str);
                        }

                        return list;
                } catch (IOException e) {
                        e.printStackTrace();
                }
                return null;
        }
}

下边这个是表情翻页的数据填充,用的是viewpager,每一页填充的是一个gridview:
package com.example.facedemo;

import java.util.List;

import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.View;
/**
 * 
 ******************************************
 * @author 廖乃波
 * @文件名称        :  ViewPagerAdapter.java
 * @创建时间        : 2013-1-27 下午02:35:27
 * @文件描述        : ViewPager 数据填充器,切记做其他操作!!!只填充View!!!!
 ******************************************
 */
public class ViewPagerAdapter extends PagerAdapter {

    private List<View> pageViews;

    public ViewPagerAdapter(List<View> pageViews) {
        super();
        this.pageViews=pageViews;
    }

    // 显示数目
    @Override
    public int getCount() {
        return pageViews.size();
    }

    @Override
    public boolean isViewFromObject(View arg0, Object arg1) {
        return arg0 == arg1;
    }

    @Override
    public int getItemPosition(Object object) {
        return super.getItemPosition(object);
    }

    @Override
    public void destroyItem(View arg0, int arg1, Object arg2) {
        ((ViewPager)arg0).removeView(pageViews.get(arg1));
    }

    /***
     * 获取每一个item 类于listview中的getview
     */
    @Override
    public Object instantiateItem(View arg0, int arg1) {
        ((ViewPager)arg0).addView(pageViews.get(arg1));
        return pageViews.get(arg1);
    }
}

最后呢,是表情的配置文件,你想怎么搞都行,我就这么搞的:
emoji_1.png,[可爱]
emoji_2.png,[笑脸]
emoji_3.png,[囧]
emoji_4.png,[生气]
emoji_5.png,[鬼脸]
emoji_6.png,[花心]
emoji_7.png,[害怕]
emoji_8.png,[我汗]
emoji_9.png,[尴尬]
emoji_10.png,[哼哼]
emoji_11.png,[忧郁]
emoji_12.png,[呲牙]
emoji_13.png,[媚眼]
emoji_14.png,[累]
emoji_15.png,[苦逼]
emoji_16.png,[瞌睡]
emoji_17.png,[哎呀]
emoji_18.png,[刺瞎]
emoji_19.png,[哭]
emoji_20.png,[激动]
emoji_21.png,[难过]
emoji_22.png,[害羞]
emoji_23.png,[高兴]
emoji_24.png,[愤怒]
emoji_25.png,[亲]
emoji_26.png,[飞吻]
emoji_27.png,[得意]
emoji_28.png,[惊恐]
emoji_29.png,[口罩]
emoji_30.png,[惊讶]
emoji_31.png,[委屈]
emoji_32.png,[生病]
emoji_33.png,[红心]
emoji_34.png,[心碎]
emoji_35.png,[玫瑰]
emoji_36.png,[花]
emoji_37.png,[外星人]
emoji_38.png,[金牛座]
emoji_39.png,[双子座]
emoji_40.png,[巨蟹座]
emoji_41.png,[狮子座]
emoji_42.png,[处女座]
emoji_43.png,[天平座]
emoji_44.png,[天蝎座]
emoji_45.png,[射手座]
emoji_46.png,[摩羯座]
emoji_47.png,[水瓶座]
emoji_48.png,[白羊座]
emoji_49.png,[双鱼座]
emoji_50.png,[星座]
emoji_51.png,[男孩]
emoji_52.png,[女孩]
emoji_53.png,[嘴唇]
emoji_54.png,[爸爸]
emoji_55.png,[妈妈]
emoji_56.png,[衣服]
emoji_57.png,[皮鞋]
emoji_58.png,[照相]
emoji_59.png,[电话]
emoji_60.png,[石头]
emoji_61.png,[胜利]
emoji_62.png,[禁止]
emoji_63.png,[滑雪]
emoji_64.png,[高尔夫]
emoji_65.png,[网球]
emoji_66.png,[棒球]
emoji_67.png,[冲浪]
emoji_68.png,[足球]
emoji_69.png,[小鱼]
emoji_70.png,[问号]
emoji_71.png,[叹号]
emoji_179.png,[顶]
emoji_180.png,[写字]
emoji_181.png,[衬衫]
emoji_182.png,[小花]
emoji_183.png,[郁金香]
emoji_184.png,[向日葵]
emoji_185.png,[鲜花]
emoji_186.png,[椰树]
emoji_187.png,[仙人掌]
emoji_188.png,[气球]
emoji_189.png,[炸弹]
emoji_190.png,[喝彩]
emoji_191.png,[剪子]
emoji_192.png,[蝴蝶结]
emoji_193.png,[机密]
emoji_194.png,[铃声]
emoji_195.png,[女帽]
emoji_196.png,[裙子]
emoji_197.png,[理发店]
emoji_198.png,[和服]
emoji_199.png,[比基尼]
emoji_200.png,[拎包]
emoji_201.png,[拍摄]
emoji_202.png,[铃铛]
emoji_203.png,[音乐]
emoji_204.png,[心星]
emoji_205.png,[粉心]
emoji_206.png,[丘比特]
emoji_207.png,[吹气]
emoji_208.png,[口水]
emoji_209.png,[对]
emoji_210.png,[错]
emoji_211.png,[绿茶]
emoji_212.png,[面包]
emoji_213.png,[面条]
emoji_214.png,[咖喱饭]
emoji_215.png,[饭团]
emoji_216.png,[麻辣烫]
emoji_217.png,[寿司]
emoji_218.png,[苹果]
emoji_219.png,[橙子]
emoji_220.png,[草莓]
emoji_221.png,[西瓜]
emoji_222.png,[柿子]
emoji_223.png,[眼睛]
emoji_224.png,[好的]

忘了布局文件,哇哈哈:
<?xml version="1.0" encoding="utf-8"?>
<com.example.facedemo.FaceRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/FaceRelativeLayout"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" >

    <RelativeLayout
        android:id="@+id/rl_input"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/chat_footer_bg" >

        <ImageButton
            android:id="@+id/btn_face"
            android:layout_width="40dip"
            android:layout_height="40dip"
            android:layout_alignParentLeft="true"
            android:layout_centerVertical="true"
            android:layout_marginLeft="8dip"
            android:background="@drawable/chat_send_btn"
            android:src="@drawable/ib_face" />

        <Button
            android:id="@+id/btn_send"
            android:layout_width="60dp"
            android:layout_height="40dp"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:layout_marginRight="10dp"
            android:background="@drawable/chat_send_btn"
            android:text="发送" />

        <EditText
            android:id="@+id/et_sendmessage"
            android:layout_width="fill_parent"
            android:layout_height="40dp"
            android:layout_centerVertical="true"
            android:layout_marginLeft="8dp"
            android:layout_marginRight="10dp"
            android:layout_toLeftOf="@id/btn_send"
            android:layout_toRightOf="@id/btn_face"
            android:background="@drawable/login_edit_normal"
            android:singleLine="true"
            android:textSize="18sp" />
    </RelativeLayout>

    <RelativeLayout
        android:id="@+id/ll_facechoose"
        android:layout_width="fill_parent"
        android:layout_height="124dip"
        android:layout_below="@id/rl_input"
        android:background="#f6f5f5"
        android:visibility="gone" >

        <android.support.v4.view.ViewPager
            android:id="@+id/vp_contains"
            android:layout_width="match_parent"
            android:layout_height="match_parent" >
        </android.support.v4.view.ViewPager>

        <LinearLayout
            android:id="@+id/iv_image"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_marginBottom="6dip"
            android:gravity="center"
            android:orientation="horizontal" >
        </LinearLayout>
    </RelativeLayout>
</com.example.facedemo.FaceRelativeLayout>

源码下载


Android聊天界面源码:实现了聊天气泡、表情图标(可翻页)[附件下载](52im.net).rar (2.61 MB , 下载次数: 58 , 售价: 5 金币)

(本源码来自csdn博客lnb333666,感谢原作者分享)

附录:全站精品资源下载


[1] 精品源码下载:
轻量级即时通讯框架MobileIMSDK的iOS源码(开源版)[附件下载]
开源IM工程“蘑菇街TeamTalk”2015年5月前未删减版完整代码 [附件下载]
微信本地数据库破解版(含iOS、Android),仅供学习研究 [附件下载]
NIO框架入门(四):Android与MINA2、Netty4的跨平台UDP双向通信实战 [附件下载]
NIO框架入门(三):iOS与MINA2、Netty4的跨平台UDP双向通信实战 [附件下载]
NIO框架入门(二):服务端基于MINA2的UDP双向通信Demo演示 [附件下载]
NIO框架入门(一):服务端基于Netty4的UDP双向通信Demo演示 [附件下载]
用于IM中图片压缩的Android工具类源码,效果可媲美微信 [附件下载]
高仿Android版手机QQ可拖拽未读数小气泡源码 [附件下载]
一个WebSocket实时聊天室Demo:基于node.js+socket.io [附件下载]
Android聊天界面源码:实现了聊天气泡、表情图标(可翻页) [附件下载]
高仿Android版手机QQ首页侧滑菜单源码 [附件下载]
开源libco库:单机千万连接、支撑微信8亿用户的后台框架基石 [源码下载]
分享java AMR音频文件合并源码,全网最全
微信团队原创Android资源混淆工具:AndResGuard [有源码]
一个基于MQTT通信协议的完整Android推送Demo [附件下载]
Android版高仿微信聊天界面源码 [附件下载]
仿微信的IM聊天时间显示格式(含iOS/Android/Web实现)[图文+源码]

[2] 精品文档和工具下载:
计算机网络通讯协议关系图(中文珍藏版)[附件下载]
史上最全即时通讯软件简史(精编大图版)[附件下载]
基于RTMP协议的流媒体技术的原理与应用(技术论文)[附件下载]
独家发布《TCP/IP详解 卷1:协议》CHM版 [附件下载]
良心分享:WebRTC 零基础开发者教程(中文)[附件下载]
MQTT协议手册(中文翻译版)[附件下载]
经典书籍《UNIX网络编程》最全下载(卷1+卷2、中文版+英文版)[附件下载]
音视频开发理论入门书籍之《视频技术手册(第5版)》[附件下载]
国际电联H.264视频编码标准官方技术手册(中文版)[附件下载]
Apache MINA2.0 开发指南(中文版)[附件下载]
网络通讯数据抓包和分析工具 Wireshark 使用教程(中文) [附件下载]
最新收集NAT穿越(p2p打洞)免费STUN服务器列表 [附件下载]
高性能网络编程经典:《The C10K problem(英文)》[附件下载]
即时通讯系统的原理、技术和应用(技术论文)[附件下载]
技术论文:微信对网络影响的技术试验及分析[附件下载]
华为内部3G网络资料: WCDMA系统原理培训手册[附件下载]
网络测试:Android版多路ping命令工具EnterprisePing[附件下载]
Android反编译利器APKDB:没有美工的日子里继续坚强的撸
一款用于P2P开发的NAT类型检测工具 [附件下载]
两款增强型Ping工具:持续统计、图形化展式网络状况 [附件下载]

[3] 精选视频、演讲PPT下载:
QQ空间移动端10亿级视频播放技术优化揭秘(视频+PPT)[附件下载]
RTC实时互联网2017年度大会精选演讲PPT [附件下载]
微信分享开源IM网络层组件库Mars的技术实现(视频+PPT)[附件下载]
微服务理念在微信海量用户后台架构中的实践(视频+PPT)[附件下载]
移动端IM开发和构建中的技术难点实践分享(视频+PPT)[附件下载]
网易云信的高品质即时通讯技术实践之路(视频+PPT)[附件下载]
腾讯音视频实验室:直面音视频质量评估之痛(视频+PPT)[附件下载]
腾讯QQ1.4亿在线用户的技术挑战和架构演进之路PPT[附件下载]
微信朋友圈海量技术之道PPT[附件下载]
手机淘宝消息推送系统的架构与实践(音频+PPT)[附件下载]
如何进行实时音视频的质量评估与监控(视频+PPT)[附件下载]
Go语言构建高并发消息推送系统实践PPT(来自360公司)[附件下载]
网易IM云千万级并发消息处理能力的架构设计与实践PPT [附件下载]
手机QQ的海量用户移动化实践分享(视频+PPT)[附件下载]
钉钉——基于IM技术的新一代企业OA平台的技术挑战(视频+PPT)[附件下载]
微信技术总监谈架构:微信之道——大道至简(PPT讲稿)[附件下载]
Netty的架构剖析及应用案例介绍(视频+PPT)[附件下载]
声网架构师谈实时音视频云的实现难点(视频采访)
滴滴打车架构演变及应用实践(PPT讲稿)[附件下载]
微信海量用户背后的后台系统存储架构(视频+PPT)[附件下载]
在线音视频直播室服务端架构最佳实践(视频+PPT)[附件下载]
从0到1:万人在线的实时音视频直播技术实践分享(视频+PPT)[附件下载]
微信移动端应对弱网络情况的探索和实践PPT[附件下载]
Android版微信从300KB到30MB的技术演进(PPT讲稿)[附件下载]

即时通讯网 - 即时通讯开发者社区! 来源: - 即时通讯开发者社区!

标签:聊天界面
上一篇:声网架构师谈实时音视频云的实现难点(视频采访)下一篇:Android版高仿微信聊天界面源码 [附件下载]

本帖已收录至以下技术专辑

推荐方案
评论 4
自已撸了个聊天程序,只想要表情图标素材,不需要代码 哈哈
签名: 好想把妹!
跟我找的一样
很好,原理清晰
正好需要
打赏楼主 ×
使用微信打赏! 使用支付宝打赏!

返回顶部