前言;正常实现聊天功能想必大家都使用三方的Sdk比如环信融云集成的,但是聊天记录的保存只能有三天,想增加保存时长就需要花钱,so 我只好自己想办法实现了,这个demo是类似于留言板,并非即时通讯!只实现了表情文字图文混排,可以通过手动刷新实现即时通讯ok废话少说,先看效果图;

项目下载地址

https://github.com/PangHaHa12138/TestChatdemo

表情下载地址

http://emojipedia.org/apple/

大体思路:

1.先从布局开始,聊天界面就是多条目的listview,左右各算一种类型,这样正常语音,文字,图文混排,大图片,大表情,视频,都是x2倍的,聊天条目就是listview条目背景透明,然后imageview+给textview设置气泡的聊天背景

不过我现在暂时只实现了文字表情,然后键盘这块是gridview实现,写正则来过滤表情的编码,点击事件判断选中表情还是删除

感谢开源键盘控件:

https://github.com/w446108264/XhsEmoticonsKeyboard

继承XhsEmoticonsKeyboard控件写一个带表情的键盘

2.代码大体逻辑,发送的消息其实就是一个文本内容,通过SpannableStringBuilder和Spannable实现图文混排,其实就是把各种表情序列号也可以说是索引写到一个xml文件里,然后用一个map来存这些图片对应的编码,其实就是图片名字,然后通过正则来找到正确的索引,即xml里存的图片对应文件夹drawable下的

SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(content);

Spannable spannable = QqFilter.spannableFilter(tv_content.getContext(),
        spannableStringBuilder,
        content,
        EmoticonsKeyboardUtils.getFontHeight(tv_content),
        null);
tv_content.setText(spannable);

然后就是listview的展示了,页面初始化的时候根据服务器返回的字段判断是别人发的在左边,还是我发的在右面,然后在adapter里调用图文混排的方法找到对应表情图片填充条目,发送的时候先检查是否是正确的表情字符,找到对应表情图片的集合map,找到对应的表情名字,然后和文字一起传到服务器,然后进行网络请求,上传成功之后再刷新界面,上拉加载的话是加载历史记录全部的,下拉刷新是请求最新的,每次请求都控制页面显示最多28条数据,也就是4页,上来初始化页面也是,然后可以通过聊完上拉不停的重复手动实现即时通讯

当然这是开玩笑了

下面上代码:

主要布局

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
    android:id="@+id/titlefragment"
    android:name="com.panghaha.it.testchatdemo.Titlefragment"
    android:layout_width="match_parent"
    android:layout_height="45dp"/>

    <!--<TextView-->
        <!--android:layout_centerInParent="true"-->
        <!--android:textSize="22sp"-->
        <!--android:text="在未来的日子里,\n努力让抛弃我的人\n始终觉得她们的决定是正确的"-->
        <!--android:layout_width="wrap_content"-->
        <!--android:layout_height="wrap_content" />-->
    <FrameLayout
        android:id="@+id/content"
        android:layout_above="@+id/bottom_navigation_bar"
        android:layout_below="@+id/titlefragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </FrameLayout>

    <com.ashokvarma.bottomnavigation.BottomNavigationBar
        android:id="@+id/bottom_navigation_bar"
        android:layout_gravity="bottom"
        android:layout_alignParentBottom="true"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</RelativeLayout>

主界面,下面四个按钮,切换四个fragment

聊天界面布局 最外层用自己继承

XhsEmoticonsKeyBoard 键盘类
<com.panghaha.it.testchatdemo.common.SimpleUserdefEmoticonsKeyBoard
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:id="@+id/keyboard"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:orientation="vertical"
        android:background="@drawable/pic_bg3x"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <!--<include layout="@layout/activity_right_toobar"/>-->
        <android.support.v7.widget.Toolbar
            android:id="@+id/toobaraaa"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/titbar"
            android:minHeight="?attr/actionBarSize">

            <TextView
                android:id="@+id/toolbarmtit"
                style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:lines="1"
                android:ellipsize="end"
                android:text="安琪宝贝"
                android:scrollHorizontally="true"
                android:textColor="@color/white"
                android:layout_gravity="center" />
            <!--自定义toolbar的title 和subtitle -->

        </android.support.v7.widget.Toolbar>

        <android.support.v4.widget.SwipeRefreshLayout
            android:id="@+id/uploadmore"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <ListView
                android:id="@+id/lv_chat"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:cacheColorHint="#00000000"
                android:divider="@null"
                android:fadingEdge="none"
                android:fitsSystemWindows="true"
                android:listSelector="#00000000"
                android:scrollbarStyle="outsideOverlay"
                android:scrollingCache="false"
                android:smoothScrollbar="true"
                android:stackFromBottom="true" />
        </android.support.v4.widget.SwipeRefreshLayout>

    </LinearLayout>

</com.panghaha.it.testchatdemo.common.SimpleUserdefEmoticonsKeyBoard>

键盘类

public class SimpleUserdefEmoticonsKeyBoard extends XhsEmoticonsKeyBoard {public final int APPS_HEIGHT = 120;

    public SimpleUserdefEmoticonsKeyBoard(Context context, AttributeSet attrs) {super(context, attrs);
    }@Override
    protected void inflateKeyboardBar(){mInflater.inflate(R.layout.view_keyboard_userdef, this);
    }@Override
    protected View inflateFunc(){return mInflater.inflate(R.layout.view_func_emoticon_userdef, null);
    }@Override
    public void reset() {EmoticonsKeyboardUtils.closeSoftKeyboard(getContext());
        mLyKvml.hideAllFuncView();
        mBtnFace.setImageResource(R.drawable.chatting_emoticons);
    }@Override
    public void onFuncChange(int key) {if (FUNC_TYPE_EMOTION == key) {mBtnFace.setImageResource(R.drawable.chatting_softkeyboard);
        } else {mBtnFace.setImageResource(R.drawable.chatting_emoticons);
        }checkVoice();
    }@Override
    public void OnSoftClose() {super.OnSoftClose();
        if (mLyKvml.getCurrentFuncKey() == FUNC_TYPE_APPPS) {setFuncViewHeight(EmoticonsKeyboardUtils.dip2px(getContext(), APPS_HEIGHT));
        }}@Override
    protected void showText() {mEtChat.setVisibility(VISIBLE);
        mBtnFace.setVisibility(VISIBLE);
        mBtnVoice.setVisibility(GONE);
    }@Override
    protected void showVoice() {mEtChat.setVisibility(GONE);
        mBtnFace.setVisibility(GONE);
        mBtnVoice.setVisibility(VISIBLE);
        reset();
    }@Override
    protected void checkVoice() {if (mBtnVoice.isShown()) {mBtnVoiceOrText.setImageResource(R.drawable.chatting_softkeyboard);
        } else {mBtnVoiceOrText.setImageResource(R.drawable.chatting_vodie);
        }}@Override
    public void onClick(View v) {int i = v.getId();
        if (i == com.keyboard.view.R.id.btn_voice_or_text) {if (mEtChat.isShown()) {mBtnVoiceOrText.setImageResource(R.drawable.chatting_softkeyboard);
                showVoice();
            } else {showText();
                mBtnVoiceOrText.setImageResource(R.drawable.chatting_vodie);
                EmoticonsKeyboardUtils.openSoftKeyboard(mEtChat);
            }} else if (i == com.keyboard.view.R.id.btn_face) {toggleFuncView(FUNC_TYPE_EMOTION);
        } else if (i == com.keyboard.view.R.id.btn_multimedia) {toggleFuncView(FUNC_TYPE_APPPS);
            setFuncViewHeight(EmoticonsKeyboardUtils.dip2px(getContext(), APPS_HEIGHT));
        }}

表情过滤和定位类

public class QqFilter extends EmoticonFilter {public static final int WRAP_DRAWABLE = -1;
    private int emoticonSize = -1;
    public static final Pattern QQ_RANGE = Pattern.compile("\\[[a-zA-Z0-9\\u4e00-\\u9fa5]+\\]");

