【Android】基于webView打造富文本编辑器(H5)

目录

  • 前言
  • 一、实现效果
  • 二、具体实现
    • 1. 导入网页资源
    • 2. 页面设计
    • 3. 功能调用
    • 4. 完整代码
  • 总结


前言

HTML5是构建Web内容的一种语言描述方式。HTML5是Web中核心语言HTML的规范,用户使用任何手段进行网页浏览时看到的内容原本都是HTML格式的,在浏览器中通过一些技术处理将其转换成为了可识别的信息。

而WebView 是一种嵌入式浏览器,原生APP应用可以用它来展示网络内容。其功能强大,除了具有一般View的属性和设置外,还可以对url请求、页面加载、渲染、页面交互进行强大的处理.。
在这里插入图片描述
所以,因为H5的跨平台和成本低的优势,越来越多的项目都使用了Android原生控件与WebView进行混合开发,用WebView加载html界面,实现本地安卓代码与HTML+CSS+JS的交互。

webView的基本使用可以参考这篇文章:https://blog.csdn.net/carson_ho/article/details/52693322

本文将利用webView实现一个富文本编辑器,它是一种可内嵌于浏览器,所见即所得的文本编辑器,其实质就是html代码,可改变文本样式、文间插入图片或视频等。我为了实现这一功能借鉴很多项目(主要是MRichEditor和RichEditTextCopyToutiao,都是基于 richeditor-android),修复了一些Bug,最终效果还行。借鉴的第三方库可移步我之前写的文章:常用的第三方开源库汇总


一、实现效果

在这里插入图片描述

在这里插入图片描述

二、具体实现

富文本编辑器主要功能就是文字大小、颜色、加粗、斜体、下划线、删除线、缩进、居中或靠左靠右,无序或有序列表,插入链接、图片、视频等。原有项目会存在一些导入html标签和输入框光标的bug,解决这些需要一点HTML+CSS+JS的知识。

1. 导入网页资源

在项目根目录下新建assets文件夹
在这里插入图片描述
导入资源:对于熟悉网页端的开发人员来说,修改这些并不难。
在这里插入图片描述

2. 页面设计

编辑器的一个按钮即对应着js中的一个函数,通过RichEditor自定义 view去调用js函数, 它继承自 Webview ,加载html文件,枚举类型Type定义了支持的排版格式。

public class RichEditor extends WebView {private static final String SETUP_HTML = "file:///android_asset/editor.html";public enum Type {BOLD,ITALIC,SUBSCRIPT,SUPERSCRIPT,STRIKETHROUGH,UNDERLINE,H1,H2,H3,H4,H5,H6,ORDEREDLIST,UNORDEREDLIST,JUSTIFYCENTER,JUSTIFYFULL,JUSTIFYLEFT,JUSTIFYRIGHT}@SuppressLint("SetJavaScriptEnabled")public RichEditor(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);setVerticalScrollBarEnabled(false);setHorizontalScrollBarEnabled(false);getSettings().setJavaScriptEnabled(true);setWebChromeClient(new WebChromeClient());setWebViewClient(createWebviewClient());loadUrl(SETUP_HTML);applyAttributes(context, attrs);}
}

其中​editor.html​即编辑器所展示的主页面

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

RichEditor控件中的函数对应着​rich_editor.js​中的接口

RichEditor:

public void setBold() {exec("javascript:RE.setBold();");}

​rich_editor.js:

RE.setBold = function() {document.execCommand('bold', false, null);RE.enabledEditingItems()
}

在布局中使用:

<com.text.richeditor.editor.RichEditorandroid:id="@+id/rich_Editor"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:overScrollMode="never" />

主要样式如下:
在这里插入图片描述
除了底部的功能栏可以使用,最左边还有一个排版按钮,这里主要是为了不方便弹出底部时,可以选择使用弹窗弹出功能栏,功能是一致的,可以选择性阉割。使用某一功能时会有颜色高亮提示。

3. 功能调用

