文档章节

Android原生简易图文编辑器和展示实现

叶大侠
 叶大侠
发布于 2017/05/21 10:01
字数 1588
阅读 50
收藏 1
点赞 0
评论 0

talk is cheep, show you the code. 源码参考

背景

尽管Android设备的性能日益增强,但是通过webview来展示内容和原生的体验还是有一定的差距的,在某些情况下,我们只是需要简单的图文并排就够了,比如一些帖子,这个时候用webview就显的有点重,考虑到这一点,我们决定在客户端原生支持特定的网页标签。

为了兼顾到各个平台,我们约定输出是标准的html内容,对于已有的内容,可以进行内容的重新排版,把多余的标签去掉并换成约定好的标签。

设计思路

笔者有着多年对于markdown编辑器的使用经验,对于markdown语法的简洁有深度的喜爱,对于很多时候的编辑工作都是够用了,我更倾向于轻便够用而非周全复杂的东西,在设计编辑器的时候,不经意就想到了markdown。

经过协商我们初期先支持下面的几种简单的样式:

  1. 标题[一级] 对应 <h1> 标签。
  2. 文本段落[一级] 对应 <p> 标签。
  3. 文本加粗、换行[二级,嵌在<p>里面] 对应 <b> <br> 标签。
  4. 图片[一级],对应 <img> 标签。
  5. 超链接[二级] 对应 <a> 标签。

约定结果:

正确:

<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的,因此组合了两种方式。

根据上面约定支持的标签,我定义了三种类型的控件:

YRichEidtText

  1. EditImageView是插入编辑框的图片控件,它也负责了上传的相关工作。
  2. RichEditText这个控件负责段落的编辑,段落内可以支持一些文字样式,比如加粗和超链接。这个控件是cwac-richedit的一个实现,它封装了很多的spannable实现,这里只是用到了加粗和链接,源码虽然有改动,为了尊重作者的劳动成果,决定不改动它的名字。
  3. 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视图,可以看到结构还是非常清晰的,对于以后想添加一些其他的样式也是很好扩展的。

总结

虽然不是一个很复杂的东西,从整个实现思路来看,还是比较好的兼顾了性能和可扩展度,也很好体现了分而治之和数据驱动视图的开发模式。不过从功能上来说还是存在一些缺陷的,比如光标不能跨段进行选择, 只能支持至上而下的排版。

参考

  1. XRichText: https://github.com/sendtion/XRichText
  2. cwac-richedit: https://github.com/commonsguy/cwac-richedit
  3. Html解析库Jsoup:https://jsoup.org/

© 著作权归作者所有

共有 人打赏支持
叶大侠

叶大侠

粉丝 56
博文 44
码字总数 67312
作品 5
广州
程序员
Android蓝牙库-FastBle的简易使用

发布自Kindem的博客,欢迎大家转载,但是要注意注明出处 最近在做物联网课设,过程中需要用到Android的蓝牙API,奈何原生的蓝牙API使用有点麻烦。于是上网搜索看有没有好用的Android蓝牙库,...

Kindem ⋅ 05/25 ⋅ 0

Qt (5.10.0)for android

Qt 是一个1991年由Qt Company开发的跨平台C++图形用户界面应用程序开发框架,至此实现了全面支持iOS、Android、WP,它提供给应用程序开发者建立艺术级的图形用户界面所需的所有功能。那么如何在...

SmileAgain_ ⋅ 05/14 ⋅ 0

RN与原生交互(一)——基本页面跳转

React Native(以下简称RN)开发app过程中大部分都可以在JS端完成,但是也有一些功能是需要原生端来完成的。这时RN与原生端就不可避免的需要进行交互,比如页面跳转和数据传递。本篇文章主要以...

不變旋律 ⋅ 06/13 ⋅ 0

Android JNI学习(二)——实战JNI之“hello world”

本系列文章如下: Android JNI(一)——NDK与JNI基础 Android JNI学习(二)——实战JNI之“hello world” Android JNI学习(三)——Java与Native相互调用 Android JNI学习(四)——JNI的常用方法...

隔壁老李头 ⋅ 05/09 ⋅ 0

Flutter尝鲜:跨平台移动应用开发

开始之前 本人使用Flutter开发的一个图文App《每日图文》,可以同时运行在Android和iOS上,欢迎体验,喜欢的话还不吝Star一下。 叮叮叮~ 传送门 Flutter为何物? 随着移动App开发成本越来越...

开发的猫 ⋅ 06/06 ⋅ 0

