#本文主要是讲解部分原理,源码及其使用请移步Github
https://github.com/RexSuper/RichEditor
Demo
https://github.com/RexSuper/RichEditor/tree/master/RichHtmlEditorforAndroid/sample
Add it in your root build.gradle at the end of repositories:
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
Step 2. Add the dependency
dependencies {
implementation 'com.github.RexSuper:RichEditor:1.0.5'
}
github上的示例代码必须看,因为富文本有些功能后续不得不自己填充,比如用户的资源文件得先放到自己服务器生成链接等等
2019-09-04 优化用户体验,在编辑时候所有资源改为本地,加载转在线改为,最后统一发布的时候
2019-08-20 增加音频功能 增加链接下载功能 和视频下载功能
2019-08-16 增加各种文件功能
2019-08-15 增加添加视频功能
2019-06-21 增加基本图文混排等功能
目录
一个简单的带 contenteditable="true"的内容
1.网页的contenteditable如何实现hint功能
2.如何获取光标所在文字的所有style(需求场景 如选中了加粗 B变色,但你光标选中了)
6.自带只能实现固定几种的font size (1-7自带几种)怎么指定大小实现font-size
前言:
富文本显示还好,Android富文本编辑一直是一个大坑,还得考虑和其他端同步的问题,问题是你无论用什么方式实现,EditText支持的标签并不多,自定义span或者webview自定义标签也较为繁琐。
用html+webview+js肯定是最合适的可直接导出html兼容性高,Android端用js调用本地静态网页中的contenteditable(此原理做IOS富文本编辑可以无缝照搬)
本文致力于网络常见能搜到的功能实现原理外,补全其他可能遇到的坑,网上能搜到的内容只提关键字
本文也是基础RichEditor for Android思路上一个优化和讲解。它提供了如何和html edit之间交流。理论上来说html能实现的安卓就能实现了,我们只是需要找到这些我们需要的方法
- 也可以用另一种理解方式,html本身就自带这些所有功能,兼容性又好,写好html作为静态放到客户端本地。然后通过webview 用js去调这些功能。那为什么不让web前端做成在线的 再通过js调用呢?要让安卓和ios来写这些呢,我想一定是你的领导在 鼓励你走向 真·全栈之路 的 第一步吧。既然要我们做,我们就得硬着头皮做,还要做好~拿人钱财,替人浪费时间不是?
简单版图例
Video | Image | Audio | File And Download |
Italic | Subscript | Superscript | Strikethrough |
Underline | JustifyLeft | JustifyCenter | JustifyRight |
Blockquote | Heading | Undo | Redo |
Indent | Outdent | InsertLink | Checkbox |
TextColor | TextBackgroundColor | FontSize | UnorderedList |
OrderedList | Hint | NewLine | Blod |
android简单富文本显示基本原理:最小模型示例
spannablestring和html.fromhtml +style 可以简单使用为图文混排,多种颜色但这种只支持到h4部分标签,很多内容如font-size都不支持,只支持small big这样的,此处顺带一提- android复杂富文本编辑和再现
如果你采用自定义原始view 1.编辑,2.整理成数据传输到后台再回来3.再现,三处都很复杂。如果使用html前端的自带的edit,则可以不用考虑后面的问题,难点在于如果在安卓端去调用一个个功能,然后获取其他状态,光标等问题
- 编辑器的框架搭建(以加粗为例,其他居中 撤销什么的 均是一个逻辑 事件监听 html自带)最小实现模型
asset里面放置一些静态网页和我们需要的初始化css
editor.html
一个简单的带 contenteditable="true"的内容
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="user-scalable=no">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" type="text/css" href="normalize.css">
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div id="editor" contenteditable="true"></div>
<script type="text/javascript" src="rich_editor.js"></script>
</body>
</html>
安卓通过js和html互相调用 例如设置加粗
rich_editor.js
RE.editor = document.getElementById('editor');
RE.setBold = function() {
document.execCommand('bold', false, null);
}
Webview中调用js中的加粗
//简化最小模型代码
public void setBold() {
exec("javascript:RE.setBold();");
}
public void exec(String trigger) {
load(trigger)
}
private void load(String trigger) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
evaluateJavascript(trigger, null);
} else {
loadUrl(trigger);
}
}
1.网页的contenteditable如何实现hint功能
方法1:
editor.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
.placeholder::after {
content: attr(placeholder);
position: absolute;
top: 10px;
color: #a9a9a9;
}
.placeholder-hide::after {
display:none;
}
</style>
</head>
<body>
<div class="editor placeholder" contenteditable="true" placeholder="你想说什么"></div>
<script>
document.querySelector('.editor').addEventListener("input", function(event) {
var inputValue = event.target.innerHTML;
if (inputValue) {
document.querySelector('.editor').className = 'editor placeholder placeholder-hide'
} else {
document.querySelector('.editor').className = 'editor placeholder'
}
})
</script>
</body>
</html>
方法2
js
RE.setPlaceholder = function(placeholder) {
RE.editor.setAttribute("placeholder", placeholder);
}
css
#editor[placeholder]:empty:not(:focus):before {
content: attr(placeholder);
color: #9B9B9B;
font-size:15px;
}}
抽取成设置方法
<script>
function setPlaceholderColor (color) {
var placeHolderStyle = document.querySelector("#placeholder-style");
var styleContent = "#editor:empty:not(:focus):before {color:" + color +"}";
if (placeHolderStyle) {
placeHolderStyle.innerText = styleContent
} else {
placeHolderStyle = document.createElement('style')
placeHolderStyle.setAttribute('id', 'placeholder-style')
placeHolderStyle.innerText = styleContent
document.documentElement.appendChild(placeHolderStyle)
}
}
</script>
public void setPlaceholderColor(String color) {
exec("javascript:RE.setPlaceholderColor('" + color+ "');");
}
2.如何获取光标所在文字的所有style(需求场景 如选中了加粗 B变色,但你光标选中了)
rich_editor.js
配合7:addEventListener
RE.getSelectedNode = function() {
var node,selection;
if (window.getSelection) {
selection = getSelection();
node = selection.anchorNode;
}
if (!node && document.selection) {
selection = document.selection
var range = selection.getRangeAt ? selection.getRangeAt(0) : selection.createRange();
node = range.commonAncestorContainer ? range.commonAncestorContainer :
range.parentElement ? range.parentElement() : range.item(0);
}
if (node) {
var item = (node.nodeName == "#text" ? node.parentNode : node);
console.log("innerHTML:"+item.innerHTML);
console.log("font-size:"+item.style["font-size"]);
console.log("color:"+item.getAttribute("color"));
console.log("queryCommandState1 bold:"+document.queryCommandState('bold'));
}
}
4.如何自动换行
public void setNewLine() {
exec("javascript:RE.prepareInsert();");
exec("javascript:RE.insertHTML('<br></br>');");
}
5.如何处理图片
5.1安卓实现webview图片点击放大方法回调
这种方式比网上搜出来PageFinish后要好
loadUrl("javascript:(function())有时候会因为加载问题,导致代码没添加进去,那种有bug
现提供更好的方式,同时实现的
public final static String IMG_CLICK_JS = "<script type='text/javascript'>window.onload = function(){" +
"var $img = document.getElementsByTagName('img');" +
"for(var p in $img){" +
" if (typeof $img[p] === 'object') {" +
" $img[p].style.width = '100%';" +
" $img[p].style.height ='auto';" +
" $img[p].onclick = function(e){" +
" ImgClick(e.srcElement.src);" +
" };" +
" }" +
"}" +
"};" +
"function ImgClick(src) {" +
" var message = {" +
" 'imgUrl' : src," +
" };" +
" window.imageOnclick.openImage(src);" +
"};" +
"</script>";
*你也可以按照7.webview和js交互 console.log()传值方式 替换window.imageOnclick.openImage(src);"
将上面代码 直接加入到你的html中
实现对应回调
webview.getSettings().setJavaScriptEnabled(true);
webview.addJavascriptInterface(new JavascriptInterfaceImageOnclick(), "imageOnclick");
private class JavascriptInterfaceImageOnclick {
@android.webkit.JavascriptInterface
public void openImage(String imgUrl) {
if (mOnClickImageTagListener != null && !TextUtils.isEmpty(imgUrl)) {
mOnClickImageTagListener.onClick(imgUrl);
}
}
}
6.自带只能实现固定几种的font size (1-7自带几种)怎么指定大小实现font-size
//待更新
font size 类似于
- xx-small
- x-small
- small
- medium
- large
- x-large
- xx-large
而我们需要 int px
7.webview和js交互
addJavascriptInterface
removeJavascriptInterface
evaluateJavascript
一般是addJavascriptInterface或者
shouldOverrideUrlLoading
接收信息
其实最好用的是,也最安全
console.log() webview接收
editor(WebView)-->webChromeClient -->onConsoleMessage
8.如何在客户端load自带美化的css解决
1.不可二次编辑
loadDataWithBaseURL(String baseUrl, String data,
String mimeType, String encoding, String historyUrl)
2.可二次编辑
editor.loadCSS("file:///android_asset/article_night.css")
无法继续编辑的问题
9.事件监听
RE.editor.addEventListener("input", RE.callback);//其他html支持的 keyup ,selectionchange都支持 选中 文本变化 可以查下资料
源码下载
https://github.com/RexSuper/RichHtmlEditorForAndroid
一定要理解,不然产品随便有个新需求你将寸步难行
10.添加视频
// 让其可以继续进去编辑模式
RE.insertVideo = function(url,custom) {
var html = ' <video src="' + url + '" ' + custom +'></video> ';
RE.insertHTML(html);
}
private void addVideo() {
//需要编辑框有光标才行
richEditor.focusEditor();
// pb.setVisibility(View.VISIBLE);
//将视频上传到自己服务器得到链接
//============>
richEditor.setNeedSetNewLineAfter(true);
richEditor.insertVideo("https://www.w3school.com.cn/example/html5/mov_bbb.mp4",
//增加进度控制
"controls=\"controls\"" +
//视频显示第一帧
" initial-time=\"0.01\" " +
//宽高
"height=\"300\" " +
//样式
" style=\"margin-top:10px;max-width:100%;\""
);
}
//临时记录
function insertHtml(html) {
var sel,range,div,node
sel = window.getSelection()//返回一个Selection对象,用来表示用户选择的文本范围或插入符当前位置。
range = sel.getRangeAt(0) //获取Range,参数为0或其他能够==0,如false,'',null
div=document.createElement('div')
div.innerHTML=html
node=div.firstChild
range.deleteContents()//删除目前range的内容
range.insertNode(node)//新增的节点内容
range.setStartAfter(node)//重新定位range(光标位置)
sel.removeAllRanges() //清除所有选中
sel.addRange(range) //将新定位的range加入
}
特别鸣谢
前端知识修正:@ZX
新建交流群
结尾:
这是一个不该由客户端实现又可以实现的功能