talk is cheep, show you the code. 源码参考html
尽管 Android 设备的性能日益加强,可是经过 webview 来展现内容和原生的体验仍是有必定的差距的,在某些状况下,咱们只是须要简单的图文并排就够了,好比一些帖子,这个时候用 webview 就显的有点重,考虑到这一点,咱们决定在客户端原生支持特定的网页标签。java
为了兼顾到各个平台,咱们约定输出是标准的 html 内容,对于已有的内容,能够进行内容的从新排版,把多余的标签去掉并换成约定好的标签。node
笔者有着多年对于 markdown 编辑器的使用经验,对于markdown语法的简洁有深度的喜好,对于不少时候的编辑工做都是够用了,我更倾向于轻便够用而非周全复杂的东西,在设计编辑器的时候,不经意就想到了 markdown 。git
通过协商咱们初期先支持下面的几种简单的样式:github
<h1>
标签。<p>
标签。<p>
里面] 对应 <b>
<br>
标签。<img>
标签。<a>
标签。约定结果:web
正确:markdown
<h1>这是一级标题</h1>
<p>段落1</p>
<p>段落2<b>我是加粗部分</b>hello<br></p>
<img src="http://github.com/pic.png" width="100", height="100"/>
<p>段落3<b>加粗加粗加粗<br>加粗加粗加粗</b></p>
复制代码
错误:编辑器
<p>段落1<p>内部段落</p></p>
<p>段落3<img src="http://github.com/pic.png" width="100", height="100"/></p>
复制代码
定义好支持的标签以后,接着就是由设计师设计好各个标签对应的文字样式,间距和图片的展现方式了。性能
经过研究几个开源项目,发现原生实现富文本编辑器主要有两个思路,一个是基于单个 EditText 经过组合不一样的 Spannable 来实现,另一个是组合 EditText 和 ImageView 等不一样的控件,我的认为第二种方式更加灵活,可是加粗,连接等处理也是须要 Spannable 的,所以组合了两种方式。单元测试
根据上面约定支持的标签,我定义了三种类型的控件:
EditImageView
是插入编辑框的图片控件,它也负责了上传的相关工做。RichEditText
这个控件负责段落的编辑,段落内能够支持一些文字样式,好比加粗和超连接。这个控件是 cwac-richedit 的一个实现,它封装了不少的 spannable 实现,这里只是用到了加粗和连接,源码虽然有改动,为了尊重做者的劳动成果,决定不改动它的名字。HeadingEditText
这个控件用来处理标题的输入,其实就是字体大一些和加粗的 EditText。RichTextEditor
是比较核心的实现,它继承了ScrollView
,它的职责是协调控件和光标、返回键之间的交互,主要实现了下面的接口:
输出:生成 html 的过程其实就是遍历各个控件了 RichEditText 里面的 Spannable 的过程。
Note: 值得一提的是笔者在看的时候,发现 cwac-richedit 这个项目是运行不起来的,通常状况下到这里就放弃对这个库的研究了,可是翻了下代码,发现做者的单元测试很充分,并且文档描述也算是比较清晰,仔细研究了一下,发现代码设计的有不少亮点,思路也很是清晰,因而后面就选择了这个库做为基础的文字段落样式实现,若是想基于它实现下划线,斜体,字体颜色等功能,应该是很是方便的一件事情。
咱们的富文本会做未帖子的详情和评论列表中,因为是出如今列表中,咱们须要考虑到控件的复用问题,因此一开始定义一个完整实现的富文本控件的思路就放弃了,而是经过按竖直方向拆分不一样的 Item,利用 RecylerView 或者 ListView 的复用特性来实现,尽管这样作起来会麻烦很多,可是完美地避免了列表不断滑动过程当中对象不断建立和销毁带来的内存抖动问题。
从下面流程图能够看出这个处理流程,首先解析 html 相关节点,并把其中相关的值和属性封装到不一样的对象中,而后经过列表数据去驱动整个视图的显示,解析 Html 是经过 Jsoup 来实现的,接口很是友好,和用 Jquery 差很少。
因为要处理的标签不多,在 Jsoup 的帮助下,整个解析代码不超过 30 行:
Document doc = Jsoup.parseBodyFragment(htmlContent);
List<Node> childNodeList = doc.body().childNodes();
if (childNodeList == null || childNodeList.isEmpty()) {
return null;
}
final int size = childNodeList.size();
List<IHtmlElement> elList = new ArrayList<>();
for (int pos = 0; pos != size; pos++) {
Node childNode = childNodeList.get(pos);
String tagName = childNode.nodeName();
if (tagName.equalsIgnoreCase("h")) {
elList.add(new PElement(Html.fromHtml(((Element) childNode).html())));
} else if(tagName.equalsIgnoreCase("h1")){
elList.add(new HElement(((Element) childNode).html()));
}else if (tagName.equalsIgnoreCase("img")) {
String src = childNode.attr("src");
String width = childNode.attr("width");
String height = childNode.attr("height");
elList.add(new ImgElement(src, NumberUtils.parseInt(width, 0), NumberUtils.parseInt(height, 0)));
} else {
if (childNode instanceof Element) {
elList.add(new PElement(Html.fromHtml(((Element) childNode).html())));
} else {
elList.add(new PElement(htmlContent));
}
}
}
复制代码
节点对象和对应的 Item 视图,能够看到结构仍是很是清晰的,对于之后想添加一些其余的样式也是很好扩展的。
虽然不是一个很复杂的东西,从整个实现思路来看,仍是比较好的兼顾了性能和可扩展度,也很好体现了分而治之和数据驱动视图的开发模式。不过从功能上来讲仍是存在一些缺陷的,好比光标不能跨段进行选择, 只能支持至上而下的排版。
技术交流群:70948803,大部分时间群里都是安静的,只交流技术相关,不多发言,不欢迎广告喷子。