Android富文本编辑器,图片、视频、文字混合编辑

一、添加依赖

开源框架xrichtext 只支持图文混编,我们在之上进行扩展。另采用 jiaozivideoplayer 作为播放器。

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}

dependencies {
    implementation 'com.github.sendtion:XRichText:1.9.1'
    implementation 'cn.jzvd:jiaozivideoplayer:7.0.4'

}

二、扩展实现

我们通过修改两个主要类来实现功能:RichTextEditor RichTextView。

由于许多重要方法都设为private,通过继承无法直接使用,所有直接复制类的所有内容,到一个新的类中,‘

并加上我们的方法。

RichTextView

public RelativeLayout createJzvdStd(String videoPath, String videoTitle, String thumbImageUrl) {

    RelativeLayout relativeLayout = (RelativeLayout) inflater.inflate(R.layout.jzvd_std, null);
    JzvdStd jzvdStd = relativeLayout.findViewById(R.id.jzvStd);
    jzvdStd.setTag(viewTagIndex++);
    jzvdStd.setUp(videoPath, videoTitle);
    ImageView thumbImageView = jzvdStd.thumbImageView;
    if (!TextUtils.isEmpty(thumbImageUrl))
        VolleyUtil.getInstance().sendImageLoader(thumbImageUrl, thumbImageView);
    return relativeLayout;
}

