Android富文本编辑器(可添加表情)

先看几张效果图。看是不是你要的,其实也可以改为你要的
效果图:

富文本编辑器.jpg

思路

自定义WebView加载html文件,html中用可编辑的js文件。
然后自定义webview通过javascript:RE.xx方法和js交互,实际是调用evaluateJavascript和js交互,它是不是很熟悉,就是app调js的方法就是通过它

因为我不懂js,所以也是在别人的js上改的。或者有些样式不会改问前端最好

准备工作

rich_editor4.js

var RE = {};

RE.currentSelection = {
"startContainer": 0,
"startOffset": 0,
"endContainer": 0,
"endOffset": 0};

RE.editor = document.getElementById('editor');

document.addEventListener("selectionchange", function() { RE.backuprange(); });

// Initializations
RE.callback = function() {
window.location.href = "re-callback://" + encodeURI(RE.getHtml());
}

RE.setHtml = function(contents) {
RE.editor.innerHTML = decodeURIComponent(contents.replace(/\+/g, '%20'));
}

RE.getHtml = function() {
return RE.editor.innerHTML;
}

RE.getText = function() {
return RE.editor.innerText;
}

RE.setBaseTextColor = function(color) {
RE.editor.style.color  = color;
}

RE.setBaseFontSize = function(size) {
RE.editor.style.fontSize = size;
}

RE.setPadding = function(left, top, right, bottom) {
  RE.editor.style.paddingLeft = left;
  RE.editor.style.paddingTop = top;
  RE.editor.style.paddingRight = right;
  RE.editor.style.paddingBottom = bottom;
}

RE.setBackgroundColor = function(color) {
document.body.style.backgroundColor = color;
}

RE.setBackgroundImage = function(image) {
RE.editor.style.backgroundImage = image;
}

RE.setWidth = function(size) {
RE.editor.style.minWidth = size;
}

RE.setHeight = function(size) {
RE.editor.style.height = size;
}

RE.setTextAlign = function(align) {
RE.editor.style.textAlign = align;
}

RE.setVerticalAlign = function(align) {
RE.editor.style.verticalAlign = align;
}

RE.setPlaceholder = function(placeholder) {
RE.editor.setAttribute("placeholder", placeholder);
}

RE.setInputEnabled = function(inputEnabled) {
RE.editor.contentEditable = String(inputEnabled);
}

RE.undo = function() {
document.execCommand('undo', false, null);
}

RE.redo = function() {
document.execCommand('redo', false, null);
}

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

RE.setItalic = function() {
document.execCommand('italic', false, null);
}

RE.setSubscript = function() {
document.execCommand('subscript', false, null);
}

RE.setSuperscript = function() {
document.execCommand('superscript', false, null);
}

RE.setStrikeThrough = function() {
document.execCommand('strikeThrough', false, null);
}

RE.setUnderline = function() {
document.execCommand('underline', false, null);
}

RE.setBullets = function(b) {
document.execCommand('InsertUnorderedList', false, b);
//if(b){
//document.execCommand('InsertUnorderedList', false, b);
//}else{
//document.execCommand('InsertUnorderedList', false, null);
//}
}

RE.setNumbers = function() {
document.execCommand('insertOrderedList', false, null);
}
RE.setNumbers = function(b) {
document.execCommand('InsertOrderedList', false, b);
//if(b){
//document.execCommand('InsertOrderedList', false, b);
//  }else{
//document.execCommand('InsertOrderedList', false, null);
//  }
}

RE.setTextColor = function(color) {
RE.restorerange();
document.execCommand("styleWithCSS", null, true);
document.execCommand('foreColor', false, color);
document.execCommand("styleWithCSS", null, false);
}

RE.setTextBackgroundColor = function(color) {
RE.restorerange();
document.execCommand("styleWithCSS", null, true);
document.execCommand('hiliteColor', false, color);
document.execCommand("styleWithCSS", null, false);
}

RE.setFontSize = function(fontSize){
document.execCommand("fontSize", false, fontSize);
}

RE.setHeading = function(heading,b) {
if(b)
document.execCommand('formatBlock', false, '<h'+heading+'>');
else
document.execCommand('formatBlock', false, '<p>');
}

RE.setIndent = function() {
document.execCommand('indent', false, null);
}

RE.setOutdent = function() {
document.execCommand('outdent', false, null);
}

