android 图文混排——文字适配表情(资源图片、sd卡图片)

我们在做项目的时候会遇到很多地方需要使用图文混排的功能。
特别是聊天、论坛和贴吧之类的。

这里我主要以EditText这个控件来说,TextView同样适用。因为EditText上实现图文混排比Textview上要复杂,且所实现的功能Textview上同样适用。

这里利用了正则表达式和Spannable来进行图文混排。

1,BiaoQingData 添加图片资源,预备使用

 /**
     * 将资源类设置成单例,全局只需要一个对象即可
     */
    private BiaoQingData() {
        for (int i = 0; i < emojis.length; i++) {
            biaoqingMap.put(Pattern.compile(Pattern.quote(emojis[i])), icons[i]);
        }
    }

    private static class BiaoQingDatas {
        private static BiaoQingData biaoqingUtil = new BiaoQingData();
    }

    public static BiaoQingData getInstance() {
        return BiaoQingDatas.biaoqingUtil;
    }

    /**
     * 表情集合
     */
    public final Map<Pattern, Object> biaoqingMap = new HashMap<>();

    //表情资源对应的id
    public final String ee_1 = "[ee_1]";
    public final String ee_2 = "[ee_2]";
    public final String ee_3 = "[ee_3]";
    public final String ee_4 = "[ee_4]";
    public final String ee_5 = "[ee_5]";
    public final String ee_6 = "[ee_6]";
    public final String ee_7 = "[ee_7]";
    public final String ee_8 = "[ee_8]";
    public final String ee_9 = "[ee_9]";
    public final String ee_10 = "[ee_10]";


    //存放资源对应id的数组
    public String[] emojis = new String[]{
            ee_1,
            ee_2,
            ee_3,
            ee_4,
            ee_5,
            ee_6,
            ee_7,
            ee_8,
            ee_9,
            ee_10,

    };

    //存放资源的数组
    public int[] icons = new int[]{
            R.drawable.ee_1,
            R.drawable.ee_2,
            R.drawable.ee_3,
            R.drawable.ee_4,
            R.drawable.ee_5,
            R.drawable.ee_6,
            R.drawable.ee_7,
            R.drawable.ee_8,
            R.drawable.ee_9,
            R.drawable.ee_10,

    };

2,EditTexts 自定义EditView,监听复制、粘贴动作中的表情替换


/**
 * 重写EditText的监听事件,目的是为了监听粘贴
 */
public class EditTexts extends EditText {
    private boolean isCopy;//是否是复制的文本

    public EditTexts(Context context) {
        super(context);
    }

    public EditTexts(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public boolean isCopy() {
        return isCopy;
    }

    public void setCopy(boolean copy) {
        isCopy = copy;
    }

    @Override
    public boolean onTextContextMenuItem(int id) {
        /**
         * id:16908319
         全选
         id:16908328
         选择
         id:16908320
         剪贴
         id:16908321
         复制
         id:16908322
         粘贴
         id:16908324
         输入法
         **/
        if (id == 16908322) {
            setCopy(true);
        }
        return super.onTextContextMenuItem(id);
    }

}

3,BiaoqingUtil 最关键的适配表情类

public class BiaoqingUtil {

    /**
     * Pattern.compile(String str);//编译正则表达式,在此过程中创建一个新的模式实例。
     * Pattern.quote(String str);//引用给定字符串使用" \ Q "和" \ E”,这样所有其它元字符失去他们的特殊意义。
     */
    private BiaoqingUtil() {
    }

    private static class BiaoqingUtils {
        private static BiaoqingUtil biaoqingUtil = new BiaoqingUtil();
    }

    public static BiaoqingUtil getInstance() {
        return BiaoqingUtils.biaoqingUtil;
    }



    /**
     * 仅作参考,不是方法,不可调用
     *
     * @param context
     * @param message
     * @param editText
     */
    private void getTextStr(Context context, String message, EditText editText) {
        Spannable span = showBiaoqing(context, message);
        // 设置内容
        editText.setText(span, TextView.BufferType.SPANNABLE);
    }