    public static Matcher getMatcher(CharSequence matchStr) {return QQ_RANGE.matcher(matchStr);
    }@Override
    public void filter(EditText editText, CharSequence text, int start, int lengthBefore, int lengthAfter) {emoticonSize = emoticonSize == -1 ? EmoticonsKeyboardUtils.getFontHeight(editText) : emoticonSize;
        clearSpan(editText.getText(), start, text.toString().length());
        Matcher m = getMatcher(text.toString().substring(start, text.toString().length()));
        if (m != null) {while (m.find()) {String key = m.group();
                int icon = DefQqEmoticons.sQqEmoticonHashMap.get(key);
                if (icon > 0) {emoticonDisplay(editText.getContext(), editText.getText(), icon, emoticonSize, start + m.start(), start + m.end());
                }}}}public static Spannable spannableFilter(Context context, Spannable spannable, CharSequence text, int fontSize, EmojiDisplayListener emojiDisplayListener) {Matcher m = getMatcher(text);
        if (m != null) {while (m.find()) {String key = m.group();
                int icon = DefQqEmoticons.sQqEmoticonHashMap.get(key);
                if (emojiDisplayListener == null) {if (icon > 0) {emoticonDisplay(context, spannable, icon, fontSize, m.start(), m.end());
                    }} else {emojiDisplayListener.onEmojiDisplay(context, spannable, "" + icon, fontSize, m.start(), m.end());
                }}}return spannable;
    }private void clearSpan(Spannable spannable, int start, int end) {if (start == end) {return;
        }EmoticonSpan[] oldSpans = spannable.getSpans(start, end, EmoticonSpan.class);
        for (int i = 0; i < oldSpans.length; i++) {spannable.removeSpan(oldSpans[i]);
        }}public static void emoticonDisplay(Context context, Spannable spannable, int emoticon, int fontSize, int start, int end) {Drawable drawable = getDrawable(context, emoticon);
        if (drawable != null) {int itemHeight;
            int itemWidth;
            if (fontSize == WRAP_DRAWABLE) {itemHeight = drawable.getIntrinsicHeight();
                itemWidth = drawable.getIntrinsicWidth();
            } else {itemHeight = fontSize;
                itemWidth = fontSize;
            }drawable.setBounds(0, 0, itemHeight, itemWidth);
            EmoticonSpan imageSpan = new EmoticonSpan(drawable);
            spannable.setSpan(imageSpan, start, end, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
        }}

生成添加各类型表情的工厂

package com.panghaha.it.testchatdemo.common;

import android.content.Context;
import android.text.Editable;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.TextView;

import com.panghaha.it.testchatdemo.R;
import com.sj.emoji.DefEmoticons;
import com.sj.emoji.EmojiBean;
import com.testemticon.DefXhsEmoticons;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collections;

import sj.keyboard.adpater.EmoticonsAdapter;
import sj.keyboard.adpater.PageSetAdapter;
import sj.keyboard.data.EmoticonEntity;
import sj.keyboard.data.EmoticonPageEntity;
import sj.keyboard.data.EmoticonPageSetEntity;
import sj.keyboard.data.PageEntity;
import sj.keyboard.data.PageSetEntity;
import sj.keyboard.interfaces.EmoticonClickListener;
import sj.keyboard.interfaces.EmoticonDisplayListener;
import sj.keyboard.interfaces.PageViewInstantiateListener;
import sj.keyboard.utils.EmoticonsKeyboardUtils;
import sj.keyboard.utils.imageloader.ImageBase;
import sj.keyboard.utils.imageloader.ImageLoader;
import sj.keyboard.widget.EmoticonPageView;
import sj.keyboard.widget.EmoticonsEditText;
import sj.qqkeyboard.DefQqEmoticons;

/**
 *
 * 表情工厂类 加载表情种类
 * */

public class SimpleCommonUtils {public static void initEmoticonsEditText(EmoticonsEditText etContent) {etContent.addEmoticonFilter(new EmojiFilter());
        etContent.addEmoticonFilter(new XhsFilter());
    }public static EmoticonClickListener getCommonEmoticonClickListener(final EditText editText) {return new EmoticonClickListener() {@Override
            public void onEmoticonClick(Object o, int actionType, boolean isDelBtn) {if (isDelBtn) {SimpleCommonUtils.delClick(editText);
                } else {if (o == null) {return;
                    }if (actionType == Constants.EMOTICON_CLICK_TEXT) {String content = null;
                        if (o instanceof EmojiBean) {content = ((EmojiBean) o).emoji;
                        } else if (o instanceof EmoticonEntity) {content = ((EmoticonEntity) o).getContent();
                        }if (TextUtils.isEmpty(content)) {return;
                        }int index = editText.getSelectionStart();
                        Editable editable = editText.getText();
                        editable.insert(index, content);
                    }}}};
    }public static PageSetAdapter sCommonPageSetAdapter;

    public static PageSetAdapter getCommonAdapter(Context context, EmoticonClickListener emoticonClickListener) {if(sCommonPageSetAdapter != null){return sCommonPageSetAdapter;
        }PageSetAdapter pageSetAdapter = new PageSetAdapter();
        //原生的笑脸表情
//        addEmojiPageSetEntity(pageSetAdapter, context, emoticonClickListener);
        //QQ笑脸表情
        addQqPageSetEntity(pageSetAdapter, context, emoticonClickListener);

        //龟头表情
//        addXhsPageSetEntity(pageSetAdapter, context, emoticonClickListener);
        //兔斯基
//        addWechatPageSetEntity(pageSetAdapter, context, emoticonClickListener);
        //好好学习表情包
//        addGoodGoodStudyPageSetEntity(pageSetAdapter, context, emoticonClickListener);
        //颜文字
        addKaomojiPageSetEntity(pageSetAdapter, context, emoticonClickListener);

//        addTestPageSetEntity(pageSetAdapter, context);

        return pageSetAdapter;
    }/**
     * 插入emoji表情集
     *
     * @param pageSetAdapter
     * @param context
     * @param emoticonClickListener
     */
    public static void addEmojiPageSetEntity(PageSetAdapter pageSetAdapter, Context context, final EmoticonClickListener emoticonClickListener) {ArrayList<EmojiBean> emojiArray = new ArrayList<>();
        Collections.addAll(emojiArray, DefEmoticons.sEmojiArray);
        EmoticonPageSetEntity emojiPageSetEntity= new EmoticonPageSetEntity.Builder().setLine(3).setRow(7).setEmoticonList(emojiArray).setIPageViewInstantiateItem(getDefaultEmoticonPageViewInstantiateItem(new EmoticonDisplayListener<Object>() {@Override
                    public void onBindView(int position, ViewGroup parent, EmoticonsAdapter.ViewHolder viewHolder, Object object, final boolean isDelBtn) {final EmojiBean emojiBean = (EmojiBean) object;
                        if (emojiBean == null && !isDelBtn) {return;
                        }viewHolder.ly_root.setBackgroundResource(com.keyboard.view.R.drawable.bg_emoticon);

                        if (isDelBtn) {viewHolder.iv_emoticon.setImageResource(R.drawable.icon_del);
                        } else {viewHolder.iv_emoticon.setImageResource(emojiBean.icon);
                        }viewHolder.rootView.setOnClickListener(new View.OnClickListener() {@Override
                            public void onClick(View v) {if (emoticonClickListener != null) {emoticonClickListener.onEmoticonClick(emojiBean, Constants.EMOTICON_CLICK_TEXT, isDelBtn);
                                }}});
                    }})).setShowDelBtn(EmoticonPageEntity.DelBtnStatus.LAST).setIconUri(ImageBase.Scheme.DRAWABLE.toUri("icon_emoji")).build();
        pageSetAdapter.add(emojiPageSetEntity);
    }public static void addQqPageSetEntity(PageSetAdapter pageSetAdapter, Context context, final EmoticonClickListener emoticonClickListener) {EmoticonPageSetEntity kaomojiPageSetEntity= new EmoticonPageSetEntity.Builder().setLine(3).setRow(7).setEmoticonList(ParseDataUtils.ParseQqData(DefQqEmoticons.sQqEmoticonHashMap)).setIPageViewInstantiateItem(new PageViewInstantiateListener<EmoticonPageEntity>() {@Override
                    public View instantiateItem(ViewGroup container, int position, EmoticonPageEntity pageEntity) {if (pageEntity.getRootView() == null) {EmoticonPageView pageView = new EmoticonPageView(container.getContext());
                            pageView.setNumColumns(pageEntity.getRow());
                            pageEntity.setRootView(pageView);
                            try {EmoticonsAdapter adapter = new EmoticonsAdapter(container.getContext(), pageEntity, emoticonClickListener);
                                adapter.setItemHeightMaxRatio(1.8);
                                adapter.setOnDisPlayListener(getEmoticonDisplayListener(emoticonClickListener));
                                pageView.getEmoticonsGridView().setAdapter(adapter);
                            } catch (Exception e) {e.printStackTrace();
                            }}return pageEntity.getRootView();
                    }}).setShowDelBtn(EmoticonPageEntity.DelBtnStatus.LAST).setIconUri(ImageBase.Scheme.DRAWABLE.toUri("kys")).build();
        pageSetAdapter.add(kaomojiPageSetEntity);
    }public static EmoticonDisplayListener<Object> getEmoticonDisplayListener(final EmoticonClickListener emoticonClickListener){return new EmoticonDisplayListener<Object>() {@Override
            public void onBindView(int position, ViewGroup parent, EmoticonsAdapter.ViewHolder viewHolder, Object object, final boolean isDelBtn) {final EmoticonEntity emoticonEntity = (EmoticonEntity) object;
                if (emoticonEntity == null && !isDelBtn) {return;
                }viewHolder.ly_root.setBackgroundResource(com.keyboard.view.R.drawable.bg_emoticon);

                if (isDelBtn) {viewHolder.iv_emoticon.setImageResource(R.drawable.icon_del);
                } else {try {ImageLoader.getInstance(viewHolder.iv_emoticon.getContext()).displayImage(emoticonEntity.getIconUri(), viewHolder.iv_emoticon);
                    } catch (IOException e) {e.printStackTrace();
                    }}viewHolder.rootView.setOnClickListener(new View.OnClickListener() {@Override
                    public void onClick(View v) {if (emoticonClickListener != null) {emoticonClickListener.onEmoticonClick(emoticonEntity, Constants.EMOTICON_CLICK_TEXT, isDelBtn);
                        }}});
            }};
    }/**
     * 插入xhs表情集
     *
     * @param pageSetAdapter
     * @param context
     * @param emoticonClickListener
     */
    public static void addXhsPageSetEntity(PageSetAdapter pageSetAdapter, Context context, EmoticonClickListener emoticonClickListener) {EmoticonPageSetEntity xhsPageSetEntity= new EmoticonPageSetEntity.Builder().setLine(3).setRow(7).setEmoticonList(ParseDataUtils.ParseXhsData(DefXhsEmoticons.xhsEmoticonArray, ImageBase.Scheme.ASSETS)).setIPageViewInstantiateItem(getDefaultEmoticonPageViewInstantiateItem(getCommonEmoticonDisplayListener(emoticonClickListener, Constants.EMOTICON_CLICK_TEXT))).setShowDelBtn(EmoticonPageEntity.DelBtnStatus.LAST).setIconUri(ImageBase.Scheme.ASSETS.toUri("xhsemoji_19.png")).build();
        pageSetAdapter.add(xhsPageSetEntity);
    }/**
     * 插入微信表情集
     *
     * @param pageSetAdapter
     * @param context
     * @param emoticonClickListener
     */
    public static void addWechatPageSetEntity(PageSetAdapter pageSetAdapter, Context context, EmoticonClickListener emoticonClickListener) {String filePath = FileUtils.getFolderPath("wxemoticons");
        EmoticonPageSetEntity<EmoticonEntity> emoticonPageSetEntity = ParseDataUtils.parseDataFromFile(context, filePath, "wxemoticons.zip", "wxemoticons.xml");
        if (emoticonPageSetEntity == null) {return;
        }EmoticonPageSetEntity pageSetEntity= new EmoticonPageSetEntity.Builder().setLine(emoticonPageSetEntity.getLine()).setRow(emoticonPageSetEntity.getRow()).setEmoticonList(emoticonPageSetEntity.getEmoticonList()).setIPageViewInstantiateItem(getEmoticonPageViewInstantiateItem(BigEmoticonsAdapter.class, emoticonClickListener)).setIconUri(ImageBase.Scheme.FILE.toUri(filePath + "/" + emoticonPageSetEntity.getIconUri())).build();
        pageSetAdapter.add(pageSetEntity);
    }/**
     * 插入我们爱学习表情集
     *
     * @param pageSetAdapter
     * @param context
     * @param emoticonClickListener
     */
    public static void addGoodGoodStudyPageSetEntity(PageSetAdapter pageSetAdapter, Context context, EmoticonClickListener emoticonClickListener) {String filePath = FileUtils.getFolderPath("goodgoodstudy");
        EmoticonPageSetEntity<EmoticonEntity> emoticonPageSetEntity = ParseDataUtils.parseDataFromFile(context, filePath, "goodgoodstudy.zip", "goodgoodstudy.xml");
        if (emoticonPageSetEntity == null) {return;
        }EmoticonPageSetEntity pageSetEntity= new EmoticonPageSetEntity.Builder().setLine(emoticonPageSetEntity.getLine()).setRow(emoticonPageSetEntity.getRow()).setEmoticonList(emoticonPageSetEntity.getEmoticonList()).setIPageViewInstantiateItem(getEmoticonPageViewInstantiateItem(BigEmoticonsAndTitleAdapter.class, emoticonClickListener)).setIconUri(ImageBase.Scheme.FILE.toUri(filePath + "/" + emoticonPageSetEntity.getIconUri())).build();
        pageSetAdapter.add(pageSetEntity);
    }/**
     * 插入颜文字表情集
     *
     * @param pageSetAdapter
     * @param context
     * @param emoticonClickListener
     */
    public static void addKaomojiPageSetEntity(PageSetAdapter pageSetAdapter, Context context, EmoticonClickListener emoticonClickListener) {EmoticonPageSetEntity kaomojiPageSetEntity= new EmoticonPageSetEntity.Builder().setLine(3).setRow(3).setEmoticonList(ParseDataUtils.parseKaomojiData(context)).setIPageViewInstantiateItem(getEmoticonPageViewInstantiateItem(TextEmoticonsAdapter.class, emoticonClickListener)).setIconUri(ImageBase.Scheme.DRAWABLE.toUri("icon_kaomoji")).build();
        pageSetAdapter.add(kaomojiPageSetEntity);
    }/**
     * 测试页集
     *
     * @param pageSetAdapter
     * @param context
     */
    public static void addTestPageSetEntity(PageSetAdapter pageSetAdapter, Context context) {PageSetEntity pageSetEntity = new PageSetEntity.Builder().addPageEntity(new PageEntity(new SimpleAppsGridView(context))).setIconUri(ImageBase.Scheme.DRAWABLE.toUri("icon_kaomoji")).setShowIndicator(false).build();
        pageSetAdapter.add(pageSetEntity);
    }@SuppressWarnings("unchecked")public static Object newInstance(Class _Class, Object... args) throws Exception {return newInstance(_Class, 0, args);
    }@SuppressWarnings("unchecked")public static Object newInstance(Class _Class, int constructorIndex, Object... args) throws Exception {Constructor cons = _Class.getConstructors()[constructorIndex];
        return cons.newInstance(args);
    }public static PageViewInstantiateListener<EmoticonPageEntity> getDefaultEmoticonPageViewInstantiateItem(final EmoticonDisplayListener<Object> emoticonDisplayListener) {return getEmoticonPageViewInstantiateItem(EmoticonsAdapter.class, null, emoticonDisplayListener);
    }public static PageViewInstantiateListener<EmoticonPageEntity> getEmoticonPageViewInstantiateItem(final Class _class, EmoticonClickListener onEmoticonClickListener) {return getEmoticonPageViewInstantiateItem(_class, onEmoticonClickListener, null);
    }public static PageViewInstantiateListener<EmoticonPageEntity> getEmoticonPageViewInstantiateItem(final Class _class, final EmoticonClickListener onEmoticonClickListener, final EmoticonDisplayListener<Object> emoticonDisplayListener) {return new PageViewInstantiateListener<EmoticonPageEntity>() {@Override
            public View instantiateItem(ViewGroup container, int position, EmoticonPageEntity pageEntity) {if (pageEntity.getRootView() == null) {EmoticonPageView pageView = new EmoticonPageView(container.getContext());
                    pageView.setNumColumns(pageEntity.getRow());
                    pageEntity.setRootView(pageView);
                    try {EmoticonsAdapter adapter = (EmoticonsAdapter) newInstance(_class, container.getContext(), pageEntity, onEmoticonClickListener);
                        if (emoticonDisplayListener != null) {adapter.setOnDisPlayListener(emoticonDisplayListener);
                        }pageView.getEmoticonsGridView().setAdapter(adapter);
                    } catch (Exception e) {e.printStackTrace();
                    }}return pageEntity.getRootView();
            }};
    }public static EmoticonDisplayListener<Object> getCommonEmoticonDisplayListener(final EmoticonClickListener onEmoticonClickListener, final int type) {return new EmoticonDisplayListener<Object>() {@Override
            public void onBindView(int position, ViewGroup parent, EmoticonsAdapter.ViewHolder viewHolder, Object object, final boolean isDelBtn) {final EmoticonEntity emoticonEntity = (EmoticonEntity) object;
                if (emoticonEntity == null && !isDelBtn) {return;
                }viewHolder.ly_root.setBackgroundResource(com.keyboard.view.R.drawable.bg_emoticon);

                if (isDelBtn) {viewHolder.iv_emoticon.setImageResource(R.drawable.icon_del);
                } else {try {ImageLoader.getInstance(viewHolder.iv_emoticon.getContext()).displayImage(emoticonEntity.getIconUri(), viewHolder.iv_emoticon);
                    } catch (IOException e) {e.printStackTrace();
                    }}viewHolder.rootView.setOnClickListener(new View.OnClickListener() {@Override
                    public void onClick(View v) {if (onEmoticonClickListener != null) {onEmoticonClickListener.onEmoticonClick(emoticonEntity, type, isDelBtn);
                        }}});
            }};
    }public static void delClick(EditText editText) {int action = KeyEvent.ACTION_DOWN;
        int code = KeyEvent.KEYCODE_DEL;
        KeyEvent event = new KeyEvent(action, code);
        editText.onKeyDown(KeyEvent.KEYCODE_DEL, event);
    }public static void spannableEmoticonFilter(TextView tv_content, String content) {SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(content);

        Spannable spannable = QqFilter.spannableFilter(tv_content.getContext(),
                spannableStringBuilder,
                content,
                EmoticonsKeyboardUtils.getFontHeight(tv_content),
                null);
        tv_content.setText(spannable);

        /* Spannable spannable = EmojiDisplay.spannableFilter(tv_content.getContext(),
                spannableStringBuilder,
                content,
                EmoticonsKeyboardUtils.getFontHeight(tv_content));*/

//        spannable = XhsFilter.spannableFilter(tv_content.getContext(),
//                spannable,
//                content,
//                EmoticonsKeyboardUtils.getFontHeight(tv_content),
//                null);
//        tv_content.setText(spannable);
    }
}
listview adapter
package com.panghaha.it.testchatdemo.common;

import android.app.Activity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import com.bumptech.glide.Glide;
import com.panghaha.it.testchatdemo.R;

import java.util.List;

public class NyChattingListAdapter extends BaseAdapter {private final int VIEW_TYPE_COUNT = 8;
    private final int VIEW_TYPE_LEFT_TEXT = 0;
    private final int VIEW_TYPE_LEFT_IMAGE = 1;
    private final int VIEW_TYPE_RIGTH_TEXT = 2;
    private final int VIEW_TYPE_RIGTH_IMAGE = 3;

    private Activity mActivity;
    private LayoutInflater mInflater;
    private List<Data_ReceiverNews.NewsBean> mData;
    private String myuserid;
    public int isMesend;
    public int istext;
    public NyChattingListAdapter(Activity activity,String id,List<Data_ReceiverNews.NewsBean> list) {this.mActivity = activity;
        this.myuserid = id;
        this.mData = list;
        mInflater = LayoutInflater.from(activity);
    }public void setsendtype(int i){isMesend = i;
    }@Override
    public int getCount() {return mData == null ? 0 : mData.size();
    }@Override
    public Object getItem(int position) {return mData.get(position);
    }@Override
    public long getItemId(int position) {return position;
    }@Override
    public int getItemViewType(int position) {if (mData.get(position) == null) {return -1;
        }if (mData.get(position).getUserid()!=null&&mData.get(position).getUserid().equals(myuserid)||isMesend == 1){return VIEW_TYPE_RIGTH_TEXT;
        }else if (mData.get(position).getUserid()!=null&&!mData.get(position).getUserid().equals(myuserid)){return VIEW_TYPE_LEFT_TEXT;
        }return -1;
    }@Override
    public int getViewTypeCount() {return VIEW_TYPE_COUNT;
    }@Override
    public View getView(int position, View convertView, ViewGroup parent) {final Data_ReceiverNews.NewsBean bean = mData.get(position);
        int type = getItemViewType(position);
        ViewHolderText rightholder;
        if (convertView == null) {if (type == VIEW_TYPE_RIGTH_TEXT ){convertView = mInflater.inflate(R.layout.listitem_cha_right_text, null);//接收的消息
            }else if (type == VIEW_TYPE_LEFT_TEXT){convertView = mInflater.inflate(R.layout.listitem_cha_left_text, null);//发送的消息
            }rightholder = new ViewHolderText();

            rightholder.iv_avatar = (CircleImageView) convertView.findViewById(R.id.iv_avatar);
            rightholder.tv_content = (TextView) convertView.findViewById(R.id.tv_content);
            rightholder.sendtime = (TextView) convertView.findViewById(R.id.sendtime);
            rightholder.name = (TextView) convertView.findViewById(R.id.chatname);

            convertView.setTag(rightholder);
        } else {rightholder = (ViewHolderText) convertView.getTag();
        }if (type == VIEW_TYPE_RIGTH_TEXT){String time = bean.getCreatime();
            String tim = time.substring(0,time.length()-2);
            rightholder.sendtime.setText(tim);//设置头像和姓名注释了
//            rightholder.name.setText(SharedPreferencesUtil.readUsername(mActivity));
//            if (SharedPreferencesUtil.readAvatar(mActivity)!=null){
//                Glide.with(mActivity).load(SharedPreferencesUtil.readAvatar(mActivity)).into(rightholder.iv_avatar);
//            }
            disPlayRightTextView(position, convertView, rightholder, bean);
        }else if (type == VIEW_TYPE_LEFT_TEXT){String time = bean.getCreatime();
            String tim = time.substring(0,time.length()-2);
            rightholder.sendtime.setText(tim);//设置头像和姓名注释了
//            rightholder.name.setText(bean.getUserName());
//            if (bean.getPath()!=null){
//                Glide.with(mActivity).load(bean.getPath()).into(rightholder.iv_avatar);
//            }
            disPlayRightTextView(position, convertView, rightholder, bean);
        }return convertView;
    }//图文混排 设置消息
    public void disPlayRightTextView(int position, View view, ViewHolderText holder, Data_ReceiverNews.NewsBean bean) {setContent2(holder.tv_content, bean.getNewscontent());
    }//这里是添加表情
    public void setContent2(TextView tv_content, String content) {SimpleCommonUtils.spannableEmoticonFilter(tv_content, content);
    }public final class ViewHolderText {public CircleImageView iv_avatar;
        public TextView tv_content;
        public TextView sendtime;
        public TextView name;
    }
}
聊天主类
package com.panghaha.it.testchatdemo;

import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.Editable;
import android.text.TextUtils;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.lzy.okhttputils.OkHttpUtils;
import com.lzy.okhttputils.callback.StringCallback;
import com.panghaha.it.testchatdemo.common.ChattingListAdapter;
import com.panghaha.it.testchatdemo.common.Constants;
import com.panghaha.it.testchatdemo.common.Data_ReceiverNews;
import com.panghaha.it.testchatdemo.common.NyChattingListAdapter;
import com.panghaha.it.testchatdemo.common.SimpleCommonUtils;
import com.panghaha.it.testchatdemo.common.SimpleUserDefAppsGridView;
import com.panghaha.it.testchatdemo.common.SimpleUserdefEmoticonsKeyBoard;
import com.sj.emoji.EmojiBean;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import okhttp3.Call;
import okhttp3.Response;
import sj.keyboard.data.EmoticonEntity;
import sj.keyboard.interfaces.EmoticonClickListener;
import sj.keyboard.widget.EmoticonsEditText;
import sj.keyboard.widget.FuncLayout;

/***
 * ━━━━ Code is far away from ━━━━━━
 *     ()      ()
 *     ( )    ( )
 *     ( )    ( )
 *   ┏┛┻━━━┛┻┓
 *   ┃   ━   ┃
 *   ┃ ┳┛ ┗┳ ┃
 *   ┃   ┻   ┃
 *   ┗━┓   ┏━┛
 *     ┃   ┃
 *     ┃   ┗━━━┓
 *     ┃       ┣┓
 *     ┃       ┏┛
 *     ┗┓┓┏━┳┓┏┛
 *      ┃┫┫ ┃┫┫
 *      ┗┻┛ ┗┻┛
 * ━━━━ bug with the more protecting ━━━
 * <p/>
 * Created by PangHaHa12138 on 2017/6/7.
 */
public class ActivityChat extends AppCompatActivity implements FuncLayout.OnFuncKeyBoardListener {private ListView lvChat;
    private SimpleUserdefEmoticonsKeyBoard ekBar;
    private ChattingListAdapter chattingListAdapter;
    //    private MyChattingAdapter myChattingAdapter;
    private NyChattingListAdapter myChattingAdapter;
    private Toolbar toolbar;
    private TextView title;
    private ImageView addview;
    private String userid,taskid;
    private Data_ReceiverNews.NewsBean newsBean;
    private Data_ReceiverNews data_receiverNews;
    private List<Data_ReceiverNews.NewsBean> newsBeanList = new ArrayList<Data_ReceiverNews.NewsBean>();
    private List<Data_ReceiverNews.NewsBean> newsBeanListshort= new ArrayList<Data_ReceiverNews.NewsBean>();
    private int allcount;
    private SwipeRefreshLayout uploadmore;
    private View footview;
    private int footerHeight;
    private boolean isshort;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);
        setContentView(R.layout.activitychat);
//        userid = SharedPreferencesUtil.readUserid(Activity_Chat.this);
//        Intent intent = getIntent();
//        taskid = intent.getStringExtra("taskidchat");
        //这里参数我是为了测试写死的,正常可以通过intent或者读取缓存传值
        userid = "02774bc536964386a68bd2b64145c910";
        taskid = "eb05f06c46dd4acd87e0bef85575f981";
        initview();
    }private void initview() {lvChat = (ListView) findViewById(R.id.lv_chat);
        ekBar = (SimpleUserdefEmoticonsKeyBoard) findViewById(R.id.keyboard);
        uploadmore = (SwipeRefreshLayout) findViewById(R.id.uploadmore);
        toolbar = (Toolbar) findViewById(R.id.toobaraaa);
        setSupportActionBar(toolbar);
        footview = View.inflate(ActivityChat.this,R.layout.chat_footer,null);
        footview.measure(0,0);
        footerHeight = footview.getMeasuredHeight();
        footview.setPadding(0,-footerHeight,0,0);
        lvChat.addFooterView(footview);
        getSupportActionBar().setDisplayShowTitleEnabled(false);

        showBack();
        initEmoticonsKeyBoardBar();
        initListView();
        uploadmore.setColorSchemeResources(android.R.color.holo_blue_light, android.R.color.holo_green_light,
                android.R.color.holo_orange_light);
        //上拉加载历史记录
        uploadmore.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {@Override
            public void onRefresh() {Handler handler = new Handler();
                handler.postDelayed(new Runnable() {@Override
                    public void run() {try {OkHttpUtils.get(Http_Api.URL_NewReceiver).params("userid",userid).params("taskid",taskid).execute(new StringCallback() {@Override
                                        public void onSuccess(String s, Call call, Response response) {
//                                            LogUtil.d("返回值",s);
                                            data_receiverNews = JsonUtil.parseJsonToBean(s,Data_ReceiverNews.class);
                                            allcount = Integer.parseInt(data_receiverNews.getResult());
                                            if (allcount != 0){if (newsBeanList!=null){newsBeanList.clear();
                                                }newsBeanList = data_receiverNews.getNews();
                                                myChattingAdapter = new NyChattingListAdapter(ActivityChat.this,userid,newsBeanList);
                                                lvChat.setAdapter(myChattingAdapter);
                                                myChattingAdapter.notifyDataSetChanged();
                                                uploadmore.setRefreshing(false);
                                            }else {
//                                                ToastUtil.showToast("对不起,没有更多消息了");
                                                Toast.makeText(ActivityChat.this,"对不起没有更多消息了",Toast.LENGTH_SHORT).show();
                                                uploadmore.setRefreshing(false);
                                            }}});
                        } catch (Exception e) {e.printStackTrace();
                        }}},2000);

            }});
    }private void initEmoticonsKeyBoardBar() {SimpleCommonUtils.initEmoticonsEditText(ekBar.getEtChat());
        ekBar.setAdapter(SimpleCommonUtils.getCommonAdapter(this, emoticonClickListener));
        ekBar.addOnFuncKeyBoardListener(this);
        ekBar.addFuncView(new SimpleUserDefAppsGridView(this));
//        ekBar.addFuncView(new SimpleAppsGridView(this));

        ekBar.getEtChat().setOnSizeChangedListener(new EmoticonsEditText.OnSizeChangedListener() {@Override
            public void onSizeChanged(int w, int h, int oldw, int oldh) {scrollToBottom();
            }});
        ekBar.getBtnSend().setOnClickListener(new View.OnClickListener() {@Override
            public void onClick(View v) {OnSendBtnClick(ekBar.getEtChat().getText().toString());
                ekBar.getEtChat().setText("");
            }});
        ekBar.getBtnVoice().setLongClickable(true);
        ekBar.getBtnVoice().setOnTouchListener(new View.OnTouchListener() {@Override
            public boolean onTouch(View v, MotionEvent event) {
//                ToastUtil.showToast("功能未完善");
                return false;
            }});
        ekBar.getBtnVoice().setOnClickListener(new View.OnClickListener() {@Override
            public void onClick(View v) {
//                ToastUtil.showToast("功能未完善");
            }});

//        ekBar.getEmoticonsToolBarView().addFixedToolItemView(false, R.drawable.icon_add, null, new View.OnClickListener() {
//            @Override
//            public void onClick(View v) {
//                Toast.makeText(Activity_Chat.this, "ADD", Toast.LENGTH_SHORT).show();
//            }
//        });

//        ekBar.getEmoticonsToolBarView().addToolItemView(R.drawable.icon_setting, new View.OnClickListener() {
//            @Override
//            public void onClick(View v) {
//                Toast.makeText(Activity_Chat.this, "SETTING", Toast.LENGTH_SHORT).show();
//            }
//        });
    }//表情点击事件
    EmoticonClickListener emoticonClickListener = new EmoticonClickListener() {@Override
        public void onEmoticonClick(Object o, int actionType, boolean isDelBtn) {if (isDelBtn) {SimpleCommonUtils.delClick(ekBar.getEtChat());
            } else {if(o == null){return;
                }if(actionType == Constants.EMOTICON_CLICK_BIGIMAGE){if(o instanceof EmoticonEntity){OnSendImage(((EmoticonEntity)o).getIconUri());
                    }} else {String content = null;
                    if(o instanceof EmojiBean){content = ((EmojiBean)o).emoji;
                    } else if(o instanceof EmoticonEntity){content = ((EmoticonEntity)o).getContent();
                    }if(TextUtils.isEmpty(content)){return;
                    }int index = ekBar.getEtChat().getSelectionStart();
                    Editable editable = ekBar.getEtChat().getText();
                    editable.insert(index, content);
                }}}};

