基本功能

RichEditor 是一个继承自 WebView 的自己定义 view,枚举类型 Type 定了它所支持的排版格式:

public enum Type {
BOLD,
ITALIC,
SUBSCRIPT,
SUPERSCRIPT,
STRIKETHROUGH,
UNDERLINE,
H1,
H2,
H3,
H4,
H5,
H6
}

首先在构造函数中载入一个 html 文件。

private static final String SETUP_HTML = "file:///android_asset/editor.html"

public RichEditor(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setVerticalScrollBarEnabled(false);
setHorizontalScrollBarEnabled(false);
getSettings().setJavaScriptEnabled(true);
setWebChromeClient(new WebChromeClient());
setWebViewClient(createWebviewClient()); // 载入 html 文件
loadUrl(SETUP_HTML); applyAttributes(context, attrs);
}

当中 html 文件 - editor.html 载入了两个 css 文件 - normalize.css & style.css。一个 js 文件 - rich_editor.js

body 中插入了一个 contentEditable="true"div

<!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>

rich_editor.js 中定义了非常多排版功能的 API,RichEditor 相似 proxy。对外提供了 Java API。

public void setEditorFontColor(int color) {
String hex = convertHexColorString(color);
exec("javascript:RE.setBaseTextColor('" + hex + "');");
}

当中 exec 用于运行 js 代码。

protected void exec(final String trigger) {
if (isReady) {
load(trigger);
} else {
postDelayed(new Runnable() {
@Override public void run() {
exec(trigger);
}, 100);
}
}
}

仅仅有当 rich_editor.html 载入完毕后 isReady 才会被置为 true

protected class EditorWebViewClient extends WebViewClient {
@Override public void onPageFinished(WebView view, String url) {
isReady = url.equalsIgnoreCase(SETUP_HTML);
if (mLoadListener != null) {
mLoadListener.onAfterInitialLoad(isReady);
}
}
// other methods
}

构造函数在载入 editor.html 之前已经设置了一个 EditorWebViewClient 的实例。

public RichEditor(Context context, AttributeSet attrs, int defStyleAttr) {
// other statements
setWebViewClient(createWebviewClient());
loadUrl(SETUP_HTML)
// other statements
} protected EditorWebViewClient createWebviewClient() {
return new EditorWebViewClient();
}

editor.html 载入完毕,isReady 被设为 true 之后,exec 方法就能够调用 load(trigger) 运行 js 了。

private void load(String trigger) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
evaluateJavascript(trigger, null);
} else {
loadUrl(trigger);
}
}

evaluateJavascript 以及 loadUrl 都是 Webview 提供的方法。

evaluateJavascript 是从 API 19 開始引入的。能够异步运行 js 代码。

Asynchronously evaluates JavaScript in the context of the currently displayed page.

SCHEME

RichEditor 提供了两个 Scheme 负责回调 java 代码。

private static final String CALLBACK_SCHEME = "re-callback://";
private static final String STATE_SCHEME = "re-state://";

然后在 EditorWebViewClient 中重写了 shouldOverrideUrlLoading 方法:

protected class EditorWebViewClient extends WebViewClient {
@Override
public void onPageFinished(WebView view, String url) {
isReady = url.equalsIgnoreCase(SETUP_HTML);
if (mLoadListener != null) {
mLoadListener.onAfterInitialLoad(isReady);
}
} @Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
String decode;
try {
decode = URLDecoder.decode(url, "UTF-8");
} catch (UnsupportedEncodingException e) {
// No handling
return false;
} if (TextUtils.indexOf(url, CALLBACK_SCHEME) == 0) {
callback(decode);
return true;
} else if (TextUtils.indexOf(url, STATE_SCHEME) == 0) {
stateCheck(decode);
return true;
} return super.shouldOverrideUrlLoading(view, url);
}
}

假设 url 符合 CALLBACK 的 scheme,就会运行 callback 方法:

private void callback(String text) {
mContents = text.replaceFirst(CALLBACK_SCHEME, "");
if (mTextChangeListener != null) {
mTextChangeListener.onTextChange(mContents);
}
}

假设 url 符合 STATE 的 scheme。就会运行 stateCheck 方法:

private void stateCheck(String text) {
String state = text.replaceFirst(STATE_SCHEME, "").toUpperCase(Locale.ENGLISH);
List<Type> types = new ArrayList<>();
for (Type type : Type.values()) {
if (TextUtils.indexOf(state, type.name()) != -1) {
types.add(type);
}
} if (mDecorationStateListener != null) {
mDecorationStateListener.onStateChangeListener(state, types);
}
}

通过下面两个 set 方法能够设置回调。

public void setOnDecorationChangeListener(OnDecorationStateListener listener) {
mDecorationStateListener = listener;
} public void setOnInitialLoadListener(AfterInitialLoadListener listener) {
mLoadListener = listener;
}

总结

