关于自定义表情键盘...

在做输入的时候,除了可以输入系统的表情符号,项目中通常还要求输入表情图片

表情图片

1、正则表达式

一个正则表达式,就是一串有特定意义的字符,首先要编译成为一个Pattern对象,然后使用matcher()方法来生成一个Matcher实例,接着便可以使用该 Matcher实例对目标字符串进行匹配工作。

  • Pattern类:

  • static Pattern compile(String regularExpression):将给定的正则表达式编译并赋予给Pattern类

  • Matcher matcher(CharSequence input) :生成一个match对象

  • Matcher类:

  • boolean find()
    尝试在目标字符串里查找下一个匹配子串,如果没有,返回false

  • String group()
    返回当前查找,所组匹配的子串

    private SpannableString dealExpression(Context context) {
        ...
        // 正则表达式比配字符串里是否含有表情,如: 我好[开心]啊
        String zhengze = "\\[[^\\]]+\\]";
        // 通过传入的正则表达式来生成一个pattern
        Pattern pattern = Pattern.compile(zhengze, Pattern.CASE_INSENSITIVE);
        //得到matcher
        Matcher matcher = pattern.matcher(spannableString);
        //尝试在目标字符串里查找下一个匹配子串,如果没有,返回false
        while (matcher.find()) {
              //得到匹配的子串
             String key = matcher.group();
             ...
        }
    }


2、表情文字到表情图片的转变

如果将表情字符 212[开心]2[调皮] ,转变成表情图片,如下图,可以使用SpannableString。

  • 建立图片名称---资源ID的键值对,得到了名称,就得到了资源ID
  • 通过正则表达式,得到匹配某种规则的字符(图片名称)
  • 根据得到的资源文件ID,生成bitmap,通过Span进行包装
  • 使用SpannableString,把某个区间的字符,替换成资源图片

构造函数如下:

    public ImageSpan(Drawable d, int verticalAlignment) {
        ...
    }
    public ImageSpan(Context context, Bitmap b, int verticalAlignment) {
        ...
    }

    public void setSpan(Object what, int start, int end, int flags) {
        ...
    }

注意:
1、verticalAlignment有两个值:

  • ALIGN_BOTTOM:和baseline下面的descender对齐
  • ALIGN_BASELINE:和text的baseline对齐
    默认是ALIGN_BOTTOM。

2、spannableString的setSpan:

  • what:这里传入样式,如:ImageSpan等
  • start:样式作用在文本的起始点(产生的作用包括该点,从0开始)
  • end:样式作用在文本的结束点(产生的作用不包括该点,从0开始)
  • flags:是否包含start或者end点的字符
    flags的选项在Spanned接口中,分别为:
    • SPAN_INCLUSIVE_EXCLUSIVE:包含start,不包含end
    • SPAN_INCLUSIVE_INCLUSIVE:start,end都包含
    • SPAN_EXCLUSIVE_EXCLUSIVE:start,end都不包含
    • SPAN_EXCLUSIVE_INCLUSIVE:start不包含,end包含