首先要在activity中初始化控件,实现setOnDecorationChangeListener接口,监听点击某个功能按钮时显示对应高亮,让用户看到现在正在所使用的排版格式。

		//输入框显示字体的大小rich_Editor.setEditorFontSize(16)//输入框显示字体的颜色rich_Editor.setEditorFontColor(ContextCompat.getColor(this,R.color.deepGrey))//输入框背景设置rich_Editor.setEditorBackgroundColor(Color.WHITE)//输入框文本paddingrich_Editor.setPadding(10, 10, 10, 10)//输入提示文本rich_Editor.setPlaceholder("点击输入正文!~")rich_Editor.setOnDecorationChangeListener { _, types ->val flagArr = ArrayList<String>()for (i in types.indices) {flagArr.add(types[i].name)}if (flagArr.contains("BOLD")) {button_bold.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.colorPrimary))mEditorMenuFragment.updateActionStates(ActionType.BOLD, true)} else {button_bold.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))mEditorMenuFragment.updateActionStates(ActionType.BOLD, false)}if (flagArr.contains("ITALIC")) {button_italic.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.colorPrimary))mEditorMenuFragment.updateActionStates(ActionType.ITALIC, true)} else {button_italic.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))mEditorMenuFragment.updateActionStates(ActionType.ITALIC, false)}if (flagArr.contains("STRIKETHROUGH")) {button_strikethrough.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.colorPrimary))mEditorMenuFragment.updateActionStates(ActionType.STRIKETHROUGH, true)} else {button_strikethrough.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))mEditorMenuFragment.updateActionStates(ActionType.STRIKETHROUGH, false)}if (flagArr.contains("JUSTIFYCENTER")) {button_align_center.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.colorPrimary))button_align_left.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))button_align_right.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))mEditorMenuFragment.updateActionStates(ActionType.JUSTIFY_CENTER, true)mEditorMenuFragment.updateActionStates(ActionType.JUSTIFY_LEFT, false)mEditorMenuFragment.updateActionStates(ActionType.JUSTIFY_RIGHT, false)} else {button_align_center.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))mEditorMenuFragment.updateActionStates(ActionType.JUSTIFY_CENTER, false)}if (flagArr.contains("JUSTIFYLEFT")) {button_align_center.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))button_align_left.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.colorPrimary))button_align_right.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))mEditorMenuFragment.updateActionStates(ActionType.JUSTIFY_CENTER, false)mEditorMenuFragment.updateActionStates(ActionType.JUSTIFY_LEFT, true)mEditorMenuFragment.updateActionStates(ActionType.JUSTIFY_RIGHT, false)} else {button_align_left.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))mEditorMenuFragment.updateActionStates(ActionType.JUSTIFY_LEFT, false)}if (flagArr.contains("JUSTIFYRIGHT")) {button_align_center.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))button_align_left.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))button_align_right.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.colorPrimary))mEditorMenuFragment.updateActionStates(ActionType.JUSTIFY_CENTER, false)mEditorMenuFragment.updateActionStates(ActionType.JUSTIFY_LEFT, false)mEditorMenuFragment.updateActionStates(ActionType.JUSTIFY_RIGHT, true)} else {button_align_right.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))mEditorMenuFragment.updateActionStates(ActionType.JUSTIFY_RIGHT, false)}if (flagArr.contains("UNDERLINE")) {button_underline.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.colorPrimary))mEditorMenuFragment.updateActionStates(ActionType.UNDERLINE, true)} else {button_underline.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))mEditorMenuFragment.updateActionStates(ActionType.UNDERLINE, false)}if (flagArr.contains("ORDEREDLIST")) {button_list_ol.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.colorPrimary))button_list_ul.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))mEditorMenuFragment.updateActionStates(ActionType.ORDERED, true)mEditorMenuFragment.updateActionStates(ActionType.UNORDERED, false)} else {button_list_ol.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))mEditorMenuFragment.updateActionStates(ActionType.ORDERED, false)}if (flagArr.contains("UNORDEREDLIST")) {button_list_ul.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.colorPrimary))button_list_ol.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))mEditorMenuFragment.updateActionStates(ActionType.ORDERED, false)mEditorMenuFragment.updateActionStates(ActionType.UNORDERED, true)} else {button_list_ul.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))mEditorMenuFragment.updateActionStates(ActionType.UNORDERED, false)}}

插入图片和视频:原本是调用特有的方法实现,但是可定制化不高,我就改成了插入html的形式

