美文网首页
关于自定义表情键盘...

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

作者: i冰点 | 来源:发表于2016-08-01 21:53 被阅读365次

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

    表情图片

    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

    相关文章

      网友评论

          本文标题:关于自定义表情键盘...

          本文链接:https://www.haomeiwen.com/subject/flhfsttx.html