Repository: ZQ330093887/RichEditotAndroid Branch: master Commit: aac44f75888d Files: 41 Total size: 100.2 KB Directory structure: gitextract_1bj4zycb/ ├── .gitignore ├── .idea/ │ ├── dictionaries/ │ │ └── zhouqiong.xml │ ├── gradle.xml │ ├── misc.xml │ ├── modules.xml │ └── runConfigurations.xml ├── README.md ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── example/ │ │ └── zhouqiong/ │ │ └── richeditotandroid/ │ │ └── ExampleInstrumentedTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── assets/ │ │ │ ├── editor.html │ │ │ ├── normalize.css │ │ │ ├── rich_editor.js │ │ │ └── style.css │ │ ├── java/ │ │ │ └── com/ │ │ │ └── example/ │ │ │ └── zhouqiong/ │ │ │ └── richeditotandroid/ │ │ │ ├── ui/ │ │ │ │ ├── MainActivity.java │ │ │ │ └── WebDataActivity.java │ │ │ ├── utils/ │ │ │ │ └── Utils.java │ │ │ └── view/ │ │ │ ├── ColorPickerView.java │ │ │ └── RichEditor.java │ │ └── res/ │ │ ├── anim/ │ │ │ ├── dialog_enter.xml │ │ │ └── dialog_exit.xml │ │ ├── drawable/ │ │ │ └── ic_launcher_background.xml │ │ ├── drawable-v24/ │ │ │ └── ic_launcher_foreground.xml │ │ ├── layout/ │ │ │ ├── activity_main.xml │ │ │ └── activity_show_diarys.xml │ │ ├── mipmap-anydpi-v26/ │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ └── values/ │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test/ │ └── java/ │ └── com/ │ └── example/ │ └── zhouqiong/ │ └── richeditotandroid/ │ └── ExampleUnitTest.java ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.iml .gradle /local.properties /.idea/workspace.xml /.idea/libraries .DS_Store /build /captures .externalNativeBuild ================================================ FILE: .idea/dictionaries/zhouqiong.xml ================================================ ================================================ FILE: .idea/gradle.xml ================================================ ================================================ FILE: .idea/misc.xml ================================================ Android CorrectnessLintAndroid LintAndroid PerformanceLintAndroid Android ================================================ FILE: .idea/modules.xml ================================================ ================================================ FILE: .idea/runConfigurations.xml ================================================ ================================================ FILE: README.md ================================================ # RichEditotAndroid WebView + JavaScript 方式的实现一个android富文本编辑器 >最近产品提出在移动端实现富文本编辑器功能,需要能编辑文字加粗、颜色、插入图片、下划线等一系列功能等,最后要生成一个html格式然后base64转码后上传服务端,最后在前端网页和移动端显示。 关于作者 的 《简书》 主页点击这里:[Android富文本编辑器](https://www.jianshu.com/p/9c2c1416d894) 这里的功能需要根据需求实现,通过insertImage传入一个URL或者本地图片路径都可以,这里用户可以自己调用本地相或者拍照获取图片,传图本地图片路径,也可以将本地图片路径上传到服务器(自己的服务器或者免费的七牛服务器),返回在服务端的URL地址,将地址传如即可(我这里传了一张写死的图片URL,如果你插入的图片不现实,请检查你是否添加网络请求权限) ![](https://upload-images.jianshu.io/upload_images/3278692-937a13db04c28c9f.gif?imageMogr2/auto-orient/strip) ![](https://upload-images.jianshu.io/upload_images/3278692-d309391fbdf9c3f6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![](https://upload-images.jianshu.io/upload_images/3278692-960adde8b912f4af.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![](https://upload-images.jianshu.io/upload_images/3278692-bf1fbb1028586dba.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ================================================ FILE: app/.gitignore ================================================ /build ================================================ FILE: app/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion 26 defaultConfig { applicationId "com.example.zhouqiong.richeditotandroid" minSdkVersion 15 targetSdkVersion 26 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support:appcompat-v7:26.1.0' implementation 'com.android.support.constraint:constraint-layout:1.0.2' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.1' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' } ================================================ FILE: app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile ================================================ FILE: app/src/androidTest/java/com/example/zhouqiong/richeditotandroid/ExampleInstrumentedTest.java ================================================ package com.example.zhouqiong.richeditotandroid; import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.*; /** * Instrumented test, which will execute on an Android device. * * @see Testing documentation */ @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { @Test public void useAppContext() throws Exception { // Context of the app under test. Context appContext = InstrumentationRegistry.getTargetContext(); assertEquals("com.example.zhouqiong.richeditotandroid", appContext.getPackageName()); } } ================================================ FILE: app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: app/src/main/assets/editor.html ================================================
================================================ FILE: app/src/main/assets/normalize.css ================================================ /*! normalize.css v3.0.2 | MIT License | git.io/normalize */ /** * 1. Set default font family to sans-serif. * 2. Prevent iOS text size adjust after orientation change, without disabling * user zoom. */ html { font-family: sans-serif; /* 1 */ -webkit-text-size-adjust: 100%; /* 2 */ } /** * Remove default margin. */ body { margin: 0; } /* HTML5 display definitions ========================================================================== */ /** * Correct `block` display not defined for any HTML5 element in IE 8/9. * Correct `block` display not defined for `details` or `summary` in IE 10/11 * and Firefox. * Correct `block` display not defined for `main` in IE 11. */ article, aside, details, figcaption, figure, footer, header, hgroup, main, menu, nav, section, summary { display: block; } /** * 1. Correct `inline-block` display not defined in IE 8/9. * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. */ audio, canvas, progress, video { display: inline-block; /* 1 */ vertical-align: baseline; /* 2 */ } /** * Prevent modern browsers from displaying `audio` without controls. * Remove excess height in iOS 5 devices. */ audio:not([controls]) { display: none; height: 0; } /** * Address `[hidden]` styling not present in IE 8/9/10. * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. */ [hidden], template { display: none; } /* Links ========================================================================== */ /** * Remove the gray background color from active links in IE 10. */ a { background-color: transparent; } /** * Improve readability when focused and also mouse hovered in all browsers. */ a:active, a:hover { outline: 0; } /* Text-level semantics ========================================================================== */ /** * Address styling not present in IE 8/9/10/11, Safari, and Chrome. */ abbr[title] { border-bottom: 1px dotted; } /** * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. */ b, strong { font-weight: bold; } /** * Address styling not present in Safari and Chrome. */ dfn { font-style: italic; } /** * Address variable `h1` font-size and margin within `section` and `article` * contexts in Firefox 4+, Safari, and Chrome. */ h1 { font-size: 2em; margin: 0.67em 0; } /** * Address styling not present in IE 8/9. */ mark { background: #ff0; color: #000; } /** * Address inconsistent and variable font size in all browsers. */ small { font-size: 80%; } /** * Prevent `sub` and `sup` affecting `line-height` in all browsers. */ sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } sup { top: -0.5em; } sub { bottom: -0.25em; } /* Embedded content ========================================================================== */ /** * Remove border when inside `a` element in IE 8/9/10. */ img { border: 0; } /** * Correct overflow not hidden in IE 9/10/11. */ svg:not(:root) { overflow: hidden; } /* Grouping content ========================================================================== */ /** * Address margin not present in IE 8/9 and Safari. */ figure { margin: 1em 40px; } /** * Address differences between Firefox and other browsers. */ hr { box-sizing: content-box; height: 0; } /** * Contain overflow in all browsers. */ pre { overflow: auto; } /** * Address odd `em`-unit font size rendering in all browsers. */ code, kbd, pre, samp { font-family: monospace, monospace; font-size: 1em; } /* Forms ========================================================================== */ /** * Known limitation: by default, Chrome and Safari on OS X allow very limited * styling of `select`, unless a `border` property is set. */ /** * 1. Correct color not being inherited. * Known issue: affects color of disabled elements. * 2. Correct font properties not being inherited. * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. */ button, input, optgroup, select, textarea { color: inherit; /* 1 */ font: inherit; /* 2 */ margin: 0; /* 3 */ } /** * Address `overflow` set to `hidden` in IE 8/9/10/11. */ button { overflow: visible; } /** * Address inconsistent `text-transform` inheritance for `button` and `select`. * All other form control elements do not inherit `text-transform` values. * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. * Correct `select` style inheritance in Firefox. */ button, select { text-transform: none; } /** * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` * and `video` controls. * 2. Correct inability to style clickable `input` types in iOS. * 3. Improve usability and consistency of cursor style between image-type * `input` and others. */ button, html input[type="button"], /* 1 */ input[type="reset"], input[type="submit"] { -webkit-appearance: button; /* 2 */ cursor: pointer; /* 3 */ } /** * Re-set default cursor for disabled elements. */ button[disabled], html input[disabled] { cursor: default; } /** * Address Firefox 4+ setting `line-height` on `input` using `!important` in * the UA stylesheet. */ input { line-height: normal; } /** * It's recommended that you don't attempt to style these elements. * Firefox's implementation doesn't respect box-sizing, padding, or width. * * 1. Address box sizing set to `content-box` in IE 8/9/10. * 2. Remove excess padding in IE 8/9/10. */ input[type="checkbox"], input[type="radio"] { box-sizing: border-box; /* 1 */ padding: 0; /* 2 */ } /** * Fix the cursor style for Chrome's increment/decrement buttons. For certain * `font-size` values of the `input`, it causes the cursor style of the * decrement button to change from `default` to `text`. */ input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-outer-spin-button { height: auto; } /** * 1. Address `appearance` set to `searchfield` in Safari and Chrome. * 2. Address `box-sizing` set to `border-box` in Safari and Chrome */ input[type="search"] { -webkit-appearance: textfield; /* 1 */ -webkit-box-sizing: content-box; /* 2 */ box-sizing: content-box; } /** * Remove inner padding and search cancel button in Safari and Chrome on OS X. * Safari (but not Chrome) clips the cancel button when the search input has * padding (and `textfield` appearance). */ input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } /** * Define consistent border, margin, and padding. */ fieldset { border: 1px solid #c0c0c0; margin: 0 2px; padding: 0.35em 0.625em 0.75em; } /** * 1. Correct `color` not being inherited in IE 8/9/10/11. * 2. Remove padding so people aren't caught out if they zero out fieldsets. */ legend { border: 0; /* 1 */ padding: 0; /* 2 */ } /** * Remove default vertical scrollbar in IE 8/9/10/11. */ textarea { overflow: auto; } /** * Don't inherit the `font-weight` (applied by a rule above). * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. */ optgroup { font-weight: bold; } /* Tables ========================================================================== */ /** * Remove most spacing between table cells. */ table { border-collapse: collapse; border-spacing: 0; } td, th { padding: 0; } ================================================ FILE: app/src/main/assets/rich_editor.js ================================================ /** * Copyright (C) 2017 Wasabeef * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 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() { document.execCommand('insertUnorderedList', false, null); } RE.setNumbers = function() { 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) { document.execCommand('formatBlock', false, ''); } 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, '
'); } RE.insertImage = function(url, alt) { var html = '' + alt + ''; 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,""+title+""); } 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 = '  '; 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); ================================================ FILE: app/src/main/assets/style.css ================================================ /** * Copyright (C) 2017 Wasabeef * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ @charset "UTF-8"; html { height: 100%; } body { overflow: scroll; display: table; table-layout: fixed; width: 100%; min-height:100%; } #editor { display: table-cell; outline: 0px solid transparent; background-repeat: no-repeat; background-position: center; background-size: cover; } #editor[placeholder]:empty:not(:focus):before { content: attr(placeholder); opacity: .5; }} ================================================ FILE: app/src/main/java/com/example/zhouqiong/richeditotandroid/ui/MainActivity.java ================================================ package com.example.zhouqiong.richeditotandroid.ui; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.content.Intent; import android.graphics.Color; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import com.example.zhouqiong.richeditotandroid.R; import com.example.zhouqiong.richeditotandroid.view.ColorPickerView; import com.example.zhouqiong.richeditotandroid.view.RichEditor; public class MainActivity extends AppCompatActivity implements View.OnClickListener { /********************View**********************/ //文本编辑器 private RichEditor mEditor; //加粗按钮 private ImageView mBold; //颜色编辑器 private TextView mTextColor; //显示显示View private LinearLayout llColorView; //预览按钮 private TextView mPreView; //图片按钮 private TextView mImage; //按序号排列(ol) private ImageView mListOL; //按序号排列(ul) private ImageView mListUL; //字体下划线 private ImageView mLean; //字体倾斜 private ImageView mItalic; //字体左对齐 private ImageView mAlignLeft; //字体右对齐 private ImageView mAlignRight; //字体居中对齐 private ImageView mAlignCenter; //字体缩进 private ImageView mIndent; //字体较少缩进 private ImageView mOutdent; //字体索引 private ImageView mBlockquote; //字体中划线 private ImageView mStrikethrough; //字体上标 private ImageView mSuperscript; //字体下标 private ImageView mSubscript; /********************boolean开关**********************/ //是否加粗 boolean isClickBold = false; //是否正在执行动画 boolean isAnimating = false; //是否按ol排序 boolean isListOl = false; //是否按ul排序 boolean isListUL = false; //是否下划线字体 boolean isTextLean = false; //是否下倾斜字体 boolean isItalic = false; //是否左对齐 boolean isAlignLeft = false; //是否右对齐 boolean isAlignRight = false; //是否中对齐 boolean isAlignCenter = false; //是否缩进 boolean isIndent = false; //是否较少缩进 boolean isOutdent = false; //是否索引 boolean isBlockquote = false; //字体中划线 boolean isStrikethrough = false; //字体上标 boolean isSuperscript = false; //字体下标 boolean isSubscript = false; /********************变量**********************/ //折叠视图的宽高 private int mFoldedViewMeasureHeight; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); initClickListener(); } /** * 初始化View */ private void initView() { initEditor(); initMenu(); initColorPicker(); } /** * 初始化文本编辑器 */ private void initEditor() { mEditor = findViewById(R.id.re_main_editor); //mEditor.setEditorHeight(400); //输入框显示字体的大小 mEditor.setEditorFontSize(18); //输入框显示字体的颜色 mEditor.setEditorFontColor(Color.BLACK); //输入框背景设置 mEditor.setEditorBackgroundColor(Color.WHITE); //mEditor.setBackgroundColor(Color.BLUE); //mEditor.setBackgroundResource(R.drawable.bg); //mEditor.setBackground("https://raw.githubusercontent.com/wasabeef/art/master/chip.jpg"); //输入框文本padding mEditor.setPadding(10, 10, 10, 10); //输入提示文本 mEditor.setPlaceholder("请输入编辑内容"); //是否允许输入 //mEditor.setInputEnabled(false); //文本输入框监听事件 mEditor.setOnTextChangeListener(new RichEditor.OnTextChangeListener() { @Override public void onTextChange(String text) { Log.d("mEditor", "html文本:" + text); } }); } /** * 初始化颜色选择器 */ private void initColorPicker() { ColorPickerView right = findViewById(R.id.cpv_main_color); right.setOnColorPickerChangeListener(new ColorPickerView.OnColorPickerChangeListener() { @Override public void onColorChanged(ColorPickerView picker, int color) { mTextColor.setBackgroundColor(color); mEditor.setTextColor(color); } @Override public void onStartTrackingTouch(ColorPickerView picker) { } @Override public void onStopTrackingTouch(ColorPickerView picker) { } }); } /** * 初始化菜单按钮 */ private void initMenu() { mBold = findViewById(R.id.button_bold); mTextColor = findViewById(R.id.button_text_color); llColorView = findViewById(R.id.ll_main_color); mPreView = findViewById(R.id.tv_main_preview); mImage = findViewById(R.id.button_image); mListOL = findViewById(R.id.button_list_ol); mListUL = findViewById(R.id.button_list_ul); mLean = findViewById(R.id.button_underline); mItalic = findViewById(R.id.button_italic); mAlignLeft = findViewById(R.id.button_align_left); mAlignRight = findViewById(R.id.button_align_right); mAlignCenter = findViewById(R.id.button_align_center); mIndent = findViewById(R.id.button_indent); mOutdent = findViewById(R.id.button_outdent); mBlockquote = findViewById(R.id.action_blockquote); mStrikethrough = findViewById(R.id.action_strikethrough); mSuperscript = findViewById(R.id.action_superscript); mSubscript = findViewById(R.id.action_subscript); getViewMeasureHeight(); } /** * 获取控件的高度 */ private void getViewMeasureHeight() { //获取像素密度 float mDensity = getResources().getDisplayMetrics().density; //获取布局的高度 int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); llColorView.measure(w, h); int height = llColorView.getMeasuredHeight(); mFoldedViewMeasureHeight = (int) (mDensity * height + 0.5); } private void initClickListener() { mBold.setOnClickListener(this); mTextColor.setOnClickListener(this); mPreView.setOnClickListener(this); mImage.setOnClickListener(this); mListOL.setOnClickListener(this); mListUL.setOnClickListener(this); mLean.setOnClickListener(this); mItalic.setOnClickListener(this); mAlignLeft.setOnClickListener(this); mAlignRight.setOnClickListener(this); mAlignCenter.setOnClickListener(this); mIndent.setOnClickListener(this); mOutdent.setOnClickListener(this); mBlockquote.setOnClickListener(this); mStrikethrough.setOnClickListener(this); mSuperscript.setOnClickListener(this); mSubscript.setOnClickListener(this); } @Override public void onClick(View v) { int id = v.getId(); if (id == R.id.button_bold) {//字体加粗 if (isClickBold) { mBold.setImageResource(R.mipmap.bold); } else { //加粗 mBold.setImageResource(R.mipmap.bold_); } isClickBold = !isClickBold; mEditor.setBold(); } else if (id == R.id.button_text_color) {//设置字体颜色 //如果动画正在执行,直接return,相当于点击无效了,不会出现当快速点击时, // 动画的执行和ImageButton的图标不一致的情况 if (isAnimating) return; //如果动画没在执行,走到这一步就将isAnimating制为true , 防止这次动画还没有执行完毕的 //情况下,又要执行一次动画,当动画执行完毕后会将isAnimating制为false,这样下次动画又能执行 isAnimating = true; if (llColorView.getVisibility() == View.GONE) { //打开动画 animateOpen(llColorView); } else { //关闭动画 animateClose(llColorView); } } else if (id == R.id.button_image) {//插入图片 //这里的功能需要根据需求实现,通过insertImage传入一个URL或者本地图片路径都可以,这里用户可以自己调用本地相 //或者拍照获取图片,传图本地图片路径,也可以将本地图片路径上传到服务器(自己的服务器或者免费的七牛服务器), //返回在服务端的URL地址,将地址传如即可(我这里传了一张写死的图片URL,如果你插入的图片不现实,请检查你是否添加 // 网络请求权限) mEditor.insertImage("http://www.1honeywan.com/dachshund/image/7.21/7.21_3_thumb.JPG", "dachshund"); } else if (id == R.id.button_list_ol) { if (isListOl) { mListOL.setImageResource(R.mipmap.list_ol); } else { mListOL.setImageResource(R.mipmap.list_ol_); } isListOl = !isListOl; mEditor.setNumbers(); } else if (id == R.id.button_list_ul) { if (isListUL) { mListUL.setImageResource(R.mipmap.list_ul); } else { mListUL.setImageResource(R.mipmap.list_ul_); } isListUL = !isListUL; mEditor.setBullets(); } else if (id == R.id.button_underline) { if (isTextLean) { mLean.setImageResource(R.mipmap.underline); } else { mLean.setImageResource(R.mipmap.underline_); } isTextLean = !isTextLean; mEditor.setUnderline(); } else if (id == R.id.button_italic) { if (isItalic) { mItalic.setImageResource(R.mipmap.lean); } else { mItalic.setImageResource(R.mipmap.lean_); } isItalic = !isItalic; mEditor.setItalic(); } else if (id == R.id.button_align_left) { if (isAlignLeft) { mAlignLeft.setImageResource(R.mipmap.align_left); } else { mAlignLeft.setImageResource(R.mipmap.align_left_); } isAlignLeft = !isAlignLeft; mEditor.setAlignLeft(); } else if (id == R.id.button_align_right) { if (isAlignRight) { mAlignRight.setImageResource(R.mipmap.align_right); } else { mAlignRight.setImageResource(R.mipmap.align_right_); } isAlignRight = !isAlignRight; mEditor.setAlignRight(); } else if (id == R.id.button_align_center) { if (isAlignCenter) { mAlignCenter.setImageResource(R.mipmap.align_center); } else { mAlignCenter.setImageResource(R.mipmap.align_center_); } isAlignCenter = !isAlignCenter; mEditor.setAlignCenter(); } else if (id == R.id.button_indent) { if (isIndent) { mIndent.setImageResource(R.mipmap.indent); } else { mIndent.setImageResource(R.mipmap.indent_); } isIndent = !isIndent; mEditor.setIndent(); } else if (id == R.id.button_outdent) { if (isOutdent) { mOutdent.setImageResource(R.mipmap.outdent); } else { mOutdent.setImageResource(R.mipmap.outdent_); } isOutdent = !isOutdent; mEditor.setOutdent(); } else if (id == R.id.action_blockquote) { if (isBlockquote) { mBlockquote.setImageResource(R.mipmap.blockquote); } else { mBlockquote.setImageResource(R.mipmap.blockquote_); } isBlockquote = !isBlockquote; mEditor.setBlockquote(); } else if (id == R.id.action_strikethrough) { if (isStrikethrough) { mStrikethrough.setImageResource(R.mipmap.strikethrough); } else { mStrikethrough.setImageResource(R.mipmap.strikethrough_); } isStrikethrough = !isStrikethrough; mEditor.setStrikeThrough(); } else if (id == R.id.action_superscript) { if (isSuperscript) { mSuperscript.setImageResource(R.mipmap.superscript); } else { mSuperscript.setImageResource(R.mipmap.superscript_); } isSuperscript = !isSuperscript; mEditor.setSuperscript(); } else if (id == R.id.action_subscript) { if (isSubscript) { mSubscript.setImageResource(R.mipmap.subscript); } else { mSubscript.setImageResource(R.mipmap.subscript_); } isSubscript = !isSubscript; mEditor.setSubscript(); } //H1--H6省略,需要的自己添加 else if (id == R.id.tv_main_preview) {//预览 Intent intent = new Intent(MainActivity.this, WebDataActivity.class); intent.putExtra("diarys", mEditor.getHtml()); startActivity(intent); } } /** * 开启动画 * * @param view 开启动画的view */ private void animateOpen(LinearLayout view) { view.setVisibility(View.VISIBLE); ValueAnimator animator = createDropAnimator(view, 0, mFoldedViewMeasureHeight); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { isAnimating = false; } }); animator.start(); } /** * 关闭动画 * * @param view 关闭动画的view */ private void animateClose(final LinearLayout view) { int origHeight = view.getHeight(); ValueAnimator animator = createDropAnimator(view, origHeight, 0); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { view.setVisibility(View.GONE); isAnimating = false; } }); animator.start(); } /** * 创建动画 * * @param view 开启和关闭动画的view * @param start view的高度 * @param end view的高度 * @return ValueAnimator对象 */ private ValueAnimator createDropAnimator(final View view, int start, int end) { ValueAnimator animator = ValueAnimator.ofInt(start, end); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int value = (int) animation.getAnimatedValue(); ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); layoutParams.height = value; view.setLayoutParams(layoutParams); } }); return animator; } } ================================================ FILE: app/src/main/java/com/example/zhouqiong/richeditotandroid/ui/WebDataActivity.java ================================================ package com.example.zhouqiong.richeditotandroid.ui; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.webkit.JavascriptInterface; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.Toast; import com.example.zhouqiong.richeditotandroid.R; import com.example.zhouqiong.richeditotandroid.ui.MainActivity; /** * Created by ZQiong on 2018/3/20. */ public class WebDataActivity extends AppCompatActivity { //自己制造的一些假数据。外加筛选图片样式 /**** 3333333333 ***************************************************/ // private String dataStr=""+"\"\" \r

\r\t品类定位的思考\r

\r

\r\t品类定\n" + // "

\r\t点击我跳回APP"+""; /*** 11111111111 **************************************************************************/ //这个数据的外层不加两层标签,不过在下面一个地方加上一个div和图片样式 // private String dataStr = "\"\" \r

\r\t品类定位的思考\r

\r

\r\t品类定"; /**** 11111111111 *************************************************************************/ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_show_diarys); final String dataStr = getIntent().getStringExtra("diarys"); initWebView(dataStr); } public void initWebView(String data) { WebView mWebView = findViewById(R.id.showdiarys); WebSettings settings = mWebView.getSettings(); //settings.setUseWideViewPort(true);//调整到适合webview的大小,不过尽量不要用,有些手机有问题 settings.setLoadWithOverviewMode(true);//设置WebView是否使用预览模式加载界面。 mWebView.setVerticalScrollBarEnabled(false);//不能垂直滑动 mWebView.setHorizontalScrollBarEnabled(false);//不能水平滑动 settings.setTextSize(WebSettings.TextSize.NORMAL);//通过设置WebSettings,改变HTML中文字的大小 settings.setJavaScriptCanOpenWindowsAutomatically(true);//支持通过JS打开新窗口 //设置WebView属性,能够执行Javascript脚本 mWebView.getSettings().setJavaScriptEnabled(true);//设置js可用 mWebView.setWebViewClient(new WebViewClient()); mWebView.addJavascriptInterface(new AndroidJavaScript(getApplication()), "android");//设置js接口 settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);//支持内容重新布局 /****** 22222222 ***********************************************************************/ data = "" + data;//给图片设置一个样式,宽满屏 /****** 2222222222 ***********************************************************************/ mWebView.loadDataWithBaseURL(null, data, "text/html", "utf-8", null); } /** * AndroidJavaScript * 本地与h5页面交互的js类,这里写成内部类了 * returnAndroid方法上@JavascriptInterface一定不能漏了 */ private class AndroidJavaScript { Context mContxt; public AndroidJavaScript(Context mContxt) { this.mContxt = mContxt; } @JavascriptInterface public void returnAndroid(String name) {//从网页跳回到APP,这个方法已经在上面的HTML中写上了 if (name.isEmpty() || name.equals("")) { return; } Toast.makeText(getApplication(), name, Toast.LENGTH_SHORT).show(); //这里写你的操作/////////////////////// //MainActivity就是一个空页面,不影响 Intent intent = new Intent(WebDataActivity.this, MainActivity.class); intent.putExtra("name", name); startActivity(intent); } } } ================================================ FILE: app/src/main/java/com/example/zhouqiong/richeditotandroid/utils/Utils.java ================================================ package com.example.zhouqiong.richeditotandroid.utils; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.util.Base64; import java.io.ByteArrayOutputStream; /** * Created by ZQiong on 2018/3/22. */ public final class Utils { private Utils() throws InstantiationException { throw new InstantiationException("This class is not for instantiation"); } public static String toBase64(Bitmap bitmap) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos); byte[] bytes = baos.toByteArray(); return Base64.encodeToString(bytes, Base64.NO_WRAP); } public static Bitmap toBitmap(Drawable drawable) { if (drawable instanceof BitmapDrawable) { return ((BitmapDrawable) drawable).getBitmap(); } int width = drawable.getIntrinsicWidth(); width = width > 0 ? width : 1; int height = drawable.getIntrinsicHeight(); height = height > 0 ? height : 1; Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); drawable.draw(canvas); return bitmap; } public static Bitmap decodeResource(Context context, int resId) { return BitmapFactory.decodeResource(context.getResources(), resId); } public static long getCurrentTime() { return System.currentTimeMillis(); } } ================================================ FILE: app/src/main/java/com/example/zhouqiong/richeditotandroid/view/ColorPickerView.java ================================================ package com.example.zhouqiong.richeditotandroid.view; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Shader; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import com.example.zhouqiong.richeditotandroid.R; /** * Created by ZQiong on 2018/3/22. *

* 指示点的半径是颜色条宽度的 4/3 ,两端会有指示点半径长的距离是空白的,这是为了给指示点滑动到端点时留出足够的显示空间, * 这也导致当控件宽高的差值太小时,指示点会完全覆盖住颜色条,使颜色条不可见,因此应尽量根据控件的不同模式使长 * 边与段边的比例不小于 3 : 1 *
*

* 两种模式: *

* HORIZONTAL 水平 *

* VERTICAL 竖直 */ public class ColorPickerView extends View { /** * 指示点颜色 */ private int mIndicatorColor; /** * 是否启用指示点 */ private boolean mIndicatorEnable; /** * View 和 bitmapForColor 的画笔 */ private final Paint paint; /** * 指示点专用画笔,这样可以避免 mIndicatorColor 有 alpha 时,alpha 作用于 View */ private final Paint paintForIndicator; private LinearGradient linearGradient; /** * 除去上下 padding 的端点坐标 */ private int mTop, mLeft, mRight, mBottom; /** * 颜色条圆角矩形边界 */ private final Rect rect = new Rect(); /** * bitmapForIndicator 在 View 上的绘制位置 */ private final Rect rectForIndicator = new Rect(); /** * 指示点半径 */ private int mRadius; /** * 控件方向 */ private Orientation orientation; // 默认状态下长边与短边的比例为 6 :1 private static final int defaultSizeShort = 70; // * 6 private static final int defaultSizeLong = 420; // 不直接绘制在 View 提供的画布上的原因是:选取颜色时需要提取 Bitmap 上的颜色,View 的 Bitmap 无法获取, // 而且有指示点时指示点会覆盖主颜色条(重绘颜色条的颜色) private Bitmap bitmapForColor; private Bitmap bitmapForIndicator; /** * 是否需要绘制颜色条(指示点),颜色条在选取颜色时不需要再次生成(bitmapForColor),直接绘制就行 */ private boolean needReDrawColorTable = true; private boolean needReDrawIndicator = true; /** * 手指在颜色条上的坐标 */ private int curX, curY; private int[] colors = null; private int currentColor; /** * 控件方向 */ public enum Orientation { /** * 水平 */ HORIZONTAL, // 0 /** * 竖直 */ VERTICAL // 1 } { bitmapForColor = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); bitmapForIndicator = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); //Android4.0(API14)之后硬件加速功能就被默认开启了,setShadowLayer 在开启硬件加速的情况下无效,需要关闭硬件加速 this.setLayerType(View.LAYER_TYPE_SOFTWARE, null); paint = new Paint(); paint.setAntiAlias(true); paintForIndicator = new Paint(); paintForIndicator.setAntiAlias(true); curX = curY = Integer.MAX_VALUE; } public ColorPickerView(Context context) { super(context); } public ColorPickerView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public ColorPickerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); final TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ColorPickerView, defStyleAttr, 0); mIndicatorColor = array.getColor(R.styleable.ColorPickerView_indicatorColor, Color.WHITE); int or = array.getInteger(R.styleable.ColorPickerView_orientation, 0); orientation = or == 0 ? Orientation.HORIZONTAL : Orientation.VERTICAL; mIndicatorEnable = array.getBoolean(R.styleable.ColorPickerView_indicatorEnable, true); array.recycle(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int width; int height; if (widthMode == MeasureSpec.EXACTLY) { width = widthSize; } else {//xml中宽度设为warp_content width = getSuggestedMinimumWidth() + getPaddingLeft() + getPaddingRight(); } if (heightMode == MeasureSpec.EXACTLY) { height = heightSize; } else { height = getSuggestedMinimumHeight() + getPaddingTop() + getPaddingBottom(); } width = Math.max(width, orientation == Orientation.HORIZONTAL ? defaultSizeLong : defaultSizeShort); height = Math.max(height, orientation == Orientation.HORIZONTAL ? defaultSizeShort : defaultSizeLong); setMeasuredDimension(width, height); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); mTop = getPaddingTop(); mLeft = getPaddingLeft(); mBottom = getMeasuredHeight() - getPaddingBottom(); mRight = getMeasuredWidth() - getPaddingRight(); if (curX == curY || curY == Integer.MAX_VALUE) { curX = getWidth() / 2; curY = getHeight() / 2; } calculBounds(); if (colors == null) { setColors(createDefaultColorTable()); } else { setColors(colors); } createBitmap(); if (mIndicatorEnable) { needReDrawIndicator = true; } } private void createBitmap() { int hc = rect.height(); int wc = rect.width(); int hi = mRadius * 2; int wi = hi; if (bitmapForColor != null) { if (!bitmapForColor.isRecycled()) { bitmapForColor.recycle(); bitmapForColor = null; } } if (bitmapForIndicator != null) { if (!bitmapForIndicator.isRecycled()) { bitmapForIndicator.recycle(); bitmapForIndicator = null; } } bitmapForColor = Bitmap.createBitmap(wc, hc, Bitmap.Config.ARGB_8888); bitmapForIndicator = Bitmap.createBitmap(wi, hi, Bitmap.Config.ARGB_8888); } /** * 计算颜色条边界 */ private void calculBounds() { /* * 将控件可用高度(除去上下 padding )均分为 6 份,以此计算指示点半径,颜色条宽高 * 控件方向为 HORIZONTAL 时,从上往下依次占的份额为: * 1/9 留白 * 2/9 颜色条上面部分圆 * 3/9 颜色条宽 * 2/9 颜色条上面部分圆 * 1/9 留白 */ final int average = 9; /* * 每一份的高度 */ int each; int h = mBottom - mTop; int w = mRight - mLeft; int size = Math.min(w, h); if (orientation == Orientation.HORIZONTAL) { if (w <= h) { // HORIZONTAL 模式,然而宽却小于高,以 6 :1 的方式重新计算高 size = w / 6; } } else { if (w >= h) { size = h / 6; } } each = size / average; mRadius = each * 7 / 2; int t, l, b, r; final int s = each * 3 / 2; if (orientation == Orientation.HORIZONTAL) { l = mLeft + mRadius; r = mRight - mRadius; t = (getHeight() / 2) - s; b = (getHeight() / 2) + s; } else { t = mTop + mRadius; b = mBottom - mRadius; l = getWidth() / 2 - s; r = getWidth() / 2 + s; } rect.set(l, t, r, b); } /** * 设置颜色条的渐变颜色,不支持具有 alpha 的颜色,{@link Color#TRANSPARENT}会被当成 {@link Color#BLACK}处理 * 如果想设置 alpha ,可以在{@link OnColorPickerChangeListener#onColorChanged(ColorPickerView, int)} 回调 * 中调用{@link android.support.v4.graphics.ColorUtils#setAlphaComponent(int, int)}方法添加 alpha 值。 * * @param colors 颜色值 */ public void setColors(int... colors) { linearGradient = null; this.colors = colors; if (orientation == Orientation.HORIZONTAL) { linearGradient = new LinearGradient( rect.left, rect.top, rect.right, rect.top, colors, null, Shader.TileMode.CLAMP ); } else { linearGradient = new LinearGradient( rect.left, rect.top, rect.left, rect.bottom, colors, null, Shader.TileMode.CLAMP ); } needReDrawColorTable = true; invalidate(); } public int[] createDefaultColorTable() { // int[] cs = { // Color.rgb(0, 0, 0),//白色 // Color.rgb(255, 0, 0),//红色 // Color.rgb(0, 255, 0),//绿色 // Color.rgb(0, 255, 255),//青色 // Color.rgb(0, 0, 255),//蓝色 // Color.rgb(255, 0, 255),//紫色 // Color.rgb(255, 255, 255)//黑色 // }; int[] cs = { Color.rgb(0, 0, 0), Color.rgb(255, 0, 0),//红色 Color.rgb(255, 255, 0), Color.rgb(0, 255, 0),//绿色 Color.rgb(0, 255, 255),//青色 Color.rgb(0, 0, 255),//蓝色 Color.rgb(255, 0, 255), Color.rgb(255, 0, 0)//红色 }; return cs; } @Override protected void onDraw(Canvas canvas) { if (needReDrawColorTable) { createColorTableBitmap(); } // 绘制颜色条 canvas.drawBitmap(bitmapForColor, null, rect, paint); if (mIndicatorEnable) { if (needReDrawIndicator) { createIndicatorBitmap(); } // 绘制指示点 rectForIndicator.set(curX - mRadius, curY - mRadius, curX + mRadius, curY + mRadius); canvas.drawBitmap(bitmapForIndicator, null, rectForIndicator, paint); } } private void createIndicatorBitmap() { paintForIndicator.setColor(mIndicatorColor); int radius = 3; paintForIndicator.setShadowLayer(radius, 0, 0, Color.GRAY); Canvas c = new Canvas(bitmapForIndicator); c.drawCircle(mRadius, mRadius, mRadius - radius, paintForIndicator); needReDrawIndicator = false; } private void createColorTableBitmap() { Canvas c = new Canvas(bitmapForColor); RectF rf = new RectF(0, 0, bitmapForColor.getWidth(), bitmapForColor.getHeight()); // 圆角大小 int r; if (orientation == Orientation.HORIZONTAL) { r = bitmapForColor.getHeight() / 2; } else { r = bitmapForColor.getWidth() / 2; } // 先绘制黑色背景,否则有 alpha 时绘制不正常 paint.setColor(Color.BLACK); c.drawRoundRect(rf, r, r, paint); paint.setShader(linearGradient); c.drawRoundRect(rf, r, r, paint); paint.setShader(null); needReDrawColorTable = false; } @Override public boolean onTouchEvent(MotionEvent event) { int ex = (int) event.getX(); int ey = (int) event.getY(); if (!inBoundOfColorTable(ex, ey)) { return true; } if (orientation == Orientation.HORIZONTAL) { curX = ex; curY = getHeight() / 2; } else { curX = getWidth() / 2; curY = ey; } if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { if (colorPickerChangeListener != null) { colorPickerChangeListener.onStartTrackingTouch(this); calcuColor(); colorPickerChangeListener.onColorChanged(this, currentColor); } } else if (event.getActionMasked() == MotionEvent.ACTION_UP) { //手抬起 if (colorPickerChangeListener != null) { colorPickerChangeListener.onStopTrackingTouch(this); calcuColor(); colorPickerChangeListener.onColorChanged(this, currentColor); } } else { //按着+拖拽 if (colorPickerChangeListener != null) { calcuColor(); colorPickerChangeListener.onColorChanged(this, currentColor); } } invalidate(); return true; } /** * 获得当前指示点所指颜色 * * @return 颜色值 */ public int getColor() { return calcuColor(); } private boolean inBoundOfColorTable(int ex, int ey) { if (orientation == Orientation.HORIZONTAL) { if (ex <= mLeft + mRadius || ex >= mRight - mRadius) { return false; } } else { if (ey <= mTop + mRadius || ey >= mBottom - mRadius) { return false; } } return true; } private int calcuColor() { int x, y; if (orientation == Orientation.HORIZONTAL) { // 水平 y = (rect.bottom - rect.top) / 2; if (curX < rect.left) { x = 1; } else if (curX > rect.right) { x = bitmapForColor.getWidth() - 1; } else { x = curX - rect.left; } } else { // 竖直 x = (rect.right - rect.left) / 2; if (curY < rect.top) { y = 1; } else if (curY > rect.bottom) { y = bitmapForColor.getHeight() - 1; } else { y = curY - rect.top; } } int pixel = bitmapForColor.getPixel(x, y); currentColor = pixelToColor(pixel); return currentColor; } private int pixelToColor(int pixel) { int alpha = Color.alpha(pixel); int red = Color.red(pixel); int green = Color.green(pixel); int blue = Color.blue(pixel); return Color.argb(alpha, red, green, blue); } private OnColorPickerChangeListener colorPickerChangeListener; public void setOnColorPickerChangeListener(OnColorPickerChangeListener l) { this.colorPickerChangeListener = l; } public interface OnColorPickerChangeListener { /** * 选取的颜色值改变时回调 * * @param picker ColorPickerView * @param color 颜色 */ void onColorChanged(ColorPickerView picker, int color); /** * 开始颜色选取 * * @param picker ColorPickerView */ void onStartTrackingTouch(ColorPickerView picker); /** * 停止颜色选取 * * @param picker ColorPickerView */ void onStopTrackingTouch(ColorPickerView picker); } @Override protected Parcelable onSaveInstanceState() { Parcelable parcelable = super.onSaveInstanceState(); SavedState ss = new SavedState(parcelable); ss.selX = curX; ss.selY = curY; ss.color = bitmapForColor; if (mIndicatorEnable) { ss.indicator = bitmapForIndicator; } return ss; } @Override protected void onRestoreInstanceState(Parcelable state) { if (!(state instanceof SavedState)) { super.onRestoreInstanceState(state); return; } SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); curX = ss.selX; curY = ss.selY; colors = ss.colors; bitmapForColor = ss.color; if (mIndicatorEnable) { bitmapForIndicator = ss.indicator; needReDrawIndicator = true; } needReDrawColorTable = true; } private class SavedState extends BaseSavedState { int selX, selY; int[] colors; Bitmap color; Bitmap indicator = null; SavedState(Parcelable source) { super(source); } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeInt(selX); out.writeInt(selY); out.writeParcelable(color, flags); out.writeIntArray(colors); if (indicator != null) { out.writeParcelable(indicator, flags); } } } public void setPosition(int x, int y) { if (inBoundOfColorTable(x, y)) { curX = x; curY = y; if (mIndicatorEnable) { needReDrawIndicator = true; } invalidate(); } } /** * 显示默认的颜色选择器 */ public void showDefaultColorTable() { setColors(createDefaultColorTable()); } public int getIndicatorColor() { return mIndicatorColor; } public void setIndicatorColor(int color) { this.mIndicatorColor = color; needReDrawIndicator = true; invalidate(); } public void setOrientation(Orientation orientation) { this.orientation = orientation; needReDrawIndicator = true; needReDrawColorTable = true; requestLayout(); } } ================================================ FILE: app/src/main/java/com/example/zhouqiong/richeditotandroid/view/RichEditor.java ================================================ package com.example.zhouqiong.richeditotandroid.view; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.os.Build; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; import android.webkit.WebChromeClient; import android.webkit.WebView; import android.webkit.WebViewClient; import com.example.zhouqiong.richeditotandroid.utils.Utils; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; import java.util.Locale; /** * 富文本编辑实现类 * Created by ZQiong on 2018/3/22. */ public class RichEditor extends WebView { public enum Type { BOLD, ITALIC, SUBSCRIPT, SUPERSCRIPT, STRIKETHROUGH, UNDERLINE, H1, H2, H3, H4, H5, H6, ORDEREDLIST, UNORDEREDLIST, JUSTIFYCENTER, JUSTIFYFULL, JUSTUFYLEFT, JUSTIFYRIGHT } public interface OnTextChangeListener { void onTextChange(String text); } public interface OnDecorationStateListener { void onStateChangeListener(String text, List types); } public interface AfterInitialLoadListener { void onAfterInitialLoad(boolean isReady); } private static final String SETUP_HTML = "file:///android_asset/editor.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; public RichEditor(Context context) { this(context, null); } public RichEditor(Context context, AttributeSet attrs) { this(context, attrs, android.R.attr.webViewStyle); } @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); } 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); } } private void stateCheck(String text) { String state = text.replaceFirst(STATE_SCHEME, "").toUpperCase(Locale.ENGLISH); List 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); } } 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(); } 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; } 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 = Utils.decodeResource(getContext(), resid); String base64 = Utils.toBase64(bitmap); bitmap.recycle(); exec("javascript:RE.setBackgroundImage('url(data:image/png;base64," + base64 + ")');"); } @Override public void setBackground(Drawable background) { Bitmap bitmap = Utils.toBitmap(background); String base64 = Utils.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 setInputEnabled(Boolean inputEnabled) { exec("javascript:RE.setInputEnabled(" + inputEnabled + ")"); } 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 undo() { exec("javascript:RE.undo();"); } public void redo() { exec("javascript:RE.redo();"); } public void setBold() { exec("javascript:RE.setBold();"); } public void setItalic() { 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) { exec("javascript:RE.setHeading('" + heading + "');"); } 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() { exec("javascript:RE.setBlockquote();"); } public void setBullets() { exec("javascript:RE.setBullets();"); } public void setNumbers() { exec("javascript:RE.setNumbers();"); } public void insertImage(String url, String alt) { exec("javascript:RE.prepareInsert();"); exec("javascript:RE.insertImage('" + url + "', '" + alt + "');"); } 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('" + Utils.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); } } } ================================================ FILE: app/src/main/res/anim/dialog_enter.xml ================================================ ================================================ FILE: app/src/main/res/anim/dialog_exit.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_launcher_background.xml ================================================ ================================================ FILE: app/src/main/res/drawable-v24/ic_launcher_foreground.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_main.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_show_diarys.xml ================================================ ================================================ FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml ================================================ ================================================ FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml ================================================ ================================================ FILE: app/src/main/res/values/attrs.xml ================================================ ================================================ FILE: app/src/main/res/values/colors.xml ================================================ #3F51B5 #303F9F #FF4081 #333333 ================================================ FILE: app/src/main/res/values/strings.xml ================================================ RichEditotAndroid ================================================ FILE: app/src/main/res/values/styles.xml ================================================ ================================================ FILE: app/src/test/java/com/example/zhouqiong/richeditotandroid/ExampleUnitTest.java ================================================ package com.example.zhouqiong.richeditotandroid; import org.junit.Test; import static org.junit.Assert.*; /** * Example local unit test, which will execute on the development machine (host). * * @see Testing documentation */ public class ExampleUnitTest { @Test public void addition_isCorrect() throws Exception { assertEquals(4, 2 + 2); } } ================================================ FILE: build.gradle ================================================ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.0.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { google() jcenter() } } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ #Wed Mar 21 11:05:34 CST 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip ================================================ FILE: gradle.properties ================================================ # Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx1536m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true ================================================ FILE: gradlew ================================================ #!/usr/bin/env bash ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn ( ) { echo "$*" } die ( ) { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; esac # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules function splitJvmOpts() { JVM_OPTS=("$@") } eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" ================================================ FILE: gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windowz variants if not "%OS%" == "Windows_NT" goto win9xME_args if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* goto execute :4NT_args @rem Get arguments from the 4NT Shell from JP Software set CMD_LINE_ARGS=%$ :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: settings.gradle ================================================ include ':app'