RE.setJustifyLeft = function() {
document.execCommand('justifyLeft', false, null);
}

RE.setJustifyCenter = function() {
document.execCommand('justifyCenter', false, null);
}

RE.setJustifyRight = function() {
document.execCommand('justifyRight', false, null);
}

//RE.setBlockquote = function() {
//document.execCommand('formatBlock', false, '<blockquote>');
//}
RE.setBlockquote = function(b) {
if(b)
document.execCommand('formatBlock', false, '<blockquote>');
else
document.execCommand('formatBlock', false, '<p>');
}

RE.insertImage = function(url, alt) {
var html = '<img  src="' + url + '" /><br/><br/>';
RE.insertHTML(html);
}

//插入分割线
RE.insertHr = function() {
var html = '<hr color=#e2e2e2 size=1 /><br/>';
RE.insertHTML(html);
}

RE.insertHTML = function(html) {
RE.restorerange();
document.execCommand('insertHTML', false, html);
}

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();
}

RE.setTodo = function(text) {
var html = '<input type="checkbox" name="'+ text +'" value="'+ text +'"/> &nbsp;';
document.execCommand('insertHTML', false, html);
}

RE.prepareInsert = function() {
RE.backuprange();
}

RE.backuprange = function(){
var selection = window.getSelection();
if (selection.rangeCount > 0) {
  var range = selection.getRangeAt(0);
  RE.currentSelection = {
  "startContainer": range.startContainer,
  "startOffset": range.startOffset,
  "endContainer": range.endContainer,
  "endOffset": range.endOffset};
}
}

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.enabledEditingItems = function(e) {
var items = [];
if (document.queryCommandState('bold')) {
items.push('bold');
}
if (document.queryCommandState('italic')) {
items.push('italic');
}
if (document.queryCommandState('subscript')) {
items.push('subscript');
}
if (document.queryCommandState('superscript')) {
items.push('superscript');
}
if (document.queryCommandState('strikeThrough')) {
items.push('strikeThrough');
}
if (document.queryCommandState('underline')) {
items.push('underline');
}
if (document.queryCommandState('insertOrderedList')) {
items.push('orderedList');
}
if (document.queryCommandState('insertUnorderedList')) {
items.push('unorderedList');
}
if (document.queryCommandState('justifyCenter')) {
items.push('justifyCenter');
}
if (document.queryCommandState('justifyFull')) {
items.push('justifyFull');
}
if (document.queryCommandState('justifyLeft')) {
items.push('justifyLeft');
}
if (document.queryCommandState('justifyRight')) {
items.push('justifyRight');
}
if (document.queryCommandState('insertHorizontalRule')) {
items.push('horizontalRule');
}
var formatBlock = document.queryCommandValue('formatBlock');
if (formatBlock.length > 0) {
items.push(formatBlock);
}

window.location.href = "re-state://" + encodeURI(items.join(','));
}

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();
}

RE.blurFocus = function() {
RE.editor.blur();
}

RE.removeFormat = function() {
document.execCommand('removeFormat', false, null);
}

// Event Listeners
RE.editor.addEventListener("input", RE.callback);
RE.editor.addEventListener("keyup", function(e) {
var KEY_LEFT = 37, KEY_RIGHT = 39;
if (e.which == KEY_LEFT || e.which == KEY_RIGHT) {
RE.enabledEditingItems(e);
}
});
RE.editor.addEventListener("click", RE.enabledEditingItems);

editor3.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_editor4.js"></script>
</body>
</html>

还有normalize.css,style.css我就不给出了,百度下也有

关键:ACRichEmojiEditor.java

public class ACRichEmojiEditor extends WebView {

public enum Type {
    BOLD,
    ITALIC,
    SUBSCRIPT,
    SUPERSCRIPT,
    STRIKETHROUGH,
    UNDERLINE,
    BLOCKQUOTE,
    NUMBERS,
    BULLETS,
    H1,
    H2,
    H3,
    H4,
    H5,
    H6
}

public interface OnTextChangeListener {

    void onTextChange(String text);
}

public interface OnDecorationStateListener {

    void onStateChangeListener(String text, List<Type> types);
}

public interface AfterInitialLoadListener {