    private void initListView() {ShowNow();

        uploadmoreadd();

    }//显示界面的联网操作
    private void ShowNow() {try {OkHttpUtils.get(Http_Api.URL_NewReceiver).params("userid",userid).params("taskid",taskid).execute(new StringCallback() {@Override
                        public void onSuccess(String s, Call call, Response response) {
//                            LogUtil.d("返回值",s);
                            data_receiverNews = JsonUtil.parseJsonToBean(s,Data_ReceiverNews.class);
                            allcount = Integer.parseInt(data_receiverNews.getResult());
                            if (data_receiverNews.getNews()!=null){newsBeanList = data_receiverNews.getNews();
                            }//                            LogUtil.d("集合:newsBeanList--",newsBeanList+"");
                            if (allcount>28){//设置最多显示28条数据也就是4页,然后下拉加载历史数据,如果不够28条数据有多少展示多少
                                newsBeanListshort = newsBeanList.subList(newsBeanList.size()-28,newsBeanList.size());

                                myChattingAdapter = new NyChattingListAdapter(ActivityChat.this,userid,newsBeanListshort);
                                isshort = true;
                            }else {myChattingAdapter = new NyChattingListAdapter(ActivityChat.this,userid,newsBeanList);
                                isshort = false;
                            }lvChat.setAdapter(myChattingAdapter);

                        }});
        } catch (Exception e) {e.printStackTrace();
        }}//上拉加载最新数据
    private void uploadmoreadd() {lvChat.setOnScrollListener(new AbsListView.OnScrollListener() {@Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {if (scrollState == SCROLL_STATE_IDLE){ekBar.reset();
                    int lastposition = lvChat.getLastVisiblePosition();//最后一个item的位置
                    if (lastposition == lvChat.getCount() - 1){footview.setPadding(0,0,0,footerHeight);
                        Handler handler = new Handler();
                        handler.postDelayed(new Runnable() {@Override
                            public void run() {try {OkHttpUtils.get(Http_Api.URL_NewReceiver).params("userid",userid).params("taskid",taskid).execute(new StringCallback() {@Override
                                                public void onSuccess(String s, Call call, Response response) {
//                                                    LogUtil.d("返回值",s);
                                                    data_receiverNews = JsonUtil.parseJsonToBean(s,Data_ReceiverNews.class);
                                                    allcount = Integer.parseInt(data_receiverNews.getResult());
                                                    if (newsBeanList!=null){newsBeanList.clear();
                                                    }newsBeanList = data_receiverNews.getNews();

                                                    if (allcount>28){newsBeanListshort = newsBeanList.subList(newsBeanList.size()-28,newsBeanList.size());

                                                        myChattingAdapter = new NyChattingListAdapter(ActivityChat.this,userid,newsBeanListshort);
                                                        isshort = true;
                                                    }else {myChattingAdapter = new NyChattingListAdapter(ActivityChat.this,userid,newsBeanList);
                                                        isshort = false;
                                                    }lvChat.setAdapter(myChattingAdapter);
                                                    myChattingAdapter.notifyDataSetChanged();
                                                    Toast.makeText(ActivityChat.this,"加载完成",Toast.LENGTH_SHORT).show();
//                                                    ToastUtil.showToast("加载完成");

                                                }});
                                } catch (Exception e) {e.printStackTrace();
                                }footview.setPadding(0,-footerHeight,0,0);

                            }},2000);
                    }}}@Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
//                boolean enable = false;
//                if(lvChat != null && lvChat.getChildCount() > 0){
//                    // 检查列表的第一个项目是否可见
//                    boolean firstItemVisible = lvChat.getFirstVisiblePosition() == 0;
//                    // 检查第一个项目的顶部是否可见
//                    boolean topOfFirstItemVisible = lvChat.getChildAt(0).getTop() == 0;
//                    // 启用或禁用刷新布局
//                    enable = firstItemVisible && topOfFirstItemVisible;
//                }
//                uploadmore.setRefreshing(enable);
            }});
    }//用当前时间
    private String getTime() {Date date = new Date(System.currentTimeMillis());
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm ss");
        return dateFormat.format(date);
    }//发送信息联网
    private void OnSendBtnClick(String msg) {if (!TextUtils.isEmpty(msg)) {final Data_ReceiverNews.NewsBean bean = new Data_ReceiverNews.NewsBean();
            bean.setNewscontent(msg);
            bean.setCreatime(getTime());
//            LogUtil.d("------Emoji:",msg);
            try {OkHttpUtils.post(Http_Api.URL_NewSend).params("taskid",taskid).params("userid",userid).params("newscontent",msg).execute(new StringCallback() {@Override
                            public void onSuccess(String s, Call call, Response response) {
//                                LogUtil.d("返回值",s);
                                Data_uploadBack_tag back_tag = JsonUtil.parseJsonToBean(s,Data_uploadBack_tag.class);
                                if(back_tag.getResult().equals("0")){
//                                    ToastUtil.showToast("非法的表情符号!");
                                    Toast.makeText(ActivityChat.this,"非法的表情符号!",Toast.LENGTH_SHORT).show();
                                }else if (back_tag.getResult().equals("1")){
//                                    if (isshort){
//                                        if (bean!= null){
//                                            newsBeanListshort.add(bean);
//                                        }
//
//                                    }else {
//                                        if (bean!=null){
//                                            newsBeanList.add(bean);
//                                        }
//                                    }
//                                    myChattingAdapter.setsendtype(1);
                                    ShowNow();//这里选择重新联网刷新列表,而不是在集合里手动添加数据
                                    myChattingAdapter.notifyDataSetChanged();
                                    scrollToBottom();
                                }}});
            } catch (Exception e) {e.printStackTrace();
            }}}/**
     * 版本号小于21的后退按钮图片
     */
    private void showBack(){//setNavigationIcon必须在setSupportActionBar(toolbar);方法后面加入
        toolbar.setNavigationIcon(R.drawable.ic_back);
        toolbar.setNavigationOnClickListener(new View.OnClickListener() {@Override
            public void onClick(View v) {onBackPressed();
            }});
    }private void OnSendImage(String image) {if (!TextUtils.isEmpty(image)) {OnSendBtnClick("[img]" + image);//给大图片加标记
        }}private void scrollToBottom() {//设置滚动到底部
        lvChat.requestLayout();
        lvChat.post(new Runnable() {@Override
            public void run() {lvChat.setSelection(lvChat.getBottom()+2);
            }});
    }@Override
    public void OnFuncPop(int i) {scrollToBottom();
    }@Override
    public void OnFuncClose() {}@Override
    protected void onPause() {super.onPause();
        ekBar.reset();
    }
}



