【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.RichEditor
        android: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)
        //输入框文本padding
        rich_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() {
                @Override
                public 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() {
                                            @Override
                                            public 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 = newUrl
            if (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 > 0
            if (height > 0) {
                //fl_action.visibility = View.GONE
                val params = fl_action.layoutParams
                params.height = height/2
                fl_action.layoutParams = params
            } else{
                //if (fl_action.visibility != View.VISIBLE)
                fl_action.visibility = View.GONE
                iv_action.setColorFilter(ContextCompat.getColor(this@RichTextActivity, R.color.tintColor))
            }
        }

        iv_action.setOnClickListener {
            if (fl_action.visibility == View.VISIBLE) {
                fl_action.visibility = View.GONE
                iv_action.setColorFilter(ContextCompat.getColor(this@RichTextActivity, R.color.tintColor))
            } else {
//                if (isKeyboardShowing) {
//                    KeyboardUtils.hideSoftInput(this@RichTextActivity)
//                }
                KeyboardUtils.showSoftInput(this@RichTextActivity)
                fl_action.visibility = View.VISIBLE
                iv_action.setColorFilter(ContextCompat.getColor(this@RichTextActivity, R.color.colorPrimary))
            }
        }

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

class RichTextActivity :AppCompatActivity(), OnActionPerformListener {


    private var popupWindow //编辑图片的pop
            : CommonPopupWindow? = null
    private var currentUrl=""

    private val mEditorMenuFragment by lazy{ EditorMenuFragment(R.id.fl_action) }
    private var isKeyboardShowing = false

    private 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)
        //输入框文本padding
        rich_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 = imageUrl
            popupWindow?.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 > 0
            if (height > 0) {
                //fl_action.visibility = View.GONE
                val params = fl_action.layoutParams
                params.height = height/2
                fl_action.layoutParams = params
            } else{
                //if (fl_action.visibility != View.VISIBLE)
                fl_action.visibility = View.GONE
                iv_action.setColorFilter(ContextCompat.getColor(this@RichTextActivity, R.color.tintColor))
            }
        }

        iv_action.setOnClickListener {
            if (fl_action.visibility == View.VISIBLE) {
                fl_action.visibility = View.GONE
                iv_action.setColorFilter(ContextCompat.getColor(this@RichTextActivity, R.color.tintColor))
            } else {
//                if (isKeyboardShowing) {
//                    KeyboardUtils.hideSoftInput(this@RichTextActivity)
//                }
                KeyboardUtils.showSoftInput(this@RichTextActivity)
                fl_action.visibility = View.VISIBLE
                iv_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 = newUrl
            if (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


总结

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python Django 实现教师、学生双端登录管理系统

文章目录 Python Django 实现教师、学生双端登录管理系统引言Django框架简介环境准备模型设计用户认证视图和模板URL路由前端设计测试和部署获取开源项目参考 Python Django 实现教师、学生双端登录管理系统 引言 在当今的教育环境中&#xff0c;数字化管理系统已成为必不可少…

3.华为trunk和access接口配置

目的&#xff1a;PC1 连通三层交换机LSW1 LSW1配置 [Huawei]vlan batch 10 [Huawei]interface Vlanif 10 [Huawei-Vlanif10]ip address 10.10.10.10 24 [Huawei]int g0/0/1 [Huawei-GigabitEthernet0/0/1]port link-type trunk [Huawei-GigabitEthernet0/0/1]port trunk allow…

大数据分析-二手车用户数据可视化分析

项目背景 在当今的大数据时代&#xff0c;数据可视化扮演着至关重要的角色。随着信息的爆炸式增长&#xff0c;我们面临着前所未有的数据挑战。这些数据可能来自社交媒体、商业交易、科学研究、医疗记录等各个领域&#xff0c;它们庞大而复杂&#xff0c;难以通过传统的数据处…

SpringBoot启动流程、起步依赖、配置文件、运行方式与核心注解

讲一讲SpringBoot启动流程 springboot项目在启动的时候, 首先会执行启动引导类里面的SpringApplication.run(AdminApplication.class, args)方法 这个run方法主要做的事情可以分为三个部分 : 第一部分进行SpringApplication的初始化模块&#xff0c;配置一些基本的环境变量、…

LangChain-ChatGLM本地搭建|报错合集(win10)

安装过程 1. 创建虚拟环境 conda create -n langchain-chatglm python3.10 conda activate langchain-chatglm2. 部署 langchain-ChatGLM git clone https://github.com/imClumsyPanda/langchain-ChatGLMpip3 install -r requirements.txt pip3 install -U gradio pip3 inst…

python文件操作、文件操作、读写文件、写模式

with读取文件数据内容 with open(filepath,mode,encoding) as file:#具体操作,例如&#xff1a;print(file.read())#查看文件所有的内容。 with&#xff1a;Python中的一个上下文管理器&#xff0c;用于简化资源的管理和释放。它可以用于任意需要进行资源分配和释放的情境…

springboot 腾讯地图接口验签 java

1. 原因 需求需要通过小程序定位拿到用户所在行政区信息,但是小程序定位只能拿到经纬度信息,所以需要调用腾讯地图的逆地址解析(我认为:微信是腾讯的,那么使用腾讯地图的逆地址解析经度应该不会损失太多)如果WebServiceAPI Key配置中签名校验,那么调用接口就需要进行验签 2. W…

halcon ocr识别字符

基本代码 create_text_model_reader (auto, Universal_0-9A-Z_Rej.occ, TextModel) * Optionally specify text properties set_text_model_param (TextModel, [min_char_height,polarity], [30, dark_on_light]) find_text (ImageResult, TextModel, TextResultID) * Return c…

postman断言及变量及参数化

1&#xff1a;postman断言 断言&#xff1a;判断接口是否执行成功的过程 针对接口请求完成之后&#xff0c;针对他的响应状态码及响应信息进行判断,代码如下&#xff1a; //判断响应信息状态码是否正确 pm.test("Status code is 200", function () { pm.response.…

长短期记忆神经网络(LSTM)的回归预测(免费完整源代码)【MATLAB】

LSTM&#xff08;Long Short-Term Memory&#xff0c;长短期记忆网络&#xff09;是一种特殊类型的递归神经网络&#xff08;RNN&#xff09;&#xff0c;专门用于处理和预测基于时间序列的数据。与传统RNN相比&#xff0c;LSTM在处理长期依赖问题时具有显著优势。 LSTM的基本…

Linux1(介绍与基本命令1)

目录 一、初始Linux 1. Linux的起源 2. Linux是什么&#xff1f; 3. Linux内核版本 4. Linux的应用 5. 终端 6. Shell 7. Linux目录结构 二、基本命令 1. 基本的命令格式 2. shutdown 关机命令 3. pwd 当前工作目录 4. ls 查看目录内容 5. cd 改变工作目录 …

找工作小项目:day16-重构核心库、使用智能指针(2)

day16-重构核心库、使用智能指针 太多了分一篇写。 5、EventLoop 这是一个事件轮询&#xff0c;在这个部分会通过Poller进行就绪事件的获取&#xff0c;并将事件进行处理。 头文件 这里使用了一个智能指针并使用的是unique_ptr指向Poller红黑树&#xff0c;防止所有权不止…

ctfshow web 单身杯

web签到 <?phperror_reporting(0); highlight_file(__FILE__);$file $_POST[file];if(isset($file)){if(strrev($file)$file){ //翻转函数include $file;}}要进行反转并且包含文件用data协议 自己写不好写可以用函数帮你翻转 <?php $adata:text/plain,<?eval(…

HTML中的资源提示关键词

渲染阻塞问题 之前在学习浏览器的渲染原理的时候我们就知道&#xff1a;因为浏览器一次只能开启一个渲染主线程&#xff0c;所以当浏览器解析到script标签时会停止DOM树的构建&#xff0c;转而去执行script&#xff0c;如果script中引用的是外部脚本&#xff0c;则浏览器会先从…

你还不会选ProfiNET和EtherCAT网线?

在现代工业自动化领域&#xff0c;ProfiNET和EtherCAT是两种非常流行的通信协议。选择合适的网线对于确保通信的稳定性和效率至关重要。 ProfiNET是什么&#xff1f; ProfiNET是一种基于以太网的通信协议&#xff0c;由德国西门子公司开发。它支持实时通信&#xff0c;广泛应用…

玩转Matlab-Simscape(初级)- 10 - 基于COMSOLSimulink 凸轮机构的控制仿真

** 玩转Matlab-Simscape&#xff08;初级&#xff09;- 10 - 基于COMSOL&Simulink 凸轮机构的控制仿真 ** 目录 玩转Matlab-Simscape&#xff08;初级&#xff09;- 10 - 基于COMSOL&Simulink 凸轮机构的控制仿真 前言一、简介二、在Solidworks中创建3D模型&#xff…

05-5.4.1 树的存储结构

&#x1f44b; Hi, I’m Beast Cheng &#x1f440; I’m interested in photography, hiking, landscape… &#x1f331; I’m currently learning python, javascript, kotlin… &#x1f4eb; How to reach me --> 458290771qq.com 喜欢《数据结构》部分笔记的小伙伴可以…

商家转账到零钱怎么申请

开通商家转账到零钱功能涉及到多个步骤&#xff0c;包括资格审核、材料准备、提交申请等。以下是详细的步骤&#xff1a; 1. 确认开通条件&#xff1a; - 商家需要先成为微信支付商户。 - 商家的微信支付账户没有历史违规记录。 - 商家主体为企业资质。 - 商家系统已经上线并可…

Linux驱动面试题

1.导出符号表的原理&#xff1f; 2.字符设备驱动的框架流程 open read wirte close 是系统调用&#xff08;从用户空间进入内核空间的唯一的方法&#xff09;会产生swi软中断《也会存在软中断号》&#xff08;从User模式切换到SVC&#xff08;管理模式&#xff09;下因为在…

天锐绿盾加密软件,它的适用范围是什么?

天锐绿盾数据防泄密软件的适用范围广泛&#xff0c;主要可以归纳为以下几点&#xff1a; 行业适用性&#xff1a; 适用于各个行业&#xff0c;包括但不限于制造业、设计行业、软件开发、金融服务等&#xff0c;特别是对数据安全性要求较高的行业。企业规模与类型&#xff1a; 适…