    void onAfterInitialLoad(boolean isReady);
}

private static final String SETUP_HTML = "file:///android_asset/editor3.html";
private static final String CALLBACK_SCHEME = "re-callback://";
private static final String STATE_SCHEME = "re-state://";
private boolean isReady = false;
private String mContents;
private OnTextChangeListener mTextChangeListener;
private OnDecorationStateListener mDecorationStateListener;
private AfterInitialLoadListener mLoadListener;
private OnScrollChangedCallback mOnScrollChangedCallback;

public ACRichEmojiEditor(Context context) {
    this(context, null);
}

public ACRichEmojiEditor(Context context, AttributeSet attrs) {
    this(context, attrs, android.R.attr.webViewStyle);
}

@SuppressLint("SetJavaScriptEnabled")
public ACRichEmojiEditor(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);
}

protected EditorWebViewClient createWebviewClient() {
    return new EditorWebViewClient();
}

public void setOnTextChangeListener(OnTextChangeListener listener) {
    mTextChangeListener = listener;
}

public void setOnDecorationChangeListener(OnDecorationStateListener listener) {
    mDecorationStateListener = listener;
}

public void setOnInitialLoadListener(AfterInitialLoadListener listener) {
    mLoadListener = listener;
}

private void callback(String text) {
    mContents = text.replaceFirst(CALLBACK_SCHEME, "");
    if (mTextChangeListener != null) {
        mTextChangeListener.onTextChange(mContents);
    }
    return;
}


/**
 * WebView的滚动事件
 *
 * @param l
 * @param t
 * @param oldl
 * @param oldt
 */
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
    super.onScrollChanged(l, t, oldl, oldt);

    if (mOnScrollChangedCallback != null) {
        mOnScrollChangedCallback.onScroll(l - oldl, t - oldt);
    }

}

public OnScrollChangedCallback getOnScrollChangedCallback() {
    return mOnScrollChangedCallback;
}

public void setOnScrollChangedCallback(
        final OnScrollChangedCallback onScrollChangedCallback) {
    mOnScrollChangedCallback = onScrollChangedCallback;
}


/**
 * Impliment in the activity/fragment/view that you want to listen to the webview
 */
public interface OnScrollChangedCallback {
    void onScroll(int dx, int dy);
}


private void stateCheck(String text) {

    if (!text.contains("@_@")) {
        String state = text.replaceFirst(STATE_SCHEME, "").toUpperCase(Locale.ENGLISH);
        List<Type> types = new ArrayList<>();
        for (Type type : Type.values()) {
            if (TextUtils.indexOf(state, type.name()) != -1) {
                types.add(type);
            }
        }

        if (mDecorationStateListener != null) {
            mDecorationStateListener.onStateChangeListener(state, types);
        }
        return;
    }

    String state = text.replaceFirst(STATE_SCHEME, "").split("@_@")[0].toUpperCase(Locale.ENGLISH);
    List<Type> types = new ArrayList<>();
    for (Type type : Type.values()) {
        if (TextUtils.indexOf(state, type.name()) != -1) {
            types.add(type);
        }
    }

    if (mDecorationStateListener != null) {
        mDecorationStateListener.onStateChangeListener(state, types);
    }

    if (text.replaceFirst(STATE_SCHEME, "").split("@_@").length > 1) {
        mContents = text.replaceFirst(STATE_SCHEME, "").split("@_@")[1];
        if (mTextChangeListener != null) {
            mTextChangeListener.onTextChange(mContents);
        }
    }
}

private void applyAttributes(Context context, AttributeSet attrs) {
    final int[] attrsArray = new int[]{
            android.R.attr.gravity
    };
    TypedArray ta = context.obtainStyledAttributes(attrs, attrsArray);

    int gravity = ta.getInt(0, NO_ID);
    switch (gravity) {
        case Gravity.LEFT:
            exec("javascript:RE.setTextAlign(\"left\")");
            break;
        case Gravity.RIGHT:
            exec("javascript:RE.setTextAlign(\"right\")");
            break;
        case Gravity.TOP:
            exec("javascript:RE.setVerticalAlign(\"top\")");
            break;
        case Gravity.BOTTOM:
            exec("javascript:RE.setVerticalAlign(\"bottom\")");
            break;
        case Gravity.CENTER_VERTICAL:
            exec("javascript:RE.setVerticalAlign(\"middle\")");
            break;
        case Gravity.CENTER_HORIZONTAL:
            exec("javascript:RE.setTextAlign(\"center\")");
            break;
        case Gravity.CENTER:
            exec("javascript:RE.setVerticalAlign(\"middle\")");
            exec("javascript:RE.setTextAlign(\"center\")");
            break;
    }

    ta.recycle();
}