    /**
     * 适配所有表情
     *
     * @param context
     * @param text
     * @return 标记出所有的表情符号
     */
    public Spannable showBiaoqing(Context context, CharSequence text) {
        //获得工厂类的SpannableString对象
        Spannable spannable = Spannable.Factory.getInstance().newSpannable(text);
        addSmiles(context, spannable);
        return spannable;
    }

    /**
     * 添加表情
     *
     * @param context
     * @return
     */
    private void addBiaoqing(Context context, EditText editText, int drawableId, String biaoqingKey) {
        SpannableString spannableString = new SpannableString(biaoqingKey);
        //获取Drawable资源
        Drawable drawable = context.getResources().getDrawable(drawableId);
        //这句话必须,不然图片不显示
        drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
        //创建ImageSpan
        ImageSpan span = new ImageSpan(drawable, ImageSpan.ALIGN_BASELINE);
        //用ImageSpan替换文本
        spannableString.setSpan(span, 0, spannableString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        Editable e = editText.getText();
        int st = editText.getSelectionStart();
        int en = editText.getSelectionEnd();
        e.replace(st, en, spannableString);
    }

    /**
     * 删除表情
     */
    public void removeBiaoqing(EditText editText) {
        int selectionStart = editText.getSelectionStart();// 获取光标的位置
        if (selectionStart > 0) {
            String body = editText.getText().toString();
            if (!TextUtils.isEmpty(body)) {
                String tempStr = body.substring(0, selectionStart);
                if (tempStr.contains("[")) {//说明是资源表情
                    int i = tempStr.lastIndexOf("[");// 获取最后一个表情的位置
                    if (i != -1) {
                        String cs = tempStr.substring(i, selectionStart);
                        if (cs.indexOf("[ee_") != -1) {// 判断是否是一个表情
                            editText.getEditableText().delete(i, selectionStart);
                            return;
                        }
                    }
                    editText.getEditableText().delete(tempStr.length() - 1, selectionStart);
                } else if (tempStr.contains("file:///")) {//说明是本地文件表情
                    int i = tempStr.lastIndexOf("file:///");// 获取最后一个表情的位置
                    if (i != -1) {
                        String cs = tempStr.substring(i, selectionStart);
                        if (cs.indexOf(";") != -1) {// 判断是否是一个表情
                            editText.getEditableText().delete(i, selectionStart);
                            return;
                        }
                    }
                    editText.getEditableText().delete(tempStr.length() - 1, selectionStart);
                } else {
                    editText.getEditableText().delete(tempStr.length() - 1, selectionStart);
                }
            }
        }
    }

    /**
     * 供外部调用的添加表情的方法
     *
     * @param context
     * @param editText
     * @param viewId
     */
    public void addBiaoqing(Context context, EditText editText, int viewId) {
        String ee = null;
        //取出map集合中的键和值
        switch (viewId) {
            case R.id.ee_del:
                //这个图片是一个删除按钮,按下这个图片并不是为了添加图片,而是为了删除光标前一个图片
                BiaoqingUtil.getInstance().removeBiaoqing(editText);
                break;
            case R.id.ee_1:
                ee = BiaoQingData.getInstance().ee_1;
                break;
            case R.id.ee_2:
                ee = BiaoQingData.getInstance().ee_2;
                break;
            case R.id.ee_3:
                ee = BiaoQingData.getInstance().ee_3;
                break;
            case R.id.ee_4:
                ee = BiaoQingData.getInstance().ee_4;
                break;
            case R.id.ee_5:
                ee = BiaoQingData.getInstance().ee_5;
                break;
            case R.id.ee_6:
                ee = BiaoQingData.getInstance().ee_6;
                break;
            case R.id.ee_7:
                ee = BiaoQingData.getInstance().ee_7;
                break;
            case R.id.ee_8:
                ee = BiaoQingData.getInstance().ee_8;
                break;
            case R.id.ee_9:
                ee = BiaoQingData.getInstance().ee_9;
                break;
            case R.id.ee_10:
                ee = BiaoQingData.getInstance().ee_10;
                break;
        }
        if (!TextUtils.isEmpty(ee)) {
            for (Map.Entry<Pattern, Object> entry : BiaoQingData.getInstance().biaoqingMap.entrySet()) {
                Matcher matcher = entry.getKey().matcher(ee);
                if (matcher.find()) {
                    addBiaoqing(context, editText, (Integer) entry.getValue(), ee);
                }
            }
        }
    }

    /**
     * 用表情代替现有spannable
     * replace existing spannable with smiles
     *
     * @param context
     * @param spannable
     * @return
     */
    private void addSmiles(Context context, Spannable spannable) {
        //取出map集合中的键和值,map的键是正则表达式对象
        for (Map.Entry<Pattern, Object> entry : BiaoQingData.getInstance().biaoqingMap.entrySet()) {
            //解析正则表达式,这里拿到的是这个表情的具体正则表达式,例如[ee_00]代表笑脸,则用户输入的文字中是否出现了[ee_00],如果出现,则替换成表情
            Matcher matcher = entry.getKey().matcher(spannable);
            while (matcher.find()) {
                boolean set = true;
                /**将工厂类的Spannable确定为imageSpan类型,并且截取span以正则表达式“[xxx]”为规定*/
                for (ImageSpan span : spannable.getSpans(matcher.start(), matcher.end(), ImageSpan.class)) {
                    /**如果在规定的正则表达式“[xxx]”中,从matcher.start开始到结束属于这个正则表达式则说明该字符串是一个表情*/
                    if (spannable.getSpanStart(span) >= matcher.start() && spannable.getSpanEnd(span) <= matcher.end()) {
                        //如果是表情,则从这个sannable中删除这段字符串
                        spannable.removeSpan(span);
                    } else {
                        set = false;
                    }
                }
                //如果spannbale中包含表情,则替换表情
                if (set) {
                    Object value = entry.getValue();
                    //判断value是不是来自本地sd卡(String为绝对路径)并且不是来自网络
                    if (value instanceof String && !((String) value).startsWith("http")) {
                        String filePath = value.toString();
                        filePath = filePath.substring(filePath.indexOf("file:///"), filePath.length() - 1);
                        File file = new File(filePath);
                        //如果文件路径不是文件夹并且存在则替换表情
                        if (file.exists() && !file.isDirectory()) {
                            spannable.setSpan(new ImageSpan(context, Uri.fromFile(file)),
                                    matcher.start(), matcher.end(),
                                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                        }
                    } else {
                        //如果表情不来自本地文件,则适配资源表情,资源文件是int型
                        spannable.setSpan(new ImageSpan(context, (int) value), matcher.start(), matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    }
                }
            }
        }
    }
}

这个工具类中,我已经将注释写的很清楚了,大家可以根据注释来理解代码;这个图文混排的代码我是参照了环信sdk的图文混排来加以优化的。

外界只需要调用方法:

showBiaoqing(Context context, CharSequence text);//将整段文字传入,适配这段文字中的所有表情。
addBiaoqing(Context context, EditText editText, int viewId);//添加表情,传入上下文,控件,以及资源id就可以了
removeBiaoqing(EditText editText);//清除表情,根据用户所点击的光标的位置来删除,传入控件即可

注意:
其中除了资源文件的适配以外还加入了sd卡资源适配。sd卡资源适配,有些地方需要加以修改。
如果你读懂了这个图文混排,相信很容易就能晚上sd卡资源适配。
需要注意的是,就连qq也没有适配sd卡图片的图文混排,所以这个有一定难度,但是是可以做出来的。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值