最后 感谢阅读 demo下载地址
https://github.com/PangHaHa12138/TestChatdemo

安卓开发-最简单快速的仿微信聊天实现-附赠微信原生表情,QQ原生表情相关推荐

  1. ecshop二次开发的简单快速入门教程

    ecshop二次开发的简单快速入门教程,具体请下载文档 ecshop_dwt_lbi_详解.rar (106 KB) 下载次数: 9 转载于:https://my.oschina.net/u/3360 ...

  2. java项目_JNPF快速开发平台-简单快速高效开发java项目

    ◆JNPF快速开发平台 JNPF快速开发平台采用前后端分离技术.采用B/S架构开发,形成一站式开发多端(APP+PC)使用. 使用JNPF开发平台可以简单.快速.高效的构建各种类型java项目. ◆J ...

  3. python 微信机器人教程_Python创建微信机器人(附赠Python视频教程)

    微信,一个日活10亿的超级app,不仅在国内社交独领风骚,在国外社交也同样占有一席之地.今天我们要讲的便是如何用Python来做一个微信聊天机器人,突然想起鲁迅先生曾经说过的一句话: 因为是微信机器人 ...

  4. android手机 恢复微信图片,OPPO R9s Plus怎么找回微信聊天图片?恢复微信误删图片方法推荐...

    过去我们可能都会使用数码相机来拍摄图片,现在我们都已经习惯了用手机来进行拍摄,并且大家都乐于将精美的照片通过微信来进行分享.那么如果我们不慎清空了微信的缓存,或者说并未保存这些照片,那么怎么找回微信聊 ...

  5. 微信聊天新技能!微信聊天彩色字体!个性炫酷~

    上一篇关于微信聊天界面个性签名的文章推送以后,有小伙伴留言问微信聊天彩色字体设置方法,就为大家分享一下微信彩色字体~ 首先,下载安装软件[微信炫字体].软件的功能也比较简单,没有任何复杂的设置. 软件 ...

  6. python 微信聊天机器人_python操作微信自动发消息的实现(微信聊天机器人)

    前言 最近在学习python,发现一个微信自动发消息的小demo感觉很有意思,试了一下,不成功,因为demo中用的是itchat这个库来操作微信,而这个库是通过微信网页版来操作微信的,现在微信网页版已 ...

  7. 微信聊天框如何隐藏(微信教程分享)

    微信是很多人经常使用的社交平台.其中还包括很多隐私内容.如果你担心别人借手机的时候翻来覆去.可以隐藏指定的聊天对话框.所以其他人在聊天界面找不到.你知道怎么设置吗?快来一起看看吧. 微信聊天框如何隐藏 ...

  8. 【Android】快速实现仿美团选择城市界面,微信通讯录界面

    概述 本文是这个系列的第三篇,不出意外也是终结篇.因为使用经过重构后的控件已经可以快速实现市面上带 索引导航.悬停分组的列表界面了. 在前两篇里,我们从0开始,一步一步实现了仿微信通讯录.饿了么选餐界 ...

  9. 【安卓开发】简单记账app功能实现开发-期末大作业个人总结

    说在前面: 由于这一次的大作业涉及到的代码部分过于长,所以博客里不放相关代码: 工程&apk&记账app原型&素材资源链接:https://download.csdn.net/ ...