/**
 * setText
 *
 * @param contents
 */
public void setHtml(String contents) {
    if (contents == null) {
        contents = "";
    }
    try {
        exec("javascript:RE.setHtml('" + URLEncoder.encode(contents, "UTF-8") + "');");
    } catch (UnsupportedEncodingException e) {
        // No handling
    }
    mContents = contents;
}

/**
 * getText
 *
 * @return
 */
public String getHtml() {
    return mContents;
}

public void setEditorFontColor(int color) {
    String hex = convertHexColorString(color);
    exec("javascript:RE.setBaseTextColor('" + hex + "');");
}

public void setEditorFontSize(int px) {
    exec("javascript:RE.setBaseFontSize('" + px + "px');");
}

@Override
public void setPadding(int left, int top, int right, int bottom) {
    super.setPadding(left, top, right, bottom);
    exec("javascript:RE.setPadding('" + left + "px', '" + top + "px', '" + right + "px', '" + bottom
            + "px');");
}

@Override
public void setPaddingRelative(int start, int top, int end, int bottom) {
    // still not support RTL.
    setPadding(start, top, end, bottom);
}

public void setEditorBackgroundColor(int color) {
    setBackgroundColor(color);
}

@Override
public void setBackgroundColor(int color) {
    super.setBackgroundColor(color);
}

@Override
public void setBackgroundResource(int resid) {
    Bitmap bitmap = ACRichUtils.decodeResource(getContext(), resid);
    String base64 = ACRichUtils.toBase64(bitmap);
    bitmap.recycle();

    exec("javascript:RE.setBackgroundImage('url(data:image/png;base64," + base64 + ")');");
}

@Override
public void setBackground(Drawable background) {
    Bitmap bitmap = ACRichUtils.toBitmap(background);
    String base64 = ACRichUtils.toBase64(bitmap);
    bitmap.recycle();

    exec("javascript:RE.setBackgroundImage('url(data:image/png;base64," + base64 + ")');");
}

public void setBackground(String url) {
    exec("javascript:RE.setBackgroundImage('url(" + url + ")');");
}

public void setEditorWidth(int px) {
    exec("javascript:RE.setWidth('" + px + "px');");
}

public void setEditorHeight(int px) {
    exec("javascript:RE.setHeight('" + px + "px');");
}

public void setPlaceholder(String placeholder) {
    exec("javascript:RE.setPlaceholder('" + placeholder + "');");
}

public void loadCSS(String cssFile) {
    String jsCSSImport = "(function() {" +
            "    var head  = document.getElementsByTagName(\"head\")[0];" +
            "    var link  = document.createElement(\"link\");" +
            "    link.rel  = \"stylesheet\";" +
            "    link.type = \"text/css\";" +
            "    link.href = \"" + cssFile + "\";" +
            "    link.media = \"all\";" +
            "    head.appendChild(link);" +
            "}) ();";
    exec("javascript:" + jsCSSImport + "");
}
public  void setWebImageClick(WebView view) {
    String jsCode="javascript:(function(){" +
            "var imgs=document.getElementsByTagName(\"img\");" +
            " var array=new Array(); " +
            " for(var j=0;j<imgs.length;j++){ array[j]=imgs[j].src; }"+
            "for(var i=0;i<imgs.length;i++){" +

            "imgs[i].onclick=function(){" +
            "window.jsCallJavaObj.openImage(this.src,array);" +
            "}}})()";
    view.loadUrl(jsCode);
}
public  void setWebImageClick() {
    String jsCode="javascript:(function(){" +
            "var imgs=document.getElementsByTagName(\"img\");" +
            " var array=new Array(); " +
            " for(var j=0;j<imgs.length;j++){ array[j]=imgs[j].src; }"+
            "for(var i=0;i<imgs.length;i++){" +

            "imgs[i].onclick=function(){" +
            "window.jsCallJavaObj.openImage(this.src,array);" +
            "}}})()";
    exec("javascript:" + jsCode + "");
}
public void undo() {
    exec("javascript:RE.undo();");
}

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

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

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

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

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

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

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