public void addJzvdStdAtIndex(final int index, String videoPath, String videoTitle, String thumbImageUrl) {
    try {
        RelativeLayout jzvdStd = createJzvdStd(videoPath, videoTitle, thumbImageUrl);
        ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(
                LayoutParams.MATCH_PARENT, 500);
        jzvdStd.setLayoutParams(lp);
        allLayout.addView(jzvdStd, index);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

 

RichTextEditor

 

public RelativeLayout createJzvdStd(String videoPath, String videoTitle, String thumbImageUrl) {
        RelativeLayout relativeLayout = (RelativeLayout) inflater.inflate(R.layout.jzvd_std, null);
        JzvdStd jzvdStd = relativeLayout.findViewById(R.id.jzvStd);
        relativeLayout.setTag(viewTagIndex++);
        jzvdStd.setUp(videoPath, videoTitle);
        ImageView thumbImageView = jzvdStd.thumbImageView;
        if (!TextUtils.isEmpty(thumbImageUrl))
            VolleyUtil.getInstance().sendImageLoader(thumbImageUrl, thumbImageView);
        return relativeLayout;
    }

    public void insertJzvdStd(String videoPath, String videoTitle, String thumbImageUrl) {

        if (TextUtils.isEmpty(videoPath)) {
            return;
        }
        try {
            //lastFocusEdit获取焦点的EditText
            String lastEditStr = lastFocusEdit.getText().toString();
            int cursorIndex = lastFocusEdit.getSelectionStart();//获取光标所在位置
            String editStr1 = lastEditStr.substring(0, cursorIndex).trim();//获取光标前面的字符串
            String editStr2 = lastEditStr.substring(cursorIndex).trim();//获取光标后的字符串
            int lastEditIndex = allLayout.indexOfChild(lastFocusEdit);//获取焦点的EditText所在位置

            if (lastEditStr.length() == 0) {
                //如果当前获取焦点的EditText为空,直接在EditText下方插入,并且插入空的EditText
                addEditTextAtIndex(lastEditIndex + 1, "");
                addJzvdStdAtIndex(lastEditIndex + 1, videoPath, videoTitle, thumbImageUrl);
            } else if (editStr1.length() == 0) {
                //如果光标已经顶在了editText的最前面,则直接插入,并且EditText下移即可
                addJzvdStdAtIndex(lastEditIndex, videoPath, videoTitle, thumbImageUrl);
                //同时插入一个空的EditText,防止插入多张图片无法写文字
                addEditTextAtIndex(lastEditIndex + 1, "");
            } else if (editStr2.length() == 0) {
                // 如果光标已经顶在了editText的最末端,则需要添加新的imageView和EditText
                addEditTextAtIndex(lastEditIndex + 1, "");
                addJzvdStdAtIndex(lastEditIndex + 1, videoPath, videoTitle, thumbImageUrl);
            } else {
                //如果光标已经顶在了editText的最中间,则需要分割字符串,分割成两个EditText,并在两个EditText中间插入
                //把光标前面的字符串保留,设置给当前获得焦点的EditText(此为分割出来的第一个EditText)
                lastFocusEdit.setText(editStr1);
                //把光标后面的字符串放在新创建的EditText中(此为分割出来的第二个EditText)
                addEditTextAtIndex(lastEditIndex + 1, editStr2);
                //在第二个EditText的位置插入一个空的EditText,以便连续插入时,有空间写文字,第二个EditText下移
                addEditTextAtIndex(lastEditIndex + 1, "");
                //在空的EditText的位置插入布局,空的EditText下移
                addJzvdStdAtIndex(lastEditIndex + 1, videoPath, videoTitle, thumbImageUrl);
            }
            hideKeyBoard();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void addJzvdStdAtIndex(final int index, String videoPath, String videoTitle, String thumbImageUrl) {
        if (TextUtils.isEmpty(videoPath)) {
            return;
        }
        try {

            RelativeLayout jzvdStd = createJzvdStd(videoPath, videoTitle, thumbImageUrl);
            ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(
                    LayoutParams.MATCH_PARENT, 500);
            jzvdStd.setLayoutParams(lp);
            jzvdStd.findViewById(R.id.fullscreen).setEnabled(false);
            allLayout.addView(jzvdStd, index);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

并且特别主要要修改 buildEditData,才能生成正确的数据,

public List<RichTextEditor2.EditData> buildEditData() {
    List<RichTextEditor2.EditData> dataList = new ArrayList<RichTextEditor2.EditData>();
    try {
        int num = allLayout.getChildCount();
        for (int index = 0; index < num; index++) {
            View itemView = allLayout.getChildAt(index);
            RichTextEditor2.EditData itemData = new RichTextEditor2.EditData();
            if (itemView instanceof EditText) {
                EditText item = (EditText) itemView;
                itemData.inputStr = item.getText().toString();
            } else if (itemView instanceof RelativeLayout) {
                JzvdStd jzvStd = itemView.findViewById(R.id.jzvStd);
                if (jzvStd != null) {
                    String currentUrl = (String) jzvStd.jzDataSource.getCurrentUrl();
                    itemData.videoPath = currentUrl;
                } else {
                    DataImageView item = (DataImageView) itemView.findViewById(R.id.edit_imageView);
                    itemData.imagePath = item.getAbsolutePath();
                    //itemData.bitmap = item.getBitmap();//去掉这个防止bitmap一直被占用,导致内存溢出
                }
            }
            dataList.add(itemData);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }

    return dataList;
}

 

三、使用方法

把原本的类替换成我们修改之后的类后,图文使用方式不变!

RichTextEditor插入视频:

publishContent.insertJzvdStd(jsonObject.getString("filePath"), "", "");//参数为 视频地址,视频标题,封面地址

 

获取RichTextEditor生成的html数据:

private String getEditData() {
    List<RichTextEditor2.EditData> editList = publishContent.buildEditData();
    StringBuffer content = new StringBuffer();
    for (RichTextEditor2.EditData itemData : editList) {
        if (itemData.inputStr != null) {
            content.append(itemData.inputStr);
        } else if (itemData.imagePath != null) {
            content.append("<img src=\"").append(itemData.imagePath).append("\"/>");
        } else if (itemData.videoPath != null) {
            content.append("<video controls=\\\"controls\\\" autoplay=\\\"autoplay\\\" width=\\\"100%\\\" height=\\\"auto\\\"><source src=\\\"").append(itemData.videoPath).append("\\\" type=\\\"video/ogg\\\"/>Your browser does not support the video tag.</video>");
        }
        content.append("<br>");
    }
    return content.toString();
}

 

在RichTextView展示数据:

protected void showEditData(String content) {
    postInfoContent.clearAllLayout();
    List<String> textList = StringUtils.cutStringByImgTag(content);
    for (int i = 0; i < textList.size(); i++) {
        String text = textList.get(i);
        if (text.contains("<img")) {
            String imagePath = StringUtils.getSrc(text, StringUtils.IMAGE);
            postInfoContent.measure(0, 0);
            postInfoContent.addImageViewAtIndex(postInfoContent.getLastIndex(), imagePath);
        } else if (text.contains("<source")) {
            String videoPath = StringUtils.getSrc(text, StringUtils.VIDEO);
            postInfoContent.measure(0, 0);
            postInfoContent.addJzvdStdAtIndex(postInfoContent.getLastIndex(), videoPath, "", "");
        } else {
            postInfoContent.addTextViewAtIndex(postInfoContent.getLastIndex(), Html.fromHtml(text));
        }
    }
}

 

import android.graphics.Color;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.CharacterStyle;
import android.text.style.ForegroundColorSpan;
 
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
public class StringUtils {
 
 
    public static final String IMAGE = "i";
    public static final String VIDEO = "V";
 
    /**
     * @param targetStr 要处理的字符串
     * @description 切割字符串,将文本和img标签碎片化,如"ab<img>cd"转换为"ab"、"<img>"、"cd"
     */
    public static List<String> cutStringByImgTag(String targetStr) {
        List<String> splitTextList = new ArrayList<String>();
        //Pattern pattern = Pattern.compile("<img.*?src=\\\"(.*?)\\\".*?>|<source.*?src=\\\"(.*?)\\\".*?>");
        Pattern pattern = Pattern.compile("<img.*?src=\\\"(.*?)\\\".*?>|<video.*?>([\\d\\D]*?)</video>");
        Matcher matcher = pattern.matcher(targetStr);
 
        int lastIndex = 0;
        while (matcher.find()) {
            if (matcher.start() > lastIndex) {
                splitTextList.add(targetStr.substring(lastIndex, matcher.start()));
            }
            splitTextList.add(targetStr.substring(matcher.start(), matcher.end()));
            lastIndex = matcher.end();
        }
        if (lastIndex != targetStr.length()) {
            splitTextList.add(targetStr.substring(lastIndex, targetStr.length()));
        }
        return splitTextList;
    }
 
    /**
     * 获取img标签中的src值
     *
     * @param content
     * @return
     */
    public static String getSrc(String content, String type) {
        String str_src = null;
        //目前img标签标示有3种表达式
        //<img alt="" src="1.jpg"/>   <img alt="" src="1.jpg"></img>     <img alt="" src="1.jpg">
        //开始匹配content中的<img />标签
        Pattern p_img = null;
        switch (type) {
            case VIDEO:
                p_img = Pattern.compile("<(source|SOURCE)(.*?)(/>|></img>|>)");
                break;
            case IMAGE:
                p_img = Pattern.compile("<(img|IMG)(.*?)(/>|></img>|>)");
                break;
        }
        Matcher m_img = p_img.matcher(content);
        boolean result_img = m_img.find();
        if (result_img) {
            while (result_img) {
                //获取到匹配的<img />标签中的内容
                String str_img = m_img.group(2);
 
                //开始匹配<img />标签中的src
                Pattern p_src = Pattern.compile("(src|SRC)=(\"|\')(.*?)(\"|\')");
                Matcher m_src = p_src.matcher(str_img);
                if (m_src.find()) {
                    str_src = m_src.group(3);
                }
                //结束匹配<img />标签中的src
 
                //匹配content中是否存在下一个<img />标签,有则继续以上步骤匹配<img />标签中的src
                result_img = m_img.find();
            }
        }
        return str_src;
    }
 
    /**
     * 关键字高亮显示
     *
     * @param target 需要高亮的关键字
     * @param text   需要显示的文字
     * @return spannable 处理完后的结果,记得不要toString(),否则没有效果
     * SpannableStringBuilder textString = TextUtilTools.highlight(item.getItemName(), KnowledgeActivity.searchKey);
     * vHolder.tv_itemName_search.setText(textString);
     */
    public static SpannableStringBuilder highlight(String text, String target) {
        SpannableStringBuilder spannable = new SpannableStringBuilder(text);
        CharacterStyle span = null;
 
        Pattern p = Pattern.compile(target);
        Matcher m = p.matcher(text);
        while (m.find()) {
            span = new ForegroundColorSpan(Color.parseColor("#EE5C42"));// 需要重复!
            spannable.setSpan(span, m.start(), m.end(),
                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
        return spannable;
    }
 
    /**
     * 从html文本中提取图片地址,或者文本内容
     *
     * @param html       传入html文本
     * @param isGetImage true获取图片,false获取文本
     * @return
     */
    public static ArrayList<String> getTextFromHtml(String html, boolean isGetImage) {
        ArrayList<String> imageList = new ArrayList<>();
        ArrayList<String> textList = new ArrayList<>();
        //根据img标签分割出图片和字符串
        List<String> list = cutStringByImgTag(html);
        for (int i = 0; i < list.size(); i++) {
            String text = list.get(i);
            if (text.contains("<img") && text.contains("src=")) {
                //从img标签中获取图片地址
                String imagePath = getSrc(text, IMAGE);
                imageList.add(imagePath);
            } else {
                textList.add(text);
            }
        }
        //判断是获取图片还是文本
        if (isGetImage) {
            return imageList;
        } else {
            return textList;
        }
    }
 
}

 

  • 0
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qq_37879809

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值