张高兴的 Xamarin.Android 学习笔记:(二)“Hello World”

  完成环境配置后开始第一个简单项目。打开 Visual Studio 新建一个 Xamarin.Android 项目 “HelloAndroid”。(GitHub:https://github.com/ZhangGaoxing/xamarin-android-demo/tree/mas...

张高兴 ⋅ 2017/01/13 ⋅ 0

Canvas/Shader- Android

Shader的五个子类:BitmapShader、LinearGradient、RadialGradient、SweepGradient和ComposeShader。 Android中Canvas绘图之Shader使用图文详解- https://blog.csdn.net/iispring/article/de......

shareus ⋅ 05/15 ⋅ 0

OpenGL实现物体动画和视频特效

OpenGL实现视频的水印、滤镜?OpenGL实现视频的剪裁、旋转? 2D/3D物体的 旋转,平移,缩放? OpenGL图片滤镜与视频滤镜? 矩阵(Matrix)是一个按照长方阵列排列的复数或实数集合,最早来自于方...

shareus ⋅ 04/24 ⋅ 0

Android中MVP模式的了解及实例

Android中MVP的由来 在了解MVP之前我相信很多人已经学习过MVC这个框架了!MVP的诞生可以说是建立在MVC在安卓某些不足的方面 MVC简介 .M对应Model,代表业务数据 .V对应View,代表视图 .C对应C...

九尾74 ⋅ 05/26 ⋅ 0

React-Native 之 环境配置和简单使用

前言 学习本系列内容需要具备一定 HTML 开发基础,没有基础的朋友可以先转至 HTML快速入门(一) 学习 本人接触 React Native 时间并不是特别长,所以对其中的内容和性质了解可能会有所偏差,在...

postdep ⋅ 05/01 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Springboot2 之 Spring Data Redis 实现消息队列——发布/订阅模式

一般来说,消息队列有两种场景,一种是发布者订阅者模式,一种是生产者消费者模式,这里利用redis消息“发布/订阅”来简单实现订阅者模式。 实现之前先过过 redis 发布订阅的一些基础概念和操...

Simonton ⋅ 23分钟前 ⋅ 0

error:Could not find gradle

一.更新Android Studio后打开Project,报如下错误: Error: Could not find com.android.tools.build:gradle:2.2.1. Searched in the following locations: file:/D:/software/android/andro......

Yao--靠自己 ⋅ 昨天 ⋅ 0

Spring boot 项目打包及引入本地jar包

Spring Boot 项目打包以及引入本地Jar包 [TOC] 上篇文章提到 Maven 项目添加本地jar包的三种方式 ,本篇文章记录下在实际项目中的应用。 spring boot 打包方式 我们知道,传统应用可以将程序...

Os_yxguang ⋅ 昨天 ⋅ 0

常见数据结构(二)-树(二叉树,红黑树,B树)

本文介绍数据结构中几种常见的树:二分查找树,2-3树,红黑树,B树 写在前面 本文所有图片均截图自coursera上普林斯顿的课程《Algorithms, Part I》中的Slides 相关命题的证明可参考《算法(第...

浮躁的码农 ⋅ 昨天 ⋅ 0

android -------- 混淆打包报错 (warning - InnerClass ...)

最近做Android混淆打包遇到一些问题,Android Sdutio 3.1 版本打包的 错误如下: Android studio warning - InnerClass annotations are missing corresponding EnclosingMember annotation......

切切歆语 ⋅ 昨天 ⋅ 0

eclipse酷炫大法之设置主题、皮肤

eclipse酷炫大法 目前两款不错的eclipse 1.系统设置 Window->Preferences->General->Appearance 2.Eclipse Marketplace下载【推荐】 Help->Eclipse Marketplace->搜索‘theme’进行安装 比如......

anlve ⋅ 昨天 ⋅ 0

vim编辑模式、vim命令模式、vim实践

vim编辑模式 编辑模式用来输入或修改文本内容,编辑模式除了Esc外其他键几乎都是输入 如何进入编辑模式 一般模式输入以下按键,均可进入编辑模式,左下角提示 insert(中文为插入) 字样 i ...

蛋黄Yolks ⋅ 昨天 ⋅ 0

大数据入门基础:SSH介绍

什么是ssh 简单说,SSH是一种网络协议,用于计算机之间的加密登录。 如果一个用户从本地计算机,使用SSH协议登录另一台远程计算机,我们就可以认为,这种登录是安全的,即使被中途截获,密码...

董黎明 ⋅ 昨天 ⋅ 0

web3j教程

web3j是一个轻量级、高度模块化、响应式、类型安全的Java和Android类库提供丰富API,用于处理以太坊智能合约及与以太坊网络上的客户端(节点)进行集成。 汇智网最新发布的web3j教程,详细讲解...

汇智网教程 ⋅ 昨天 ⋅ 0

谷歌:安全问题机制并不如你想象中安全

腾讯科技讯 5月25日,如今的你或许已经对许多网站所使用的“安全问题机制”习以为常了,但你真的认为包括“你第一个宠物的名字是什么?”这些问题能够保障你的帐户安全吗? 根据谷歌(微博)安...

问题终结者 ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部