public void setTextColor(int color) {
    exec("javascript:RE.prepareInsert();");

    String hex = convertHexColorString(color);
    exec("javascript:RE.setTextColor('" + hex + "');");
}

public void setTextBackgroundColor(int color) {
    exec("javascript:RE.prepareInsert();");

    String hex = convertHexColorString(color);
    exec("javascript:RE.setTextBackgroundColor('" + hex + "');");
}

public void setFontSize(int fontSize) {
    if (fontSize > 7 || fontSize < 1) {
        Log.e("RichEditor", "Font size should have a value between 1-7");
    }
    exec("javascript:RE.setFontSize('" + fontSize + "');");
}

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

public void setHeading(int heading, boolean b, boolean isItalic, boolean isBold, boolean isStrikeThrough) {
    exec("javascript:RE.prepareInsert();");

    exec("javascript:RE.setHeading('" + heading + "'," + b + ");");
}

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

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

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

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

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

public void setBlockquote(boolean b, boolean isItalic, boolean isBold, boolean isStrikeThrough) {
    exec("javascript:RE.prepareInsert();");

// if (!b) {
// if (isItalic)
// exec("javascript:RE.setItalic();");
// if (isBold)
// exec("javascript:RE.setBold();");
// if (isStrikeThrough)
// exec("javascript:RE.setStrikeThrough();");
// }
exec("javascript:RE.setBlockquote(" + b + ");");
}

//设置点点
public void setBullets() {
    exec("javascript:RE.setBullets();");
}
//设置点点
public void setBullets(boolean b) {
    exec("javascript:RE.prepareInsert();");
    exec("javascript:RE.setBullets(" + b + ");");
    //Log.d("Acheng",b+":Bullets");
}
//设置数字
public void setNumbers() {
    exec("javascript:RE.setNumbers();");
}
//设置数字
public void setNumbers(boolean b) {
    exec("javascript:RE.prepareInsert();");
    exec("javascript:RE.setNumbers(" + b + ");");
    //Log.d("Acheng",b+":Numbers");
}

public void insertImage(String url, String alt) {
    exec("javascript:RE.prepareInsert();");
    exec("javascript:RE.insertImage('" + url + "', '" + alt + "');");
}

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


public void insertLink(String href, String title) {
    exec("javascript:RE.prepareInsert();");
    exec("javascript:RE.insertLink('" + href + "', '" + title + "');");
}

public void insertTodo() {
    exec("javascript:RE.prepareInsert();");
    exec("javascript:RE.setTodo('" + ACRichUtils.getCurrentTime() + "');");
}

public void focusEditor() {
    requestFocus();
    exec("javascript:RE.focus();");
}

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

private String convertHexColorString(int color) {
    return String.format("#%06X", (0xFFFFFF & color));
}

protected void exec(final String trigger) {
    if (isReady) {
        load(trigger);
    } else {
        postDelayed(new Runnable() {
            @Override
            public void run() {
                exec(trigger);
            }
        }, 100);
    }
}

private void load(String trigger) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        evaluateJavascript(trigger, null);
    } else {
        loadUrl(trigger);
    }
}

protected class EditorWebViewClient extends WebViewClient {
    @Override
    public void onPageFinished(WebView view, String url) {
        isReady = url.equalsIgnoreCase(SETUP_HTML);
        if (mLoadListener != null) {
            mLoadListener.onAfterInitialLoad(isReady);
        }
    }

    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        String decode;
        try {
            decode = URLDecoder.decode(url, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            // No handling
            return false;
        }

        if (TextUtils.indexOf(url, CALLBACK_SCHEME) == 0) {
            callback(decode);
            return true;
        } else if (TextUtils.indexOf(url, STATE_SCHEME) == 0) {
            stateCheck(decode);
            return true;
        }

        return super.shouldOverrideUrlLoading(view, url);
    }
}
}

使用

mWebview.setHeading(1, isClick, isItalic, isBold, isStrikeThrough)

这样说肯定还有点懵,源代码地址:
https://github.com/Achenglove/ARichEditor/

它源码是不能输入表情的,而且删除完了,实际还有一个字符的。相应的类用我上面的替换就可以了。
有问题请私信和评论。我每天都看简书的

推荐阅读更多精彩内容