public void insertVideoPercentage(String url, String width, String height) {exec("javascript:RE.prepareInsert();");
//        exec("javascript:RE.insertVideoWH('" + url + "', '" + width + "', '" + height + "');");String testStr = "<video src=\"" + url + "\" width=\""+ width +"\"  height=\""+ height +"\" controls></video><br>";exec("javascript:RE.insertHTML('" + testStr + "');");}
public void insertImage(String url, String alt) {exec("javascript:RE.prepareInsert();");String testStr = "<img src=\"" + url + "\" alt=\"" + alt + "\" width=\"80%\"><br><br>";exec("javascript:RE.insertHTML('" + testStr + "');");}
	// 以下图片视频地址,来自网络素材fun selectImage(){// 进入相册选择照片之后可以本地文件直接插入html,或者上传到服务器获取地址再插入html中val path="https://upload-images.jianshu.io/upload_images/5809200-a99419bb94924e6d.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"againEdit()rich_Editor.insertImage(path, "dachshund")KeyBoardUtils.openKeyboard(edit_name, this@RichTextActivity)}fun selectVideo(){// 选择视频之后可以本地文件直接插入html,或者上传到服务器获取地址再插入html中val path="https://media.w3.org/2010/05/sintel/trailer.mp4"againEdit()rich_Editor.insertVideoPercentage(path, "80%","30%")KeyBoardUtils.openKeyboard(edit_name, this@RichTextActivity)}

图片删除:插入图片后点击图片可将图片删除:

@SuppressLint("ClickableViewAccessibility")public void setImageClickListener(ImageClickListener imageClickListener) {this.imageClickListener = imageClickListener;if (this.imageClickListener != null) {RichEditor.this.setOnTouchListener(new OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:DownX = (int) event.getX();DownY = (int) event.getY();moveX = 0;moveY = 0;currentMS = System.currentTimeMillis();//long currentMS     获取系统时间break;case MotionEvent.ACTION_MOVE:moveX += Math.abs(event.getX() - DownX);//X轴距离moveY += Math.abs(event.getY() - DownY);//y轴距离DownX = event.getX();DownY = event.getY();break;case MotionEvent.ACTION_UP:long moveTime = System.currentTimeMillis() - currentMS;//移动时间//判断是否继续传递信号if (moveTime < 400 && (moveX < 25 && moveY < 25)) {//这里是点击HitTestResult mResult = getHitTestResult();if (mResult != null) {final int type = mResult.getType();if (type == HitTestResult.IMAGE_TYPE) {//|| type == WebView.HitTestResult.IMAGE_ANCHOR_TYPE || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE//如果是点击图片String imageUrl = mResult.getExtra();setInputEnabled(false);postDelayed(new Runnable() {@Overridepublic void run() {if (imageClickListener != null) {if (imageUrl.contains("file://")) {//说明是本地文件去除String newImageUrl = imageUrl.replace("file://", "");imageClickListener.onImageClick(newImageUrl);} else {imageClickListener.onImageClick(imageUrl);}}}}, 200);} else {//不是点击的图片}}}break;}return false;}});}}
view.findViewById<View>(R.id.linear_delete_pic).setOnClickListener { v: View? ->//删除图片val removeUrl ="<img src=\"$currentUrl\" alt=\"dachshund\" width=\"80%\"><br>"val newUrl: String = rich_Editor.html.replace(removeUrl, "")currentUrl = ""rich_Editor.html = newUrlif (RichUtils.isEmpty(rich_Editor.html)) {rich_Editor.html = ""}popupWindow?.dismiss()}

插入链接:必须要先执行一次restorerange ,控制用户选择的文本范围或光标的当前位置

RE.restorerange = function(){var selection = window.getSelection();selection.removeAllRanges();var range = document.createRange();range.setStart(RE.currentSelection.startContainer, RE.currentSelection.startOffset);range.setEnd(RE.currentSelection.endContainer, RE.currentSelection.endOffset);selection.addRange(range);
}RE.insertLink = function(url, title) {RE.restorerange();var sel = document.getSelection();if (sel.toString().length == 0) {document.execCommand("insertHTML",false,"<a href='"+url+"'>"+title+"</a>");} else if (sel.rangeCount) {var el = document.createElement("a");el.setAttribute("href", url);el.setAttribute("title", title);var range = sel.getRangeAt(0).cloneRange();range.surroundContents(el);sel.removeAllRanges();sel.addRange(range);}RE.callback();
}

焦点的处理:

(1) 每一次调用排版功能都要调用一次,重新处理输入焦点,避免焦点冲突

private fun againEdit() {//如果第一次点击例如加粗,没有焦点时,获取焦点并弹出软键盘rich_Editor.focusEditor()KeyBoardUtils.openKeyboard(edit_name, this@RichTextActivity)}
public void focusEditor() {requestFocus();exec("javascript:RE.focus();");}
RE.focus = function() {var range = document.createRange();range.selectNodeContents(RE.editor);range.collapse(false);
//    var selection = window.getSelection();
//    selection.removeAllRanges();
//    selection.addRange(range);RE.editor.focus();
}

(2)这里弹出排版页面时必须同时弹出键盘,否则输入框的焦点消失,之前所设置的格式也会随之消失。比如我在底部点了个加粗效果,再点击排版弹窗时,加粗效果应该存在。但是一旦键盘关闭焦点消失,光标的效果也会被重置。
在这里插入图片描述

	KeyboardUtils.registerSoftInputChangedListener(this) { height: Int ->//键盘打开isKeyboardShowing = height > 0if (height > 0) {//fl_action.visibility = View.GONEval params = fl_action.layoutParamsparams.height = height/2fl_action.layoutParams = params} else{//if (fl_action.visibility != View.VISIBLE)fl_action.visibility = View.GONEiv_action.setColorFilter(ContextCompat.getColor(this@RichTextActivity, R.color.tintColor))}}iv_action.setOnClickListener {if (fl_action.visibility == View.VISIBLE) {fl_action.visibility = View.GONEiv_action.setColorFilter(ContextCompat.getColor(this@RichTextActivity, R.color.tintColor))} else {
//                if (isKeyboardShowing) {
//                    KeyboardUtils.hideSoftInput(this@RichTextActivity)
//                }KeyboardUtils.showSoftInput(this@RichTextActivity)fl_action.visibility = View.VISIBLEiv_action.setColorFilter(ContextCompat.getColor(this@RichTextActivity, R.color.colorPrimary))}}

主要的逻辑代码(RichTextActivity.kt):

class RichTextActivity :AppCompatActivity(), OnActionPerformListener {private var popupWindow //编辑图片的pop: CommonPopupWindow? = nullprivate var currentUrl=""private val mEditorMenuFragment by lazy{ EditorMenuFragment(R.id.fl_action) }private var isKeyboardShowing = falseprivate val inputLinkDialog by lazy { InputLinkDialog(this, sureListener = {href, name ->againEdit()rich_Editor.insertLink(href,name)}) }override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_rich_text)initData()}private fun initData(){initPop()initEditor()}private fun initEditor() {//输入框显示字体的大小rich_Editor.setEditorFontSize(16)//输入框显示字体的颜色rich_Editor.setEditorFontColor(ContextCompat.getColor(this,R.color.deepGrey))//输入框背景设置rich_Editor.setEditorBackgroundColor(Color.WHITE)//输入框文本paddingrich_Editor.setPadding(10, 10, 10, 10)//输入提示文本rich_Editor.setPlaceholder("点击输入正文!~")//文本输入框监听事件rich_Editor.setOnTextChangeListener { text -> Log.e("富文本文字变动", text!!) }edit_name.addTextChangedListener(object : TextWatcher {override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}override fun afterTextChanged(s: Editable) {tv_sum.text = edit_name.text.toString().length.toString()+"/100"val html = rich_Editor.html}})rich_Editor.setOnDecorationChangeListener { _, types ->val flagArr = ArrayList<String>()for (i in types.indices) {flagArr.add(types[i].name)}if (flagArr.contains("BOLD")) {button_bold.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.colorPrimary))mEditorMenuFragment.updateActionStates(ActionType.BOLD, true)} else {button_bold.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))mEditorMenuFragment.updateActionStates(ActionType.BOLD, false)}if (flagArr.contains("ITALIC")) {button_italic.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.colorPrimary))mEditorMenuFragment.updateActionStates(ActionType.ITALIC, true)} else {button_italic.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))mEditorMenuFragment.updateActionStates(ActionType.ITALIC, false)}if (flagArr.contains("STRIKETHROUGH")) {button_strikethrough.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.colorPrimary))mEditorMenuFragment.updateActionStates(ActionType.STRIKETHROUGH, true)} else {button_strikethrough.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))mEditorMenuFragment.updateActionStates(ActionType.STRIKETHROUGH, false)}if (flagArr.contains("JUSTIFYCENTER")) {button_align_center.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.colorPrimary))button_align_left.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))button_align_right.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))mEditorMenuFragment.updateActionStates(ActionType.JUSTIFY_CENTER, true)mEditorMenuFragment.updateActionStates(ActionType.JUSTIFY_LEFT, false)mEditorMenuFragment.updateActionStates(ActionType.JUSTIFY_RIGHT, false)} else {button_align_center.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))mEditorMenuFragment.updateActionStates(ActionType.JUSTIFY_CENTER, false)}if (flagArr.contains("JUSTIFYLEFT")) {button_align_center.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))button_align_left.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.colorPrimary))button_align_right.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))mEditorMenuFragment.updateActionStates(ActionType.JUSTIFY_CENTER, false)mEditorMenuFragment.updateActionStates(ActionType.JUSTIFY_LEFT, true)mEditorMenuFragment.updateActionStates(ActionType.JUSTIFY_RIGHT, false)} else {button_align_left.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))mEditorMenuFragment.updateActionStates(ActionType.JUSTIFY_LEFT, false)}if (flagArr.contains("JUSTIFYRIGHT")) {button_align_center.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))button_align_left.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))button_align_right.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.colorPrimary))mEditorMenuFragment.updateActionStates(ActionType.JUSTIFY_CENTER, false)mEditorMenuFragment.updateActionStates(ActionType.JUSTIFY_LEFT, false)mEditorMenuFragment.updateActionStates(ActionType.JUSTIFY_RIGHT, true)} else {button_align_right.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))mEditorMenuFragment.updateActionStates(ActionType.JUSTIFY_RIGHT, false)}if (flagArr.contains("UNDERLINE")) {button_underline.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.colorPrimary))mEditorMenuFragment.updateActionStates(ActionType.UNDERLINE, true)} else {button_underline.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))mEditorMenuFragment.updateActionStates(ActionType.UNDERLINE, false)}if (flagArr.contains("ORDEREDLIST")) {button_list_ol.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.colorPrimary))button_list_ul.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))mEditorMenuFragment.updateActionStates(ActionType.ORDERED, true)mEditorMenuFragment.updateActionStates(ActionType.UNORDERED, false)} else {button_list_ol.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))mEditorMenuFragment.updateActionStates(ActionType.ORDERED, false)}if (flagArr.contains("UNORDEREDLIST")) {button_list_ul.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.colorPrimary))button_list_ol.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))mEditorMenuFragment.updateActionStates(ActionType.ORDERED, false)mEditorMenuFragment.updateActionStates(ActionType.UNORDERED, true)} else {button_list_ul.setColorFilter(ContextCompat.getColor(this@RichTextActivity,R.color.tintColor))mEditorMenuFragment.updateActionStates(ActionType.UNORDERED, false)}}rich_Editor.setImageClickListener { imageUrl ->currentUrl = imageUrlpopupWindow?.showBottom(ll_container, 0.5f)}button_rich_do.setOnClickListener { rich_Editor.redo() }button_rich_undo.setOnClickListener { rich_Editor.undo()}button_bold.setOnClickListener {againEdit()rich_Editor.setBold() }button_underline.setOnClickListener {againEdit()rich_Editor.setUnderline()}button_italic.setOnClickListener {againEdit()rich_Editor.setItalic()}button_strikethrough.setOnClickListener {againEdit()rich_Editor.setStrikeThrough()}//字号button_h1.setOnClickListener {againEdit()rich_Editor.setFontSize(2)}button_h2.setOnClickListener {againEdit()rich_Editor.setFontSize(3)}button_h3.setOnClickListener {againEdit()rich_Editor.setFontSize(4)}button_h4.setOnClickListener {againEdit()rich_Editor.setFontSize(5)}button_indent_decrease.setOnClickListener {againEdit()rich_Editor.setOutdent()}button_indent_increase.setOnClickListener {againEdit()rich_Editor.setIndent()}button_list_ul.setOnClickListener {againEdit()rich_Editor.setBullets()}button_list_ol.setOnClickListener {againEdit()rich_Editor.setNumbers()}button_align_center.setOnClickListener {againEdit()rich_Editor.setAlignCenter()}button_align_left.setOnClickListener {againEdit()rich_Editor.setAlignLeft()}button_align_right.setOnClickListener {againEdit()rich_Editor.setAlignRight()}button_link.setOnClickListener {KeyboardUtils.hideSoftInput(this@RichTextActivity)inputLinkDialog.show()}//选择图片button_image.setOnClickListener {selectImage()}//选择视频button_video.setOnClickListener {selectVideo()}mEditorMenuFragment.setActionClickListener(this)supportFragmentManager.beginTransaction().add(R.id.fl_action, mEditorMenuFragment, EditorMenuFragment::class.java.name).commit()KeyboardUtils.registerSoftInputChangedListener(this) { height: Int ->//键盘打开isKeyboardShowing = height > 0if (height > 0) {//fl_action.visibility = View.GONEval params = fl_action.layoutParamsparams.height = height/2fl_action.layoutParams = params} else{//if (fl_action.visibility != View.VISIBLE)fl_action.visibility = View.GONEiv_action.setColorFilter(ContextCompat.getColor(this@RichTextActivity, R.color.tintColor))}}iv_action.setOnClickListener {if (fl_action.visibility == View.VISIBLE) {fl_action.visibility = View.GONEiv_action.setColorFilter(ContextCompat.getColor(this@RichTextActivity, R.color.tintColor))} else {
//                if (isKeyboardShowing) {
//                    KeyboardUtils.hideSoftInput(this@RichTextActivity)
//                }KeyboardUtils.showSoftInput(this@RichTextActivity)fl_action.visibility = View.VISIBLEiv_action.setColorFilter(ContextCompat.getColor(this@RichTextActivity, R.color.colorPrimary))}}}private fun initPop() {val view= LayoutInflater.from(this@RichTextActivity).inflate(R.layout.pop_picture, null)view.findViewById<View>(R.id.linear_cancle).setOnClickListener { popupWindow?.dismiss() }view.findViewById<View>(R.id.linear_delete_pic).setOnClickListener { v: View? ->//删除图片val removeUrl ="<img src=\"$currentUrl\" alt=\"dachshund\" width=\"80%\"><br>"val newUrl: String = rich_Editor.html.replace(removeUrl, "")currentUrl = ""rich_Editor.html = newUrlif (RichUtils.isEmpty(rich_Editor.html)) {rich_Editor.html = ""}popupWindow?.dismiss()}popupWindow = CommonPopupWindow.Builder(this@RichTextActivity).setView(view).setWidthAndHeight(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT).setOutsideTouchable(true) //在外不可用手指取消.setAnimationStyle(R.style.pop_animation) //设置popWindow的出场动画.create()popupWindow?.setOnDismissListener(PopupWindow.OnDismissListener {rich_Editor.setInputEnabled(true)})}private fun againEdit() {//如果第一次点击例如加粗,没有焦点时,获取焦点并弹出软键盘rich_Editor.focusEditor()KeyBoardUtils.openKeyboard(edit_name, this@RichTextActivity)}override fun onActionPerform(type: ActionType?, vararg values: Any?) {var value = ""if (values != null && values.isNotEmpty()) {value = values[0] as String}againEdit()when (type) {ActionType.FORE_COLOR -> { rich_Editor.setTextColor(value)}ActionType.BOLD -> { rich_Editor.setBold()}ActionType.ITALIC -> rich_Editor.setItalic()ActionType.UNDERLINE -> rich_Editor.setUnderline()ActionType.STRIKETHROUGH -> rich_Editor.setStrikeThrough()ActionType.H1 -> {rich_Editor.setFontSize(2)mEditorMenuFragment.updateActionStates(ActionType.H1,true)mEditorMenuFragment.updateActionStates(ActionType.H2,false)mEditorMenuFragment.updateActionStates(ActionType.H3,false)mEditorMenuFragment.updateActionStates(ActionType.H4,false)}ActionType.H2 -> {rich_Editor.setFontSize(3)mEditorMenuFragment.updateActionStates(ActionType.H1,false)mEditorMenuFragment.updateActionStates(ActionType.H2,true)mEditorMenuFragment.updateActionStates(ActionType.H3,false)mEditorMenuFragment.updateActionStates(ActionType.H4,false)}ActionType.H3 -> {rich_Editor.setFontSize(4)mEditorMenuFragment.updateActionStates(ActionType.H1,false)mEditorMenuFragment.updateActionStates(ActionType.H2,false)mEditorMenuFragment.updateActionStates(ActionType.H3,true)mEditorMenuFragment.updateActionStates(ActionType.H4,false)}ActionType.H4 -> {rich_Editor.setFontSize(5)mEditorMenuFragment.updateActionStates(ActionType.H1,false)mEditorMenuFragment.updateActionStates(ActionType.H2,false)mEditorMenuFragment.updateActionStates(ActionType.H3,false)mEditorMenuFragment.updateActionStates(ActionType.H4,true)}ActionType.JUSTIFY_LEFT -> rich_Editor.setAlignLeft()ActionType.JUSTIFY_CENTER -> rich_Editor.setAlignCenter()ActionType.JUSTIFY_RIGHT -> rich_Editor.setAlignRight()ActionType.INDENT -> rich_Editor.setIndent()ActionType.OUTDENT -> rich_Editor.setOutdent()ActionType.UNORDERED->rich_Editor.setBullets()ActionType.ORDERED->rich_Editor.setNumbers()ActionType.IMAGE -> {selectImage()}ActionType.LINK -> {KeyboardUtils.hideSoftInput(this@RichTextActivity)inputLinkDialog.show()}ActionType.VIDEO -> {selectVideo()}else -> {}}}// 以下图片视频地址,来自网络素材fun selectImage(){// 进入相册选择照片之后可以本地文件直接插入html,或者上传到服务器获取地址再插入html中val path="https://upload-images.jianshu.io/upload_images/5809200-a99419bb94924e6d.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"againEdit()rich_Editor.insertImage(path, "dachshund")KeyBoardUtils.openKeyboard(edit_name, this@RichTextActivity)}fun selectVideo(){// 选择视频之后可以本地文件直接插入html,或者上传到服务器获取地址再插入html中val path="https://media.w3.org/2010/05/sintel/trailer.mp4"againEdit()rich_Editor.insertVideoPercentage(path, "80%","30%")KeyBoardUtils.openKeyboard(edit_name, this@RichTextActivity)}
}

4. 完整代码

完整代码我已上传github,需要的可以自行pull:https://github.com/FullCourage/RichEditor


总结

这个功能是之前做的,时间间隔有点久了,有些细微的点已经记不得了,由此事后的总结记录就格外重要。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/353515.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Boosting原理代码实现

1&#xff0e;提升方法是将弱学习算法提升为强学习算法的统计学习方法。在分类学习中&#xff0c;提升方法通过反复修改训练数据的权值分布&#xff0c;构建一系列基本分类器&#xff08;弱分类器&#xff09;&#xff0c;并将这些基本分类器线性组合&#xff0c;构成一个强分类…

什么是拷贝?我:Ctrl + C ...

前言 当谈及拷贝&#xff0c;你的第一印象会不会和我一样&#xff0c;ctrl c ctrl v ... &#xff1b;虽然效果和拷贝是一样的&#xff0c;但是你知道拷贝的原理以及它的实现方法吗&#xff1f;今天就让我们一起探究一下拷贝中深藏的知识点吧。 拷贝 首先来看下面一段代码…

Vue10-实战快速上手

实战快速上手 我们采用实战教学模式并结合ElementUI组件库&#xff0c;将所需知识点应用到实际中&#xff0c;以最快速度带领大家掌握Vue的使用&#xff1b; 1、创建工程 注意&#xff1a;命令行都要使用管理员模式运行 1、创建一个名为hello-vue的工程vue init webpack hel…

iview 组件里面的(任何一个月)整月日期全部选中_iview时间轴选中有历史记录日期

iview 组件里面的整月日期全部选中&#xff1a; ①&#xff1a;第一种是当前月的日期全部选中&#xff1a; 先上效果图&#xff1a;当前月分 获取到的值&#xff1a; 当前月的方法&#xff1a; // getDateStr() {// var curDate new Date();// var curMonth curDate.ge…

【HTML01】HTML基础-基本元素-附带案例-作业

文章目录 HTML 概述学HTML到底学什么HTML的基本结构HTML的注释的作用html的语法HTML的常用标签&#xff1a;相关单词参考资料 HTML 概述 英文全称&#xff1a;Hyper Text Markup Language 中文&#xff1a;超文本标记语言&#xff0c;就将常用的50多个标记嵌入在纯文本中&…

python pytest 参数化的几种方式

一、使用pytest.mark.parametrize装饰器&#xff1a; 可以使用pytest提供的pytest.mark.parametrize装饰器来指定参数化测试的参数。下面是一个示例&#xff1a; import pytest# pytest.mark.parametrize装饰器 # 其中num expected&#xff0c;分别对应(1, 1),(2, 4),(3, 9)&…

c语言中的字符函数

1.字符分类函数 c语言中有一系列函数是专门做字符分类的&#xff0c;也就是一个字符属于什么类型的字符。这些函数的使用需要包含一个头文件是ctype.h 可能你看这些感觉很懵&#xff0c;我以islower举例 #include<ctype.h> int main() {int retislower(A);printf("…

Git记录 上传至Gitee

1.GitHub拉去的代码需要上传至自己的Gitee需要清除原有remote服务器信息 查看原始远程服务器信息&#xff0c;后删除远程服务器信息 git remote -v git remote rm origin 2.Gitee新建软件仓库 法1&#xff09;不用初始化仓库&#xff0c;初始化会自动生成.git。如果本地.git…

Qt项目天气预报(3) - qt的http编程获取天气数据

概念 Qt中的HTTP编程主要涉及使用Qt的网络模块来进行HTTP请求和处理HTTP响应。Qt提供了一系列类来处理网络通信&#xff0c;其中最常用的类是 QNetworkAccessManager 、 QNetworkRequest 、 QNetworkReply 以及相关的支持类。 编程实例 以下是一个基本的HTTP编程示例&#xff0…

LVGL开发教程-objects对象

知不足而奋进 望远山而前行 目录 知不足而奋进 望远山而前行​ 文章目录 前言 1.图层 2.objects 2.1 位置 2.2 尺寸 2.3 align 2.4 样式 总结 前言 在嵌入式 GUI 开发中&#xff0c;LVGL&#xff08;Light and Versatile Graphics Library&#xff09;是一个强大的工…

HTTP性能测试工具-wrk

wrk性能测试工具详解 wrk是一款轻量级但功能强大的HTTP基准测试工具&#xff0c;主要用于在单机多核CPU环境下对HTTP服务进行性能测试。它通过利用系统自带的高性能I/O机制&#xff08;如epoll、kqueue等&#xff09;&#xff0c;结合多线程和事件模式&#xff0c;能够产生大量…

MAGs培养有线索了?宏组学中未培养微生物表型与培养条件预测

宏基因组测序技术让人们对地球上微生物的多样性有了更深入的了解&#xff0c;但分离培养是研究微生物的生理代谢功能并解析其生态作用的关键。2023年11月的世界微生物数据中心&#xff08;WDCM&#xff09;年会中&#xff0c;全面启动了全球“未培养微生物培养组”计划&#xf…

27、matlab傅里叶变换:fft()函数

1、傅里叶变换简介 傅里叶变换是数学中一种非常重要的工具&#xff0c;用于将一个函数&#xff08;通常是时域函数&#xff09;分解成一组正弦和余弦函数的和。通过傅里叶变换&#xff0c;可以将一个信号从时域转换到频域&#xff0c;以便更好地理解信号的频率成分和频谱特征。…

[保姆级教程]uniapp实现页面路由配置

文章目录 新建目录新建页面配置页面路由修改tabBar地址其他&#xff1a;在package.json中的pages配置详细 新建目录 先点击src–》新建–》目录 输入名称&#xff0c;并以此类推完成所有新建目录 新建页面 右击目录&#xff0c;点击新建–》vue文件 弹出弹框&#xff0c;…

JVM性能优化工具及问题排查

jvm性能优化工具 jdk提供给我们了很实用的工具来分析JVM的状态&#xff0c;线程以及配置&#xff0c;这些工具包含于jdk中&#xff0c;并且以java实现&#xff0c;是JVM性能优化必不可少的工具集&#xff0c;这些工具都在$JAVA_HOME/bin下 jps、jinfo、jstack、jmap、jstat基本…

力扣793. 阶乘函数后 K 个零

Problem: 793. 阶乘函数后 K 个零 文章目录 题目描述思路即解法复杂度Code 题目描述 思路即解法 1.根据题意可知即是要求取满足条件的n最小是多少&#xff0c;最大是多少&#xff0c;最大值和最小值一减&#xff0c;就可以算出来有多少个n满足条件了。 2.由于题目中的阶乘存在单…

springboot连接多个库

一个SpringBoot项目&#xff0c;同时连接两个数据库&#xff1a;比如一个是Mysql数据库&#xff0c;一个是oracle数据库&#xff08;啥数据库都一样&#xff0c;连接两个同为oracle的数据库&#xff0c;或两个不同的数据库&#xff0c;只需要更改对应的driver-class-name和jdbc…

读AI新生:破解人机共存密码笔记05逻辑

1. 困难问题 1.1. 管理政府或教授分子生物学之类的问题要困难得多 1.2. 这些环境很复杂&#xff0c;大部分是不可观察的&#xff08;一个国家的状态&#xff0c;一个学生的思想状态&#xff09;&#xff0c;还有更多的对象和对象类型&#xff0c;对动作…

云上战场:ToDesk云电脑、青椒云、顺网云全面对决

前言 记得端午放假期间&#xff0c;我已经在旅游的路上了&#xff0c;老板突然发短信&#xff0c;让我给他画个图&#xff0c;我手上的战斗机已经是十年前的老古董了(平常一直用的公司电脑&#xff0c;也没想过要换)&#xff0c;压根满足不了老板的任务要求&#xff0c;不得已&…

了解振弦采集仪在建筑物安全监测中的应用与研究

了解振弦采集仪在建筑物安全监测中的应用与研究 摘要&#xff1a;河北稳控科技振弦采集仪是一种常用的结构物安全监测设备&#xff0c;广泛应用于建筑物、桥梁、塔楼等工程结构的监测。本文将从振弦采集仪的原理、应用案例和研究进展等方面进行详细介绍&#xff0c;以便更好地…