最新文章

  1. SVM原理详细图文教程来了!一行代码自动选择核函数,还有模型实用工具
  2. Java多线程1:进程与线程
  3. 背景建模与前景检测3(Background Generation And Foreground Detection Phase 3)
  4. Triangular Pastures (二维01背包)
  5. 循环神经网络(RNN)模型与前向反向传播算法
  6. 中国计算机学会CCF推荐国际学术会议和期刊目录-计算机图形学与多媒体
  7. TensorFlowIO操作(一)----线程和队列
  8. alphac测试和bata测试区别_电缆识别仪与电缆故障测试仪的区别
  9. 312. Burst Balloons 戳气球
  10. 【JavaScript】如何将JS中的数据提交到Servlet服务器中
  11. python获取股票数据_python根据股票代码获取当前数据
  12. Oracle函数translate()的用法
  13. 会计云课堂实名认证后怎么更改_离职了,税务局的会计信息还是我,老板就不更改,怎么办?...
  14. 自己实现一个右滑删除的ListView
  15. Windows:打开MSDTC,恢复Windows任务栏,查看windows日志,打开远程桌面,打开Services,资源监控...
  16. 人物关系图谱:ECharts 实现
  17. 【初学数据结构系列】 顺序表的实现——通讯录
  18. 定个小目标——做一款自己的游戏
  19. ubuntu php mysql 乱码,ubuntu 服务器字符乱码问题
  20. 教室预约APP系统(基于uni-app框架)毕业设计毕业论文开题报告参考(1)系统功能

热门文章

  1. JAVA random 缺陷_Random在高并发下的缺陷以及JUC对其的优化
  2. C#实现向手机发送验证码短信
  3. Rust实现:从一组纸牌中挑选Winner纸牌
  4. Journal of Electronic Imaging 投稿分享
  5. stm32f103移植ucosIII系统
  6. xHiveAI Jetson NX盒子:jpeg图片编码
  7. knex mysql 操作_mysql – 使用knex.js的我的Sql Alter表
  8. 公式编辑器如何使用详细图解
  9. 123 白沙 李恒福 今見도(才+壽)人而成逆賊
  10. 乐博机器人Arduino周五班级,入门课程,碰撞开关控制灯闪烁