├── README.md ├── RichEditor.apk ├── RichEditor ├── .gitignore ├── .idea │ ├── .name │ ├── compiler.xml │ ├── copyright │ │ └── profiles_settings.xml │ ├── encodings.xml │ ├── gradle.xml │ ├── misc.xml │ ├── modules.xml │ ├── runConfigurations.xml │ └── vcs.xml ├── RichEditor.iml ├── app │ ├── .gitignore │ ├── app.iml │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── stone │ │ │ └── richeditor │ │ │ └── ApplicationTest.java │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── stone │ │ │ │ └── richeditor │ │ │ │ ├── DataImageView.java │ │ │ │ ├── DeletableEditText.java │ │ │ │ ├── MainActivity.java │ │ │ │ └── RichTextEditor.java │ │ └── res │ │ │ ├── drawable │ │ │ ├── close.png │ │ │ ├── cursor_shape.xml │ │ │ ├── ic_launcher.png │ │ │ ├── image1.png │ │ │ ├── image2.png │ │ │ ├── image3.png │ │ │ ├── sample.jpg │ │ │ └── share_weibo_camera_icon_p.png │ │ │ ├── layout │ │ │ ├── activity_main.xml │ │ │ ├── content_main.xml │ │ │ ├── edit_imageview.xml │ │ │ └── edit_item1.xml │ │ │ ├── menu │ │ │ └── menu_main.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-v21 │ │ │ └── dimens.xml │ │ │ ├── values-w820dp │ │ │ └── dimens.xml │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ └── strings.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── stone │ │ └── richeditor │ │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── capture01.gif └── capture02.gif /README.md: -------------------------------------------------------------------------------- 1 | # android-animate-RichEditor 2 | android rich editor which enables users to insert/delete bitmaps and text into edit-view with animations. 3 | #### target goal 4 | Recently, my product manager requires our team to implement this kind of interactive experience: a rich editor which enables users to insert/delete images and text at anywhere they want. However, almost everyone denies this demand at first, because our time remain is at tension and this kind of demand may cause unpredicted out-of-control.
5 | However, overcoming challenging difficulties is thrilling, isn't it? 6 | #### compare with other rich editors 7 | There exists other open-source rich editor projects in github, and i did run some of them. 8 | * One project uses WebView + HTML, the inserted images looks so strange there. 9 | * One project uses EditText + ImageSpan, maybe it's highly efficiency, but it's not that stable and robust. 10 | * and so on.. 11 | 12 | However, most of them are uncomfortable and inconvenient.
13 | I implement this rich editor by using SrollView + LinearLayout. As you know, there may exist many views in this Editor, but you can never doubt that SrollView + LinearLayout could be quite smooth if you deal child-views reasonably.
14 | Besides, LinearLayout could add some special animations when adding/deleting child-views. 15 | #### captured images 16 | ![PREVIEW](capture01.gif) 17 | ![PREVIEW](capture02.gif) 18 | 19 | #### demo apk download 20 | [apk download](RichEditor.apk) (right in this github project) 21 | 22 | #### extras 23 | later on, i will add some instructions for use the code. 24 | 25 | ### Version: 1.0 26 | 27 | * Pilot version 28 | 29 | ## License 30 | 31 | Copyright 2015, xmuSistone 32 | 33 | Licensed under the Apache License, Version 2.0 (the "License"); 34 | you may not use this file except in compliance with the License. 35 | You may obtain a copy of the License at 36 | 37 | http://www.apache.org/licenses/LICENSE-2.0 38 | 39 | Unless required by applicable law or agreed to in writing, software 40 | distributed under the License is distributed on an "AS IS" BASIS, 41 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 42 | See the License for the specific language governing permissions and 43 | limitations under the License. 44 | 45 | -------------------------------------------------------------------------------- /RichEditor.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmuSistone/AnimRichEditor/c67d0bc1515e67c70e4a2258da2cd69ced068443/RichEditor.apk -------------------------------------------------------------------------------- /RichEditor/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | /.idea/libraries 5 | .DS_Store 6 | /build 7 | /captures 8 | -------------------------------------------------------------------------------- /RichEditor/.idea/.name: -------------------------------------------------------------------------------- 1 | RichEditor -------------------------------------------------------------------------------- /RichEditor/.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /RichEditor/.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /RichEditor/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /RichEditor/.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /RichEditor/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 1.7 51 | 52 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /RichEditor/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /RichEditor/.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /RichEditor/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /RichEditor/RichEditor.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /RichEditor/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /RichEditor/app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /RichEditor/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.2" 6 | 7 | defaultConfig { 8 | applicationId "com.stone.richeditor" 9 | minSdkVersion 21 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:23.2.1' 26 | compile 'com.android.support:design:23.2.1' 27 | } 28 | -------------------------------------------------------------------------------- /RichEditor/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\adt-bundle-windows-x86_64-20140702\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /RichEditor/app/src/androidTest/java/com/stone/richeditor/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.stone.richeditor; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /RichEditor/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /RichEditor/app/src/main/java/com/stone/richeditor/DataImageView.java: -------------------------------------------------------------------------------- 1 | package com.stone.richeditor; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.util.AttributeSet; 6 | import android.widget.ImageView; 7 | 8 | /** 9 | * 这只是一个简单的ImageView,可以存放Bitmap和Path等信息 10 | * 11 | * @author xmuSistone 12 | * 13 | */ 14 | public class DataImageView extends ImageView { 15 | 16 | private String absolutePath; 17 | 18 | private Bitmap bitmap; 19 | 20 | public DataImageView(Context context) { 21 | this(context, null); 22 | } 23 | 24 | public DataImageView(Context context, AttributeSet attrs) { 25 | this(context, attrs, 0); 26 | } 27 | 28 | public DataImageView(Context context, AttributeSet attrs, int defStyle) { 29 | super(context, attrs, defStyle); 30 | } 31 | 32 | public String getAbsolutePath() { 33 | return absolutePath; 34 | } 35 | 36 | public void setAbsolutePath(String absolutePath) { 37 | this.absolutePath = absolutePath; 38 | } 39 | 40 | public Bitmap getBitmap() { 41 | return bitmap; 42 | } 43 | 44 | public void setBitmap(Bitmap bitmap) { 45 | this.bitmap = bitmap; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /RichEditor/app/src/main/java/com/stone/richeditor/DeletableEditText.java: -------------------------------------------------------------------------------- 1 | package com.stone.richeditor; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.KeyEvent; 6 | import android.view.inputmethod.EditorInfo; 7 | import android.view.inputmethod.InputConnection; 8 | import android.view.inputmethod.InputConnectionWrapper; 9 | import android.widget.EditText; 10 | 11 | /** 12 | * 这个是从stackOverFlow上面找到的解决方案,主要用途是处理软键盘回删按钮backSpace时回调OnKeyListener 13 | * 14 | * @author xmuSistone 15 | * 16 | */ 17 | public class DeletableEditText extends EditText { 18 | 19 | public DeletableEditText(Context context, AttributeSet attrs, int defStyle) { 20 | super(context, attrs, defStyle); 21 | } 22 | 23 | public DeletableEditText(Context context, AttributeSet attrs) { 24 | super(context, attrs); 25 | } 26 | 27 | public DeletableEditText(Context context) { 28 | super(context); 29 | } 30 | 31 | @Override 32 | public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 33 | return new DeleteInputConnection(super.onCreateInputConnection(outAttrs), 34 | true); 35 | } 36 | 37 | private class DeleteInputConnection extends InputConnectionWrapper { 38 | 39 | public DeleteInputConnection(InputConnection target, boolean mutable) { 40 | super(target, mutable); 41 | } 42 | 43 | @Override 44 | public boolean sendKeyEvent(KeyEvent event) { 45 | return super.sendKeyEvent(event); 46 | } 47 | 48 | @Override 49 | public boolean deleteSurroundingText(int beforeLength, int afterLength) { 50 | if (beforeLength == 1 && afterLength == 0) { 51 | return sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, 52 | KeyEvent.KEYCODE_DEL)) 53 | && sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, 54 | KeyEvent.KEYCODE_DEL)); 55 | } 56 | 57 | return super.deleteSurroundingText(beforeLength, afterLength); 58 | } 59 | 60 | } 61 | } -------------------------------------------------------------------------------- /RichEditor/app/src/main/java/com/stone/richeditor/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.stone.richeditor; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.ActivityNotFoundException; 5 | import android.content.ContentResolver; 6 | import android.content.Intent; 7 | import android.database.Cursor; 8 | import android.net.Uri; 9 | import android.os.Bundle; 10 | import android.os.Environment; 11 | import android.provider.MediaStore; 12 | import android.provider.MediaStore.Images.ImageColumns; 13 | import android.support.v4.app.FragmentActivity; 14 | import android.support.v7.app.AppCompatActivity; 15 | import android.util.Log; 16 | import android.view.View; 17 | import android.view.View.OnClickListener; 18 | import android.view.Window; 19 | 20 | import com.stone.richeditor.RichTextEditor.EditData; 21 | 22 | import java.io.File; 23 | import java.text.SimpleDateFormat; 24 | import java.util.Date; 25 | import java.util.List; 26 | 27 | /** 28 | * 主Activity入口 29 | * 30 | * @author xmuSistone 31 | * 32 | */ 33 | @SuppressLint("SimpleDateFormat") 34 | public class MainActivity extends FragmentActivity { 35 | private static final int REQUEST_CODE_PICK_IMAGE = 1023; 36 | private static final int REQUEST_CODE_CAPTURE_CAMEIA = 1022; 37 | private RichTextEditor editor; 38 | private View btn1, btn2, btn3; 39 | private OnClickListener btnListener; 40 | 41 | private static final File PHOTO_DIR = new File( 42 | Environment.getExternalStorageDirectory() + "/DCIM/Camera"); 43 | private File mCurrentPhotoFile;// 照相机拍照得到的图片 44 | 45 | @Override 46 | protected void onCreate(Bundle savedInstanceState) { 47 | super.onCreate(savedInstanceState); 48 | requestWindowFeature(Window.FEATURE_NO_TITLE); 49 | setContentView(R.layout.activity_main); 50 | 51 | editor = (RichTextEditor) findViewById(R.id.richEditor); 52 | btnListener = new OnClickListener() { 53 | 54 | @Override 55 | public void onClick(View v) { 56 | editor.hideKeyBoard(); 57 | if (v.getId() == btn1.getId()) { 58 | // 打开系统相册 59 | Intent intent = new Intent(Intent.ACTION_PICK); 60 | intent.setType("image/*");// 相片类型 61 | startActivityForResult(intent, REQUEST_CODE_PICK_IMAGE); 62 | } else if (v.getId() == btn2.getId()) { 63 | // 打开相机 64 | openCamera(); 65 | } else if (v.getId() == btn3.getId()) { 66 | List editList = editor.buildEditData(); 67 | // 下面的代码可以上传、或者保存,请自行实现 68 | dealEditData(editList); 69 | } 70 | } 71 | }; 72 | 73 | btn1 = findViewById(R.id.button1); 74 | btn2 = findViewById(R.id.button2); 75 | btn3 = findViewById(R.id.button3); 76 | 77 | btn1.setOnClickListener(btnListener); 78 | btn2.setOnClickListener(btnListener); 79 | btn3.setOnClickListener(btnListener); 80 | } 81 | 82 | /** 83 | * 负责处理编辑数据提交等事宜,请自行实现 84 | */ 85 | protected void dealEditData(List editList) { 86 | for (EditData itemData : editList) { 87 | if (itemData.inputStr != null) { 88 | Log.d("RichEditor", "commit inputStr=" + itemData.inputStr); 89 | } else if (itemData.imagePath != null) { 90 | Log.d("RichEditor", "commit imgePath=" + itemData.imagePath); 91 | } 92 | 93 | } 94 | } 95 | 96 | protected void openCamera() { 97 | try { 98 | // Launch camera to take photo for selected contact 99 | PHOTO_DIR.mkdirs();// 创建照片的存储目录 100 | mCurrentPhotoFile = new File(PHOTO_DIR, getPhotoFileName());// 给新照的照片文件命名 101 | final Intent intent = getTakePickIntent(mCurrentPhotoFile); 102 | startActivityForResult(intent, REQUEST_CODE_CAPTURE_CAMEIA); 103 | } catch (ActivityNotFoundException e) { 104 | } 105 | } 106 | 107 | public static Intent getTakePickIntent(File f) { 108 | Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE, null); 109 | intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(f)); 110 | return intent; 111 | } 112 | 113 | /** 114 | * 用当前时间给取得的图片命名 115 | */ 116 | private String getPhotoFileName() { 117 | Date date = new Date(System.currentTimeMillis()); 118 | SimpleDateFormat dateFormat = new SimpleDateFormat( 119 | "'IMG'_yyyy-MM-dd HH:mm:ss"); 120 | return dateFormat.format(date) + ".jpg"; 121 | } 122 | 123 | @Override 124 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 125 | if (resultCode != RESULT_OK) { 126 | return; 127 | } 128 | 129 | if (requestCode == REQUEST_CODE_PICK_IMAGE) { 130 | Uri uri = data.getData(); 131 | insertBitmap(getRealFilePath(uri)); 132 | } else if (requestCode == REQUEST_CODE_CAPTURE_CAMEIA) { 133 | insertBitmap(mCurrentPhotoFile.getAbsolutePath()); 134 | } 135 | } 136 | 137 | /** 138 | * 添加图片到富文本剪辑器 139 | * 140 | * @param imagePath 141 | */ 142 | private void insertBitmap(String imagePath) { 143 | editor.insertImage(imagePath); 144 | } 145 | 146 | /** 147 | * 根据Uri获取图片文件的绝对路径 148 | */ 149 | public String getRealFilePath(final Uri uri) { 150 | if (null == uri) { 151 | return null; 152 | } 153 | 154 | final String scheme = uri.getScheme(); 155 | String data = null; 156 | if (scheme == null) { 157 | data = uri.getPath(); 158 | } else if (ContentResolver.SCHEME_FILE.equals(scheme)) { 159 | data = uri.getPath(); 160 | } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)) { 161 | Cursor cursor = getContentResolver().query(uri, 162 | new String[] { ImageColumns.DATA }, null, null, null); 163 | if (null != cursor) { 164 | if (cursor.moveToFirst()) { 165 | int index = cursor.getColumnIndex(ImageColumns.DATA); 166 | if (index > -1) { 167 | data = cursor.getString(index); 168 | } 169 | } 170 | cursor.close(); 171 | } 172 | } 173 | return data; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /RichEditor/app/src/main/java/com/stone/richeditor/RichTextEditor.java: -------------------------------------------------------------------------------- 1 | package com.stone.richeditor; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import android.animation.LayoutTransition; 7 | import android.animation.LayoutTransition.TransitionListener; 8 | import android.annotation.SuppressLint; 9 | import android.content.Context; 10 | import android.graphics.Bitmap; 11 | import android.graphics.BitmapFactory; 12 | import android.graphics.Color; 13 | import android.util.AttributeSet; 14 | import android.util.Log; 15 | import android.view.KeyEvent; 16 | import android.view.LayoutInflater; 17 | import android.view.View; 18 | import android.view.ViewGroup; 19 | import android.view.inputmethod.InputMethodManager; 20 | import android.widget.EditText; 21 | import android.widget.LinearLayout; 22 | import android.widget.RelativeLayout; 23 | import android.widget.ScrollView; 24 | 25 | /** 26 | * 这是一个富文本编辑器,给外部提供insertImage接口,添加的图片跟当前光标所在位置有关 27 | * 28 | * @author xmuSistone 29 | * 30 | */ 31 | @SuppressLint({ "NewApi", "InflateParams" }) 32 | public class RichTextEditor extends ScrollView { 33 | private static final int EDIT_PADDING = 10; // edittext常规padding是10dp 34 | private static final int EDIT_FIRST_PADDING_TOP = 10; // 第一个EditText的paddingTop值 35 | 36 | private int viewTagIndex = 1; // 新生的view都会打一个tag,对每个view来说,这个tag是唯一的。 37 | private LinearLayout allLayout; // 这个是所有子view的容器,scrollView内部的唯一一个ViewGroup 38 | private LayoutInflater inflater; 39 | private OnKeyListener keyListener; // 所有EditText的软键盘监听器 40 | private OnClickListener btnListener; // 图片右上角红叉按钮监听器 41 | private OnFocusChangeListener focusListener; // 所有EditText的焦点监听listener 42 | private EditText lastFocusEdit; // 最近被聚焦的EditText 43 | private LayoutTransition mTransitioner; // 只在图片View添加或remove时,触发transition动画 44 | private int editNormalPadding = 0; // 45 | private int disappearingImageIndex = 0; 46 | 47 | public RichTextEditor(Context context) { 48 | this(context, null); 49 | } 50 | 51 | public RichTextEditor(Context context, AttributeSet attrs) { 52 | this(context, attrs, 0); 53 | } 54 | 55 | public RichTextEditor(Context context, AttributeSet attrs, int defStyleAttr) { 56 | super(context, attrs, defStyleAttr); 57 | inflater = LayoutInflater.from(context); 58 | 59 | // 1. 初始化allLayout 60 | allLayout = new LinearLayout(context); 61 | allLayout.setOrientation(LinearLayout.VERTICAL); 62 | allLayout.setBackgroundColor(Color.WHITE); 63 | setupLayoutTransitions(); 64 | LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, 65 | LayoutParams.WRAP_CONTENT); 66 | addView(allLayout, layoutParams); 67 | 68 | // 2. 初始化键盘退格监听 69 | // 主要用来处理点击回删按钮时,view的一些列合并操作 70 | keyListener = new OnKeyListener() { 71 | 72 | @Override 73 | public boolean onKey(View v, int keyCode, KeyEvent event) { 74 | if (event.getAction() == KeyEvent.ACTION_DOWN 75 | && event.getKeyCode() == KeyEvent.KEYCODE_DEL) { 76 | EditText edit = (EditText) v; 77 | onBackspacePress(edit); 78 | } 79 | return false; 80 | } 81 | }; 82 | 83 | // 3. 图片叉掉处理 84 | btnListener = new OnClickListener() { 85 | 86 | @Override 87 | public void onClick(View v) { 88 | RelativeLayout parentView = (RelativeLayout) v.getParent(); 89 | onImageCloseClick(parentView); 90 | } 91 | }; 92 | 93 | focusListener = new OnFocusChangeListener() { 94 | 95 | @Override 96 | public void onFocusChange(View v, boolean hasFocus) { 97 | if (hasFocus) { 98 | lastFocusEdit = (EditText) v; 99 | } 100 | } 101 | }; 102 | 103 | LinearLayout.LayoutParams firstEditParam = new LinearLayout.LayoutParams( 104 | LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 105 | editNormalPadding = dip2px(EDIT_PADDING); 106 | EditText firstEdit = createEditText("input here", 107 | dip2px(EDIT_FIRST_PADDING_TOP)); 108 | allLayout.addView(firstEdit, firstEditParam); 109 | lastFocusEdit = firstEdit; 110 | } 111 | 112 | /** 113 | * 处理软键盘backSpace回退事件 114 | * 115 | * @param editTxt 116 | * 光标所在的文本输入框 117 | */ 118 | private void onBackspacePress(EditText editTxt) { 119 | int startSelection = editTxt.getSelectionStart(); 120 | // 只有在光标已经顶到文本输入框的最前方,在判定是否删除之前的图片,或两个View合并 121 | if (startSelection == 0) { 122 | int editIndex = allLayout.indexOfChild(editTxt); 123 | View preView = allLayout.getChildAt(editIndex - 1); // 如果editIndex-1<0, 124 | // 则返回的是null 125 | if (null != preView) { 126 | if (preView instanceof RelativeLayout) { 127 | // 光标EditText的上一个view对应的是图片 128 | onImageCloseClick(preView); 129 | } else if (preView instanceof EditText) { 130 | // 光标EditText的上一个view对应的还是文本框EditText 131 | String str1 = editTxt.getText().toString(); 132 | EditText preEdit = (EditText) preView; 133 | String str2 = preEdit.getText().toString(); 134 | 135 | // 合并文本view时,不需要transition动画 136 | allLayout.setLayoutTransition(null); 137 | allLayout.removeView(editTxt); 138 | allLayout.setLayoutTransition(mTransitioner); // 恢复transition动画 139 | 140 | // 文本合并 141 | preEdit.setText(str2 + str1); 142 | preEdit.requestFocus(); 143 | preEdit.setSelection(str2.length(), str2.length()); 144 | lastFocusEdit = preEdit; 145 | } 146 | } 147 | } 148 | } 149 | 150 | /** 151 | * 处理图片叉掉的点击事件 152 | * 153 | * @param view 154 | * 整个image对应的relativeLayout view 155 | * @type 删除类型 0代表backspace删除 1代表按红叉按钮删除 156 | */ 157 | private void onImageCloseClick(View view) { 158 | if (!mTransitioner.isRunning()) { 159 | disappearingImageIndex = allLayout.indexOfChild(view); 160 | allLayout.removeView(view); 161 | } 162 | } 163 | 164 | /** 165 | * 生成文本输入框 166 | */ 167 | private EditText createEditText(String hint, int paddingTop) { 168 | EditText editText = (EditText) inflater.inflate(R.layout.edit_item1, 169 | null); 170 | editText.setOnKeyListener(keyListener); 171 | editText.setTag(viewTagIndex++); 172 | editText.setPadding(editNormalPadding, paddingTop, editNormalPadding, 0); 173 | editText.setHint(hint); 174 | editText.setOnFocusChangeListener(focusListener); 175 | return editText; 176 | } 177 | 178 | /** 179 | * 生成图片View 180 | */ 181 | private RelativeLayout createImageLayout() { 182 | RelativeLayout layout = (RelativeLayout) inflater.inflate( 183 | R.layout.edit_imageview, null); 184 | layout.setTag(viewTagIndex++); 185 | View closeView = layout.findViewById(R.id.image_close); 186 | closeView.setTag(layout.getTag()); 187 | closeView.setOnClickListener(btnListener); 188 | return layout; 189 | } 190 | 191 | /** 192 | * 根据绝对路径添加view 193 | * 194 | * @param imagePath 195 | */ 196 | public void insertImage(String imagePath) { 197 | Bitmap bmp = getScaledBitmap(imagePath, getWidth()); 198 | insertImage(bmp, imagePath); 199 | } 200 | 201 | /** 202 | * 插入一张图片 203 | */ 204 | private void insertImage(Bitmap bitmap, String imagePath) { 205 | String lastEditStr = lastFocusEdit.getText().toString(); 206 | int cursorIndex = lastFocusEdit.getSelectionStart(); 207 | String editStr1 = lastEditStr.substring(0, cursorIndex).trim(); 208 | int lastEditIndex = allLayout.indexOfChild(lastFocusEdit); 209 | 210 | if (lastEditStr.length() == 0 || editStr1.length() == 0) { 211 | // 如果EditText为空,或者光标已经顶在了editText的最前面,则直接插入图片,并且EditText下移即可 212 | addImageViewAtIndex(lastEditIndex, bitmap, imagePath); 213 | } else { 214 | // 如果EditText非空且光标不在最顶端,则需要添加新的imageView和EditText 215 | lastFocusEdit.setText(editStr1); 216 | String editStr2 = lastEditStr.substring(cursorIndex).trim(); 217 | if (allLayout.getChildCount() - 1 == lastEditIndex 218 | || editStr2.length() > 0) { 219 | addEditTextAtIndex(lastEditIndex + 1, editStr2); 220 | } 221 | 222 | addImageViewAtIndex(lastEditIndex + 1, bitmap, imagePath); 223 | lastFocusEdit.requestFocus(); 224 | lastFocusEdit.setSelection(editStr1.length(), editStr1.length()); 225 | } 226 | hideKeyBoard(); 227 | } 228 | 229 | /** 230 | * 隐藏小键盘 231 | */ 232 | public void hideKeyBoard() { 233 | InputMethodManager imm = (InputMethodManager) getContext() 234 | .getSystemService(Context.INPUT_METHOD_SERVICE); 235 | imm.hideSoftInputFromWindow(lastFocusEdit.getWindowToken(), 0); 236 | } 237 | 238 | /** 239 | * 在特定位置插入EditText 240 | * 241 | * @param index 242 | * 位置 243 | * @param editStr 244 | * EditText显示的文字 245 | */ 246 | private void addEditTextAtIndex(final int index, String editStr) { 247 | EditText editText2 = createEditText("", getResources() 248 | .getDimensionPixelSize(R.dimen.edit_padding_top)); 249 | editText2.setText(editStr); 250 | 251 | // 请注意此处,EditText添加、或删除不触动Transition动画 252 | allLayout.setLayoutTransition(null); 253 | allLayout.addView(editText2, index); 254 | allLayout.setLayoutTransition(mTransitioner); // remove之后恢复transition动画 255 | } 256 | 257 | /** 258 | * 在特定位置添加ImageView 259 | */ 260 | private void addImageViewAtIndex(final int index, Bitmap bmp, 261 | String imagePath) { 262 | final RelativeLayout imageLayout = createImageLayout(); 263 | DataImageView imageView = (DataImageView) imageLayout 264 | .findViewById(R.id.edit_imageView); 265 | imageView.setImageBitmap(bmp); 266 | imageView.setBitmap(bmp); 267 | imageView.setAbsolutePath(imagePath); 268 | 269 | // 调整imageView的高度 270 | int imageHeight = getWidth() * bmp.getHeight() / bmp.getWidth(); 271 | RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams( 272 | LayoutParams.MATCH_PARENT, imageHeight); 273 | imageView.setLayoutParams(lp); 274 | 275 | // onActivityResult无法触发动画,此处post处理 276 | allLayout.postDelayed(new Runnable() { 277 | @Override 278 | public void run() { 279 | allLayout.addView(imageLayout, index); 280 | } 281 | }, 200); 282 | } 283 | 284 | /** 285 | * 根据view的宽度,动态缩放bitmap尺寸 286 | * 287 | * @param width 288 | * view的宽度 289 | */ 290 | private Bitmap getScaledBitmap(String filePath, int width) { 291 | BitmapFactory.Options options = new BitmapFactory.Options(); 292 | options.inJustDecodeBounds = true; 293 | BitmapFactory.decodeFile(filePath, options); 294 | int sampleSize = options.outWidth > width ? options.outWidth / width 295 | + 1 : 1; 296 | options.inJustDecodeBounds = false; 297 | options.inSampleSize = sampleSize; 298 | return BitmapFactory.decodeFile(filePath, options); 299 | } 300 | 301 | /** 302 | * 初始化transition动画 303 | */ 304 | private void setupLayoutTransitions() { 305 | mTransitioner = new LayoutTransition(); 306 | allLayout.setLayoutTransition(mTransitioner); 307 | mTransitioner.addTransitionListener(new TransitionListener() { 308 | 309 | @Override 310 | public void startTransition(LayoutTransition transition, 311 | ViewGroup container, View view, int transitionType) { 312 | 313 | } 314 | 315 | @Override 316 | public void endTransition(LayoutTransition transition, 317 | ViewGroup container, View view, int transitionType) { 318 | if (!transition.isRunning() 319 | && transitionType == LayoutTransition.CHANGE_DISAPPEARING) { 320 | // transition动画结束,合并EditText 321 | // mergeEditText(); 322 | } 323 | } 324 | }); 325 | mTransitioner.setDuration(300); 326 | } 327 | 328 | /** 329 | * 图片删除的时候,如果上下方都是EditText,则合并处理 330 | */ 331 | private void mergeEditText() { 332 | View preView = allLayout.getChildAt(disappearingImageIndex - 1); 333 | View nextView = allLayout.getChildAt(disappearingImageIndex); 334 | if (preView != null && preView instanceof EditText && null != nextView 335 | && nextView instanceof EditText) { 336 | Log.d("LeiTest", "合并EditText"); 337 | EditText preEdit = (EditText) preView; 338 | EditText nextEdit = (EditText) nextView; 339 | String str1 = preEdit.getText().toString(); 340 | String str2 = nextEdit.getText().toString(); 341 | String mergeText = ""; 342 | if (str2.length() > 0) { 343 | mergeText = str1 + "\n" + str2; 344 | } else { 345 | mergeText = str1; 346 | } 347 | 348 | allLayout.setLayoutTransition(null); 349 | allLayout.removeView(nextEdit); 350 | preEdit.setText(mergeText); 351 | preEdit.requestFocus(); 352 | preEdit.setSelection(str1.length(), str1.length()); 353 | allLayout.setLayoutTransition(mTransitioner); 354 | } 355 | } 356 | 357 | /** 358 | * dp和pixel转换 359 | * 360 | * @param dipValue 361 | * dp值 362 | * @return 像素值 363 | */ 364 | public int dip2px(float dipValue) { 365 | float m = getContext().getResources().getDisplayMetrics().density; 366 | return (int) (dipValue * m + 0.5f); 367 | } 368 | 369 | /** 370 | * 对外提供的接口, 生成编辑数据上传 371 | */ 372 | public List buildEditData() { 373 | List dataList = new ArrayList(); 374 | int num = allLayout.getChildCount(); 375 | for (int index = 0; index < num; index++) { 376 | View itemView = allLayout.getChildAt(index); 377 | EditData itemData = new EditData(); 378 | if (itemView instanceof EditText) { 379 | EditText item = (EditText) itemView; 380 | itemData.inputStr = item.getText().toString(); 381 | } else if (itemView instanceof RelativeLayout) { 382 | DataImageView item = (DataImageView) itemView 383 | .findViewById(R.id.edit_imageView); 384 | itemData.imagePath = item.getAbsolutePath(); 385 | itemData.bitmap = item.getBitmap(); 386 | } 387 | dataList.add(itemData); 388 | } 389 | 390 | return dataList; 391 | } 392 | 393 | class EditData { 394 | String inputStr; 395 | String imagePath; 396 | Bitmap bitmap; 397 | } 398 | } 399 | -------------------------------------------------------------------------------- /RichEditor/app/src/main/res/drawable/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmuSistone/AnimRichEditor/c67d0bc1515e67c70e4a2258da2cd69ced068443/RichEditor/app/src/main/res/drawable/close.png -------------------------------------------------------------------------------- /RichEditor/app/src/main/res/drawable/cursor_shape.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /RichEditor/app/src/main/res/drawable/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmuSistone/AnimRichEditor/c67d0bc1515e67c70e4a2258da2cd69ced068443/RichEditor/app/src/main/res/drawable/ic_launcher.png -------------------------------------------------------------------------------- /RichEditor/app/src/main/res/drawable/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmuSistone/AnimRichEditor/c67d0bc1515e67c70e4a2258da2cd69ced068443/RichEditor/app/src/main/res/drawable/image1.png -------------------------------------------------------------------------------- /RichEditor/app/src/main/res/drawable/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmuSistone/AnimRichEditor/c67d0bc1515e67c70e4a2258da2cd69ced068443/RichEditor/app/src/main/res/drawable/image2.png -------------------------------------------------------------------------------- /RichEditor/app/src/main/res/drawable/image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmuSistone/AnimRichEditor/c67d0bc1515e67c70e4a2258da2cd69ced068443/RichEditor/app/src/main/res/drawable/image3.png -------------------------------------------------------------------------------- /RichEditor/app/src/main/res/drawable/sample.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmuSistone/AnimRichEditor/c67d0bc1515e67c70e4a2258da2cd69ced068443/RichEditor/app/src/main/res/drawable/sample.jpg -------------------------------------------------------------------------------- /RichEditor/app/src/main/res/drawable/share_weibo_camera_icon_p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmuSistone/AnimRichEditor/c67d0bc1515e67c70e4a2258da2cd69ced068443/RichEditor/app/src/main/res/drawable/share_weibo_camera_icon_p.png -------------------------------------------------------------------------------- /RichEditor/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 16 | 17 | 24 | 25 | 32 | 33 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | -------------------------------------------------------------------------------- /RichEditor/app/src/main/res/layout/content_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 14 | 15 | -------------------------------------------------------------------------------- /RichEditor/app/src/main/res/layout/edit_imageview.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | 23 | 24 | -------------------------------------------------------------------------------- /RichEditor/app/src/main/res/layout/edit_item1.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | -------------------------------------------------------------------------------- /RichEditor/app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /RichEditor/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmuSistone/AnimRichEditor/c67d0bc1515e67c70e4a2258da2cd69ced068443/RichEditor/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /RichEditor/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmuSistone/AnimRichEditor/c67d0bc1515e67c70e4a2258da2cd69ced068443/RichEditor/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /RichEditor/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmuSistone/AnimRichEditor/c67d0bc1515e67c70e4a2258da2cd69ced068443/RichEditor/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /RichEditor/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmuSistone/AnimRichEditor/c67d0bc1515e67c70e4a2258da2cd69ced068443/RichEditor/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /RichEditor/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmuSistone/AnimRichEditor/c67d0bc1515e67c70e4a2258da2cd69ced068443/RichEditor/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /RichEditor/app/src/main/res/values-v21/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 10dp 3 | 4 | -------------------------------------------------------------------------------- /RichEditor/app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /RichEditor/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /RichEditor/app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16dp 5 | 16dp 6 | 7 | 0dp 8 | 9 | -------------------------------------------------------------------------------- /RichEditor/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | RichEditor 5 | Hello world! 6 | Settings 7 | 8 | 9 | -------------------------------------------------------------------------------- /RichEditor/app/src/test/java/com/stone/richeditor/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.stone.richeditor; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /RichEditor/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:1.3.0' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /RichEditor/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /RichEditor/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmuSistone/AnimRichEditor/c67d0bc1515e67c70e4a2258da2cd69ced068443/RichEditor/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /RichEditor/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue May 17 11:01:39 CST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip 7 | -------------------------------------------------------------------------------- /RichEditor/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /RichEditor/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /RichEditor/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /capture01.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmuSistone/AnimRichEditor/c67d0bc1515e67c70e4a2258da2cd69ced068443/capture01.gif -------------------------------------------------------------------------------- /capture02.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmuSistone/AnimRichEditor/c67d0bc1515e67c70e4a2258da2cd69ced068443/capture02.gif --------------------------------------------------------------------------------