├── 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 | 
17 | 
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 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | 1.7
51 |
52 |
53 |
54 |
55 |
56 |
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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/RichEditor/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/RichEditor/RichEditor.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
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 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | generateDebugAndroidTestSources
19 | generateDebugSources
20 |
21 |
22 |
23 |
24 |
25 |
26 |
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 |
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
--------------------------------------------------------------------------------