一、添加依赖
开源框架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;
}
}
}