使用如下:

        Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resId, options);
        ImageSpan imageSpan = new ImageSpan(context,bitmap);
        //将start到end之间的字符替换成目标图片
        spannableString.setSpan(imageSpan, start, end,Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
        Drawable res=context.getResources().getDrawable(R.drawable.test);
        res.setBounds(0,0,res.getIntrinsicWidth(),res.getIntrinsicHeight());
        ImageSpan span=new ImageSpan(res,ImageSpan.ALIGN_BOTTOM);
        //ForegroundColorSpan span=new ForegroundColorSpan(resColor);
        spannableString.setSpan(span,start,end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        return spannableString;

代码如下:

    public SpannableString getSpannableString(Context context,String str){
        initBitmapOption(context);
        try {
            dealExpression(context, str);
        } catch (Exception e) {
            Log.e("dealExpression", e.getMessage());
        }
        return spannableString;
    }

    private void dealExpression(Context context,String str) {
        SpannableString spannableString=new SpannableString(str);
        // 正则表达式比配字符串里是否含有表情,如: 我好[开心]啊
        String zhengze = "\\[[^\\]]+\\]";
        // 通过传入的正则表达式来生成一个pattern
        Pattern pattern = Pattern.compile(zhengze, Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(spannableString);
        while (matcher.find()) {
            String key = matcher.group();
            if(AppUtil.getInstance().getFaceMapHX().containsKey(key)){
                int resId = AppUtil.getInstance().getFaceMapHX().get(key);
                if (resId != 0) {
                    Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resId, options);
                    if(bitmap!=null){
                        WeakReference<Bitmap> weakReference=new WeakReference<>(bitmap);
                        // 通过图片资源id来得到bitmap,用一个ImageSpan来包装
                        ImageSpan imageSpan = new ImageSpan(context,weakReference.get());
                        // 计算该图片名字的长度,也就是要替换的字符串的长度
                        int end = matcher.start() + key.length();
                        // 将该图片替换字符串中规定的位置中
                        spannableString.setSpan(imageSpan, matcher.start(), end,
                                Spannable.SPAN_INCLUSIVE_EXCLUSIVE);       
                    }
                }
            }
        }
    }

  private void initFaceMapHX() {
        mFaceMapHX.put("[):]", R.drawable.hx_1);
        ...
        }

3、定义表情键盘

通过在工具类中建立<资源名字---资源ID>的映射列表,可以很方便的代码的各处得到他们:

    private Map<String, Integer> mFaceMap = new LinkedHashMap<String, Integer>();
    public Map<String, Integer> getFaceMap() {
        if (!mFaceMap.isEmpty())
            return mFaceMap;
        mFaceMap.put("[):]", R.drawable.hx_1);
        ...
        return mFaceMap;
    }

1、表情键盘的布局文件

表情键盘
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:background="#ffffff"
              android:orientation="vertical">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:background="@drawable/shape_take_photo"
        android:orientation="horizontal">
        <FrameLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1">
            <EditText
                android:id="@+id/editText"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:minHeight="48dp"
                android:text=""
                android:maxLines="3"
                android:gravity="center_vertical"
                android:background="@drawable/char_bottombar_input_selector"
                android:paddingRight="40dp"/>
            <ImageView
                android:id="@+id/showFace"
                android:layout_width="44dp"
                android:layout_height="44dp"
                android:layout_gravity="center_vertical|right"
                android:paddingBottom="10dp"
                android:paddingTop="10dp"
                android:src="@drawable/chat_bottombar_icon_face_selector"/>
        </FrameLayout>
    </LinearLayout>

    <LinearLayout
        android:id="@+id/viewPagerInfo"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:visibility="gone">

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

        <com.hqgj.mylibrary.view.indicator.CirclePageIndicator
            android:id="@+id/indicator"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"/>

    </LinearLayout>

</LinearLayout>       

2、自定义ViewGroup,初始化刚刚的布局文件,为VIewPager指定adapter

  • 将所有表情图片的名称,存到keyList中(备用)
  • 使用GridVIew填充ViewPager
  • 使用资源图片填充GridView,为其adapter传入currentPage,能够在其中得到当前页资源图片ID
  • 添加点击事件

代码如下:


public class FaceContainerView extends LinearLayout  {
    //存储表情字符名称,如[开心]
    private ArrayList<String> keyList;
    //view是每一页,
    private ArrayList<View> faceViews;
    ...
    public FaceContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
        ...
        keyList=new ArrayList<>();
        //得到表情字符名称
        if(AppUtil.getInstance().getFaceMap()!=null){
            Set<String > keySet= AppUtil.getInstance().getFaceMap().keySet();
            if(!keySet.isEmpty()){
                keyList.addAll(keySet);
            }
        }
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        ...
        //初始化表情键盘
        initFaceView();
    }

    private void initFaceView() {
        faceViews=new ArrayList<>();
        //NUM_PAGE:页数,一页的图片数=7*3-1
        for(int index=0;index< AppUtil.getInstance().NUM_PAGE;index++){
            faceViews.add(getGridView(index));
        }
        FacePagerAdapter facePagerAdapter =new FacePagerAdapter(faceViews);
        viewPager.setAdapter(facePagerAdapter);
        viewPager.setCurrentItem(currentPage);
        indicator.setViewPager(viewPager);
        indicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            ...
        });
        ...
    }

    private View getGridView( int page) {
        GridView gridView=new GridView(context);
        gridView.setNumColumns(7);
        ...
        FaceAdapter faceAdapter=new FaceAdapter(context, page);
        gridView.setAdapter(faceAdapter);
        ...
        return gridView;
    }
}

GridView的adapter


public class FaceAdapter extends BaseAdapter {