编辑器的核心功能由 js 实现,RichEditor 封装了 js 的功能。为上层提供了 java 接口。

參考

富文本编辑器 - RichEditor的更多相关文章

  1. Android 富文本编辑器实现方案

    本人实现富文本编辑器的时候,总结了如下两种方案: 1. 纯 EditText 实现方案 2. 使用ScrollView作为最外层的父容器来控制展示效果 示例demo地址为:https://github ...

  2. 移动端强大的富文本编辑器richeditor-android

    代码地址如下:http://www.demodashi.com/demo/14883.html 一.运行效果图 二.代码具体实现 1.引入richeditor-android richeditor-a ...

  3. 【JavaScript】富文本编辑器

    这是js写的富文本编辑器,还存在一些bug,但基本功能已经实现,通过这个练习,巩固了js富文本编辑方面的知识,里面包含颜色选择器.全屏.表情.上传图片等功能,每个功能实际对应的就是一个小插件啦 部分程 ...

  4. Android 图片混排富文本编辑器控件

    概述 一个Android 图片混排富文本编辑器控件(仿兴趣部落) 详细 代码下载:http://www.demodashi.com/demo/12032.html 一.一个Android 图片混排富文 ...

  5. vue中引入Tinymce富文本编辑器

    最近想在项目上引入一个富文本编辑器,之前引入过summernote,感觉并不太适合vue使用, 然后在网上查了查,vue中使用Tinymce比较适合, 首先,我们在vue项目的components文件 ...

  6. 富文本编辑器Simditor的简易使用

    最近打算自己做一个博客系统,并不打算使用帝国cms或者wordpress之类的做后台管理!自己处于学习阶段也就想把从前台到后台一起谢了.好了,废话不多说了,先来看看富文本编辑器SimDitor,这里是 ...

  7. 个人网站对xss跨站脚本攻击(重点是富文本编辑器情况)和sql注入攻击的防范

    昨天本博客受到了xss跨站脚本注入攻击,3分钟攻陷--其实攻击者进攻的手法很简单,没啥技术含量.只能感叹自己之前竟然完全没防范. 这是数据库里留下的一些记录.最后那人弄了一个无限循环弹出框的脚本,估计 ...

  8. UEditor百度富文本编辑器--让编辑器自适应宽度的解决方案

    UEditor百度富文本编辑器的initialFrameWidth属性,默认值是1000. 不能够自适应屏幕宽度.如图1: 刚开始的时候,我是直接设置initialFrameWidth=null的.效 ...

  9. PHP Ueditor 富文本编辑器

    2016年12月11日 08:46:59 星期日 百度的简版富文本编辑器umeditor很久没更新了 全功能版本的配置项跟umeditor还是有区别的, 这里说下ueditor怎么对接到项目中去, 主 ...

随机推荐

  1. 在vue项目当中使用sass

    需要分别安装node-sass 和 sass-loader;可以不需要ruby; webpack当中配置 { test: /\.vue$/, loader: 'vue-loader', options ...

  2. C#创建Graphics对象的方法

    方法一.利用控件或窗体的Paint事件中的PainEventArgs 在窗体或控件的Paint事件中接收对图形对象的引用,作为PaintEventArgs(PaintEventArgs指定绘制控件所用 ...

  3. SharepPoint 2013安装体会

    SharePoint 2013出来了,一直没有找到合适的机器来安装.前天腾出来一台内存8G的机器,决定在Hyper-V上安装在一台虚机,然后装个Windows 2012,再装SharePoint 20 ...

  4. [LeetCode] Minimum Window Substring 散列映射问题

    题目: Given a string S and a string T, find the minimum window in S which will contain all the charact ...

  5. python作为计算器(数学用法)

    1.基本的加减乘除与取余运算 >>> print(5+10) 15 >>> print(5-10) -5 >>> print(5*10) 50 & ...

  6. The disk contains an unclean file system

    Ubuntu : Status 14: The disk contains an unclean file system By mkyong | July 23, 2014 | Viewed : 10 ...

  7. Android系统默认输入法的修改为搜狗输入法

    1.  frameworks\base\packages\SettingsProvider\res\values\defaults.xml 文件中修改默认输入法为搜狗输入法 <stringnam ...

  8. Python Challenge 第二关

    第二关和第一关一样,还是一幅图和一行提示.提示说的是: recognize the characters. maybe they are in the book, but MAYBE they are ...

  9. Java 网络通信【01】TCP

    不积跬步,无以至千里:不积小流,无以成江海.——<荀子劝学> JAVA中设计网络编程模式的主要有TCP和UDP两种. TCP是属于即时通信,点对点连接进行通信. UDP是通过数据包来进行通 ...

  10. TreeSet, LinkedHashSet and HashSet 的区别

    1. 介绍 TreeSet, LinkedHashSet and HashSet 在java中都是实现Set的数据结构 # TreeSet的主要功能用于排序 # LinkedHashSet的主要功能用 ...