Android富文本编辑器(可添加表情)
先看几张效果图。看是不是你要的,其实也可以改为你要的
效果图:
思路
自定义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 +'"/> ';
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/
它源码是不能输入表情的,而且删除完了,实际还有一个字符的。相应的类用我上面的替换就可以了。
有问题请私信和评论。我每天都看简书的