    //当前页
    private int currentPage;
    //所有的<资源名字---资源ID>列表
    private Map<String, Integer> faceMap ;
    //所有的资源ID
    private ArrayList<Integer> imageRes=new ArrayList<>();
    ...

    public FaceAdapter(Context context, int currentPage) {
        ...
        faceMap= AppUtil.getInstance().getFaceMapHX();
        initDate();
    }


    private void initDate() {
        if(faceMap!=null){
            for(Map.Entry<String ,Integer> entry:faceMap.entrySet()){
                imageRes.add(entry.getValue());
            }
        }
    }

    @Override
    public int getCount() {
        return AppUtil.getInstance().NUM + 1;
    }

    @Override
    public Object getItem(int position) {
        return faceInCurrentPage.get(position);
    }

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

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

        <...viewHolder和convertView...>
        
        if (position == AppUtil.getInstance().NUM) {
            //每一页最后那个删除图片
            ...
        } else {
            int count = AppUtil.getInstance().NUM * currentPage + position;
            if (faceMap!=null && count < faceMap.size()) {
                int res=imageRes.get(count);
                ...
            } else {
                viewHolder.faceIV.setImageDrawable(null);
                viewHolder.faceIV.setBackgroundDrawable(null);
                viewHolder.faceIV.setEnabled(false);
            }
        }
        return convertView;
    }

    ...
}

4、管理软键盘

1、软键盘本质是一个Dialog,可以通过windowSoftInputMode, 设置Activity主窗口与软键盘的交互模式。它包括两部分:

  • 对Activity窗口的调整(以便腾出空间展示软键盘)

  • adjustUnspecified:在是默认的,系统会根据界面选择不同的模式。如果有滚动列表,默认是adjustResize;如果没有,默认是adjustPan。

  • adjustResize:系统总是调整屏幕的大小用以保证软键盘的显示空间( 系统没有移动布局),如果布局不可以滚动,可能会导致输入框不在视野范围内 。

  • adjustPan:系统会通过布局的移动,来保证用户要进行输入的输入框、肯定在用户的视野范围里面,从而让用户看到自己输入的内容。

  • 对软键盘的状态控制,即控制软键盘是隐藏还是显示

  • stateUnspecified:默认的,系统会根据界面采取相应的软键盘的显示模式。

  • stateUnchanged:当前界面的软键盘状态,取决于上一个界面的软键盘状态,无论是隐藏还是显示。

  • stateHidden:软键盘总是被隐藏,不管是否有输入的需求。

  • stateAlwaysHidden:软键盘总是被隐藏,和stateHidden不同的是,当我们跳转到下个界面,如果下个页面的软键盘是显示的,而我们再次回来的时候,软键盘就会隐藏起来。

  • stateVisible:软键盘总是可见的,即使在界面上没有输入框的情况下也可以强制弹出来出来。

  • stateAlwaysVisible:软键盘总是可见的,和stateVisible不同的是,当我们跳转到下个界面,如果下个页面软键盘是隐藏的,而我们再次回来的时候,软键盘就会显示出来。

adjustPan
adjustResize

adjustResize,可以调整屏幕的大小,可以实现软键盘顶起页面的效果
代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    xmlns:...
    android:orientation="vertical"
    android:fitsSystemWindows="true">
    <EditText
        android:hint="one"
        android:id="@+id/editText3"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="20dp"
        android:layout_weight="1"/>
    <Button
        android:id="@+id/btn"
        android:text="下一页"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>
adjustResize,实现软键盘顶起页面的效果

但是再activity背景透明的情况下,如果模式为 adjustResize ,会再切换键盘的瞬间,显示前一个页面,使用 adjustPan就不会。

2、在隐藏软键盘的时候,可以传入一个ResultReceiver对象。
这个对象需要传入一个Handler,作用是控制回调函数执行在创建Handler的线程。如果这个Handler是null,则回调会在主线程执行。

通过传入ResultReceiver对象,就可以实现,在输入法键盘隐藏之后,回调onReceiveResult,显示表情键盘:

在输入法键盘隐藏之后,显示表情键盘
        InputMethodManager inputMethodManager= (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);

        inputMethodManager.hideSoftInputFromWindow(editText.getWindowToken(), 0, new ResultReceiver(null){
            @Override
            protected void onReceiveResult(int resultCode, Bundle resultData) {
                ...
            }
        });

参考:彻底搞定Android开发中软键盘的常见问题Android中ResultReceiver使用

关于.9图片

代码:FaceDemo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容