# RichText **Repository Path**: zxgit/richText ## Basic Information - **Project Name**: RichText - **Description**: richtext,可以用于显示富文本 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2023-12-20 - **Last Updated**: 2023-12-20 ## Categories & Tags **Categories**: Uncategorized **Tags**: 富文本 ## README # RichText 能简单显示富文本 ## 使用方法 如下所示,可以直接使用对应的标签生成json对象,然后让RichText解析出来。 ```kotlin val json = array( Text { ItemText.CONTENT to "标题" ItemText.TEXT_SIZE to 25 ItemText.TEXT_BOLD to true }, NewLine { ItemNewLine.ROWS to 0 }, Text { ItemText.CONTENT to "这是一段测试文本文本这是一段测试文本,这是一段测试文本,这是一段测试文本,这是一段测试文本是一段测试文本,这是一段测试文本,这是一段测试文本。" } ) richContent?.parse(json) ``` 所支持的标签如下: |标签| 说明 | |--|--| | array | json数组 | | Image | 图片标签 | | Text | 文本,支持文字大小,加粗等属性 | | Gif | 显示gif图片 | | Tag | 标签 | | Clickable | 可点击文本 | |NewLine | 换行 | ```kotlin //插入一个组件 fun insertRichItem(item: RichItem) //插入一张图片 fun insertImg(imagePath: String) //插入一个标签 fun insertTag(content: String, color: String = "#FF0000") ``` RichText对外暴露了日志接口和图片加载接口以及上报接口,可以让用户自定义实现日志和图片加载。 其中图片加载默认使用glide进行加载 **日志接口** ```kotlin interface IRichTextLog { fun vlog(tag: String, content: String) fun ilog(tag: String, content: String) fun dlog(tag: String, content: String) fun wlog(tag: String, content: String) fun eLog(tag: String, content: String) } //设置外部日志接口 fun setLogInterface(iLog: IRichTextLog) ``` **图片加载接口** ```kotlin interface ILoadImg { fun loadImg(context: Context, url:String, view:ImageView) } //设置外部图片加载接口 fun setImgLoader(iLoadImg: ILoadImg) ``` **上报接口** ```kotlin interface IReport { fun report(event: String, params: Map) } //设置上报接口 fun setReporter(report: IReport) //上报的事件 object Event { val IMG_LOAD_ERROR = "load_img_error" //图片加载错误 val PARSE_ITEM_ERROR = "parse_item_error" //解析错误 val EXPOSURE = "exposure" //曝光 val UNEXPOSURE = "unExposure" //反曝光 } ``` ## 实现原理 ### 渲染能力: ![image](类图.png) RichText使用的是ScrollView+LinearLayout的方案,将不同的模块添加到LinearLayout当中。 参考markdown,将不同的模块抽象成RichItem,设置好类型和参数,将渲染的能力交给具体的RichItem实现,返回对应的view,然后将渲染好的view添加到RichText当中。 RichItem的定义如下,每个item均可以转换成一串json,其中json分为type和attrs两大类,type表示这个item的类型,attrs表示属性。 parse方法可以将对应的json转换成view,转换成view之后就可以添加到RichText当中。 ```kotlin abstract open class RichItem { protected var richView: View? = null abstract fun toJson(): JSONObject abstract fun parse(context: Context, jsonObject: JSONObject): View abstract fun type(): RichItemType open fun getView(): View? { return this.richView } //设置是否可编辑 open fun setEditable(editable:Boolean){ } companion object { val TYPE = "type" val ATTRS = "attrs" } } ``` 例如:下面是一串json字符串,包含一个文本和一个标签 ```json [ { "type": "Text", "attrs": { "textSize": 25, "textBold": true, "content": "标题" } }, { "type": "Tag", "attrs": { "background": "#FF0000", "content": "测试标签" } } ] ``` 也可以这样写 ```kotlin array( Text { ItemText.CONTENT to "标题" ItemText.TEXT_SIZE to 25 ItemText.TEXT_BOLD to true }, Tag { ItemTag.CONTENT to "测试标签" ItemTag.BACKGROUND to "#FF0000" }, ) ``` 最后渲染出来的效果如下: ![image](img.png) ### 编辑能力: 手机端并不适合很复杂的编辑,因此RichText比较着重考虑的是渲染能力,只提供了比较轻量的编辑能力。 其中编辑能力主要由EditText来实现,这里需要注意的是插入和删除能力。 **插入:** 分三种情况,在文本前,文本中,文本后插入一个组件。 其中在文本前和文本后插入比较简单,在文本中插入,需要将当前的EditText拆分成两个EditText,然后在中间插入对应的view,对应源码如下 ```kotlin fun insertRichItem(item: RichItem) { if (!isEditable) { Tools.elog(TAG, "can not edit") return } val lastEditStr = lastFocusEdit!!.text.toString() val cusorIndex = lastFocusEdit!!.selectionStart val editStrPre = lastEditStr.substring(0, cusorIndex).trim() val editStrAfter = lastEditStr.substring(cusorIndex).trim() val lastEditIndex = layout.indexOfChild(lastFocusEdit) if (editStrPre.isEmpty() && editStrAfter.isEmpty()) { //文本前 layout.addView(item.getView(), lastEditIndex) } else if (editStrAfter.isEmpty()) { //在文本后插入 addEditTextAtIndex(lastEditIndex + 1, "") layout.addView(item.getView(), lastEditIndex + 1) } else { //文本中插入 lastFocusEdit!!.setText(editStrPre) addEditTextAtIndex(lastEditIndex + 1, editStrAfter) layout.addView(item.getView(), lastEditIndex + 1) } if (item.getView() is EditText) { item.getView()?.apply { this.setOnKeyListener(mKeyListener) this.onFocusChangeListener = mFocusListener } } item.getView()?.setTag(item) hideKeyBoard() } ``` **删除:** 删除需要注意的点是,当EditText光标到最左边,仍然还删除的时候,此时需要判断一下前面的组件是否是EditText,如果前面的组件可编辑,则将焦点转移到前面的组件,否则直接将前一个组件删除。 ```kotlin private fun onBackspacePress(editText: EditText) { val startSelection = editText.selectionStart if (startSelection == 0) { val editIndex = layout.indexOfChild(editText) val preView = layout.getChildAt(editIndex - 1) preView?.let { when (preView) { is EditText -> { if (editText.text.length == 0) { layout.removeView(editText) } (it as EditText).apply { requestFocus() setSelection(text.length) } } else -> { layout.removeView(preView) } } } } } ```