├── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── pppcar
│ │ └── richeditor
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── assets
│ │ └── fly.gif
│ ├── java
│ │ └── com
│ │ │ └── pppcar
│ │ │ └── richeditor
│ │ │ ├── GlideEngine.java
│ │ │ ├── MainActivity.java
│ │ │ ├── PictureSelecctDialog.java
│ │ │ └── VideoSelecctDialog.java
│ └── res
│ │ ├── drawable
│ │ ├── dialog_button_bg_sl.xml
│ │ ├── left_right_corner_btn_bg.xml
│ │ └── user_type_select_dialog_bg.xml
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── image_select_dialog.xml
│ │ └── video_select_dialog.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ ├── photo.png
│ │ └── video.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ ├── values-w820dp
│ │ └── dimens.xml
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ │ └── xml
│ │ └── file_paths.xml
│ └── test
│ └── java
│ └── com
│ └── pppcar
│ └── richeditor
│ └── ExampleUnitTest.java
├── build.gradle
├── gradlew
├── gradlew.bat
├── richeditorlibary
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── pppcar
│ │ └── richeditorlibary
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── pppcar
│ │ │ └── richeditorlibary
│ │ │ ├── activity
│ │ │ └── ImageRotateAct.java
│ │ │ ├── utils
│ │ │ ├── ImageUtils.java
│ │ │ ├── SDCardUtil.java
│ │ │ ├── ScreenUtils.java
│ │ │ └── UriUtil.java
│ │ │ ├── view
│ │ │ ├── DataImageView.java
│ │ │ ├── DataVideoView.java
│ │ │ ├── DeletableEditText.java
│ │ │ ├── DragLinearLayout.java
│ │ │ └── RichEditor.java
│ │ │ └── wrapper
│ │ │ └── SimpleTextWatcher.java
│ └── res
│ │ ├── drawable-hdpi
│ │ └── ab_solid_shadow_holo.9.png
│ │ ├── drawable-mdpi
│ │ └── ab_solid_shadow_holo.9.png
│ │ ├── drawable-xhdpi
│ │ └── ab_solid_shadow_holo.9.png
│ │ ├── drawable-xxhdpi
│ │ └── ab_solid_shadow_holo.9.png
│ │ ├── drawable
│ │ └── ab_solid_shadow_holo_flipped.xml
│ │ ├── layout
│ │ ├── act_rotate_img.xml
│ │ ├── edit_imageview.xml
│ │ ├── edit_videoview.xml
│ │ └── rich_edittext.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── close.png
│ │ ├── delete.png
│ │ ├── ic_launcher.png
│ │ ├── open.png
│ │ ├── rotate.png
│ │ └── up_down.png
│ │ ├── values-w820dp
│ │ └── dimens.xml
│ │ └── values
│ │ ├── attrs.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── ids.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── pppcar
│ └── richeditorlibary
│ └── ExampleUnitTest.java
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .gradle
3 | .git
4 | build
5 | local.properties
6 | gradle.properties
7 | *.iml
8 | *.project
9 | */*.project
10 | *.classpath
11 | */*.classpath
12 | .settings/*
13 | */.settings/*
14 | /app/pppscn.jks
15 | /app/release/*
16 | /app/build/*
17 | /psd
18 | /keystore/keystore.properties
19 | /app/release
20 | /keystore
21 | *.bak
22 | /pic/working_principle.drawio
23 | /app/debug
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # RichEditor
2 | [![Apache License 2.0][1]][2]
3 | [![Release Version][5]][6]
4 | [![API][3]][4]
5 | [![PRs Welcome][7]][8]
6 | [](https://996.icu)
7 | ***
8 | * ### 介绍
9 | 富文本编辑器,可添加视频和图片,带图片拖动功能
10 | 
11 | ***
12 | * ### 依赖方式
13 | #### 需要添加mavenCentral
14 | 在setting.gradle文件中
15 | ```
16 | pluginManagement {
17 | repositories {
18 | gradlePluginPortal()
19 | google()
20 | mavenCentral()
21 | }
22 | }
23 | dependencyResolutionManagement {
24 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
25 | repositories {
26 | google()
27 | mavenCentral()
28 | }
29 | }
30 | ```
31 |
32 | #### Gradle:
33 | 在你的module的build.gradle文件
34 | ```
35 | implementation 'com.log1992:RichEditor:2.0.2'
36 | ```
37 | ### Maven:
38 | ```
39 |
40 | com.log1992
41 | RichEditor
42 | 2.0.2
43 | aar
44 |
45 | ```
46 | ### Lvy
47 | ```
48 |
49 | ```
50 |
51 | ***
52 | * ### 引入的库:
53 | ```
54 | implementation 'androidx.appcompat:appcompat:1.4.0'
55 | testImplementation 'junit:junit:4.13.2'
56 | implementation 'cn.jzvd:jiaozivideoplayer:7.4.1'
57 | api 'com.github.bumptech.glide:glide:4.12.0'
58 | annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
59 | ```
60 | ***
61 | * ### 使用方法
62 | 直接使用XML文件引入即可
63 | ```
64 |
70 | ```
71 | ***
72 | * ### 注意
73 | 1. 权限处理
74 |
75 | 2. FileProvider以及`android:requestLegacyExternalStorage="true"`
76 |
77 | ***
78 | * ### 感谢
79 | [Jzvd](https://github.com/Jzvd)的[JZVideo](https://github.com/Jzvd/JZVideo)
80 | ***
81 |
82 | [1]:https://img.shields.io/:license-apache-blue.svg
83 | [2]:https://www.apache.org/licenses/LICENSE-2.0.html
84 | [3]:https://img.shields.io/badge/API-24%2B-red.svg?style=flat
85 | [4]:https://android-arsenal.com/api?level=24
86 | [5]:https://img.shields.io/badge/release-2.0.2-red.svg
87 | [6]:https://github.com/qq2519157/RichEditor/releases
88 | [7]:https://img.shields.io/badge/PRs-welcome-brightgreen.svg
89 | [8]:https://github.com/qq2519157/RichEditor/pulls
90 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .gradle
3 | .git
4 | build
5 | local.properties
6 | gradle.properties
7 | *.iml
8 | *.project
9 | */*.project
10 | *.classpath
11 | */*.classpath
12 | .settings/*
13 | */.settings/*
14 | /app/pppscn.jks
15 | /app/release/*
16 | /app/build/*
17 | /psd
18 | /keystore/keystore.properties
19 | /app/release
20 | /keystore
21 | *.bak
22 | /pic/working_principle.drawio
23 | /app/debug
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdk 31
5 | defaultConfig {
6 | applicationId "com.pppcar.richeditor"
7 | minSdk 24
8 | targetSdk 31
9 | versionCode 1
10 | versionName "1.0"
11 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | compileOptions {
20 | sourceCompatibility 11
21 | targetCompatibility 11
22 | }
23 | buildFeatures{
24 | viewBinding true
25 | }
26 | }
27 |
28 | dependencies {
29 | implementation fileTree(dir: 'libs', include: ['*.jar'])
30 | androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', {
31 | exclude group: 'com.android.support', module: 'support-annotations'
32 | })
33 | implementation 'androidx.appcompat:appcompat:1.4.0'
34 | testImplementation 'junit:junit:4.13.2'
35 | implementation 'io.github.lucksiege:pictureselector:v3.0.4'
36 | implementation "com.github.permissions-dispatcher:permissionsdispatcher:4.8.0"
37 | annotationProcessor "com.github.permissions-dispatcher:permissionsdispatcher-processor:4.8.0"
38 | implementation project(path: ':richeditorlibary')
39 | implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
40 | implementation 'io.reactivex.rxjava3:rxjava:3.1.3'
41 | }
42 |
--------------------------------------------------------------------------------
/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:\AndroidSDK\Android\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 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/pppcar/richeditor/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.pppcar.richeditor;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.pppcar.richeditor", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
17 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
33 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/assets/fly.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq2519157/RichEditor/8b982630c7162afbc1eacfb8773e235443f8807f/app/src/main/assets/fly.gif
--------------------------------------------------------------------------------
/app/src/main/java/com/pppcar/richeditor/GlideEngine.java:
--------------------------------------------------------------------------------
1 | package com.pppcar.richeditor;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.graphics.drawable.Drawable;
6 | import android.widget.ImageView;
7 |
8 | import androidx.annotation.NonNull;
9 | import androidx.annotation.Nullable;
10 | import androidx.core.graphics.drawable.RoundedBitmapDrawable;
11 | import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
12 |
13 | import com.bumptech.glide.Glide;
14 | import com.bumptech.glide.request.target.BitmapImageViewTarget;
15 | import com.bumptech.glide.request.target.CustomTarget;
16 | import com.bumptech.glide.request.transition.Transition;
17 | import com.luck.picture.lib.engine.ImageEngine;
18 | import com.luck.picture.lib.interfaces.OnCallbackListener;
19 | import com.luck.picture.lib.utils.ActivityCompatHelper;
20 |
21 | /**
22 | * Author by logan, Date on 2020/6/11.
23 | */
24 | public class GlideEngine implements ImageEngine {
25 |
26 | /**
27 | * 加载图片
28 | *
29 | * @param context 上下文
30 | * @param url 资源url
31 | * @param imageView 图片承载控件
32 | */
33 | @Override
34 | public void loadImage(@NonNull Context context, @NonNull String url, @NonNull ImageView imageView) {
35 | if (!ActivityCompatHelper.assertValidRequest(context)) {
36 | return;
37 | }
38 | Glide.with(context)
39 | .load(url)
40 | .into(imageView);
41 | }
42 |
43 | /**
44 | * 加载指定url并返回bitmap
45 | *
46 | * @param context 上下文
47 | * @param url 资源url
48 | * @param maxWidth 资源最大加载尺寸
49 | * @param maxHeight 资源最大加载尺寸
50 | * @param call 回调接口
51 | */
52 | @Override
53 | public void loadImageBitmap(@NonNull Context context, @NonNull String url, int maxWidth, int maxHeight, OnCallbackListener call) {
54 | if (!ActivityCompatHelper.assertValidRequest(context)) {
55 | return;
56 | }
57 | Glide.with(context)
58 | .asBitmap()
59 | .override(maxWidth, maxHeight)
60 | .load(url)
61 | .into(new CustomTarget() {
62 |
63 | @Override
64 | public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition super Bitmap> transition) {
65 | if (call != null) {
66 | call.onCall(resource);
67 | }
68 | }
69 |
70 | @Override
71 | public void onLoadFailed(@Nullable Drawable errorDrawable) {
72 | if (call != null) {
73 | call.onCall(null);
74 | }
75 | }
76 |
77 | @Override
78 | public void onLoadCleared(@Nullable Drawable placeholder) {
79 |
80 | }
81 |
82 | });
83 | }
84 |
85 | /**
86 | * 加载相册目录封面
87 | *
88 | * @param context 上下文
89 | * @param url 图片路径
90 | * @param imageView 承载图片ImageView
91 | */
92 | @Override
93 | public void loadAlbumCover(@NonNull Context context, @NonNull String url, @NonNull ImageView imageView) {
94 | if (!ActivityCompatHelper.assertValidRequest(context)) {
95 | return;
96 | }
97 | Glide.with(context)
98 | .asBitmap()
99 | .load(url)
100 | .override(180, 180)
101 | .centerCrop()
102 | .sizeMultiplier(0.5f)
103 | .placeholder(com.luck.picture.lib.R.drawable.ps_image_placeholder)
104 | .into(new BitmapImageViewTarget(imageView) {
105 | @Override
106 | protected void setResource(Bitmap resource) {
107 | RoundedBitmapDrawable circularBitmapDrawable =
108 | RoundedBitmapDrawableFactory.
109 | create(context.getResources(), resource);
110 | circularBitmapDrawable.setCornerRadius(8);
111 | imageView.setImageDrawable(circularBitmapDrawable);
112 | }
113 | });
114 | }
115 |
116 |
117 | /**
118 | * 加载图片列表图片
119 | *
120 | * @param context 上下文
121 | * @param url 图片路径
122 | * @param imageView 承载图片ImageView
123 | */
124 | @Override
125 | public void loadGridImage(@NonNull Context context, @NonNull String url, @NonNull ImageView imageView) {
126 | if (!ActivityCompatHelper.assertValidRequest(context)) {
127 | return;
128 | }
129 | Glide.with(context)
130 | .load(url)
131 | .override(200, 200)
132 | .centerCrop()
133 | .placeholder(com.luck.picture.lib.R.drawable.ps_image_placeholder)
134 | .into(imageView);
135 | }
136 |
137 | @Override
138 | public void pauseRequests(Context context) {
139 | Glide.with(context).pauseRequests();
140 | }
141 |
142 | @Override
143 | public void resumeRequests(Context context) {
144 | Glide.with(context).resumeRequests();
145 | }
146 |
147 | private GlideEngine() {
148 | }
149 |
150 | private static GlideEngine instance;
151 |
152 | public static GlideEngine createGlideEngine() {
153 | if (null == instance) {
154 | synchronized (GlideEngine.class) {
155 | if (null == instance) {
156 | instance = new GlideEngine();
157 | }
158 | }
159 | }
160 | return instance;
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pppcar/richeditor/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.pppcar.richeditor;
2 |
3 | import android.Manifest;
4 | import android.app.Activity;
5 | import android.app.ProgressDialog;
6 | import android.content.DialogInterface;
7 | import android.content.Intent;
8 | import android.graphics.Bitmap;
9 | import android.os.Bundle;
10 | import android.text.TextUtils;
11 | import android.util.Log;
12 | import android.widget.FrameLayout;
13 | import android.widget.ImageView;
14 | import android.widget.LinearLayout;
15 | import android.widget.RelativeLayout;
16 | import android.widget.Toast;
17 |
18 | import androidx.appcompat.app.AlertDialog;
19 | import androidx.appcompat.app.AppCompatActivity;
20 |
21 | import com.bumptech.glide.Glide;
22 | import com.bumptech.glide.load.engine.DiskCacheStrategy;
23 | import com.luck.picture.lib.app.PictureAppMaster;
24 | import com.luck.picture.lib.basic.PictureSelector;
25 | import com.luck.picture.lib.config.PictureMimeType;
26 | import com.luck.picture.lib.config.SelectMimeType;
27 | import com.luck.picture.lib.config.SelectModeConfig;
28 | import com.luck.picture.lib.entity.LocalMedia;
29 | import com.luck.picture.lib.entity.MediaExtraInfo;
30 | import com.luck.picture.lib.interfaces.OnResultCallbackListener;
31 | import com.luck.picture.lib.utils.MediaUtils;
32 | import com.luck.picture.lib.utils.ToastUtils;
33 | import com.pppcar.richeditor.databinding.ActivityMainBinding;
34 | import com.pppcar.richeditorlibary.utils.ImageUtils;
35 | import com.pppcar.richeditorlibary.view.DataImageView;
36 | import com.pppcar.richeditorlibary.view.RichEditor;
37 |
38 | import java.util.ArrayList;
39 |
40 | import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
41 | import io.reactivex.rxjava3.annotations.NonNull;
42 | import io.reactivex.rxjava3.core.Observable;
43 | import io.reactivex.rxjava3.core.ObservableOnSubscribe;
44 | import io.reactivex.rxjava3.core.Observer;
45 | import io.reactivex.rxjava3.disposables.Disposable;
46 | import io.reactivex.rxjava3.schedulers.Schedulers;
47 | import permissions.dispatcher.NeedsPermission;
48 | import permissions.dispatcher.OnNeverAskAgain;
49 | import permissions.dispatcher.OnPermissionDenied;
50 | import permissions.dispatcher.OnShowRationale;
51 | import permissions.dispatcher.PermissionRequest;
52 | import permissions.dispatcher.RuntimePermissions;
53 |
54 | @RuntimePermissions
55 | public class MainActivity extends AppCompatActivity implements OnResultCallbackListener {
56 |
57 | private ProgressDialog insertDialog;
58 | private ActivityMainBinding mBinding;
59 |
60 | @Override
61 | protected void onCreate(Bundle savedInstanceState) {
62 | super.onCreate(savedInstanceState);
63 | mBinding = ActivityMainBinding.inflate(getLayoutInflater());
64 | setContentView(mBinding.getRoot());
65 | MainActivityPermissionsDispatcher.initViewWithPermissionCheck(this);
66 | }
67 |
68 | /**
69 | * 上传照片
70 | */
71 | public void uploadImage() {
72 | PictureSelecctDialog pictureSelecctDialog = new PictureSelecctDialog(this, v -> {
73 | int tag = (Integer) v.getTag();
74 | PictureSelector pictureSelector = PictureSelector.create(this);
75 | switch (tag) {
76 | case PictureSelecctDialog.FROM_ALBUM:
77 | pictureSelector
78 | .openGallery(SelectMimeType.ofImage())
79 | .setImageEngine(GlideEngine.createGlideEngine())
80 | .setSelectionMode(SelectModeConfig.MULTIPLE)
81 | .forResult(this);
82 | break;
83 | case PictureSelecctDialog.TAKE_PICTURE:
84 | pictureSelector
85 | .openCamera(SelectMimeType.ofImage())
86 | .isCameraForegroundService(false)
87 | .forResult(this);
88 | break;
89 | default:
90 | break;
91 | }
92 | });
93 | pictureSelecctDialog.show();
94 | }
95 |
96 | @OnShowRationale({Manifest.permission.CAMERA,
97 | Manifest.permission.READ_EXTERNAL_STORAGE,
98 | Manifest.permission.WRITE_EXTERNAL_STORAGE})
99 | //给用户解释要请求什么权限,为什么需要此权限
100 | public void showRationale(final PermissionRequest request) {
101 | new AlertDialog.Builder(MainActivity.this, androidx.appcompat.R.style.Theme_AppCompat_Light_Dialog_Alert)
102 | .setMessage("使用此功能需要权限,是否继续请求权限")
103 | .setPositiveButton("继续", new DialogInterface.OnClickListener() {
104 | @Override
105 | public void onClick(DialogInterface dialog, int which) {
106 | request.proceed();//继续执行请求
107 | }
108 | }).setNegativeButton("取消", new DialogInterface.OnClickListener() {
109 | @Override
110 | public void onClick(DialogInterface dialog, int which) {
111 | request.cancel();//取消执行请求
112 | }
113 | })
114 | .show();
115 | }
116 |
117 | @OnNeverAskAgain({Manifest.permission.CAMERA,
118 | Manifest.permission.READ_EXTERNAL_STORAGE,
119 | Manifest.permission.WRITE_EXTERNAL_STORAGE})
120 | public void multiNeverAsk() {
121 | ToastUtils.showToast(this, "权限未授予,部分功能可能无法正常执行");
122 | initView();
123 | }
124 |
125 | @OnPermissionDenied({Manifest.permission.CAMERA,
126 | Manifest.permission.READ_EXTERNAL_STORAGE,
127 | Manifest.permission.WRITE_EXTERNAL_STORAGE})//一旦用户拒绝了
128 | public void multiDenied() {
129 | ToastUtils.showToast(this, "已拒绝一个或以上权限,可能影响正常使用");
130 | }
131 |
132 | @Override
133 | public void onRequestPermissionsResult(int requestCode, @androidx.annotation.NonNull String[] permissions, @androidx.annotation.NonNull int[] grantResults) {
134 | super.onRequestPermissionsResult(requestCode, permissions, grantResults);
135 | MainActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
136 | }
137 |
138 |
139 | /**
140 | * 上传视频
141 | */
142 | public void uploadVideo() {
143 | VideoSelecctDialog videoSelecctDialog = new VideoSelecctDialog(this, v -> {
144 | int tag = (Integer) v.getTag();
145 | PictureSelector pictureSelector = PictureSelector.create(this);
146 | switch (tag) {
147 | case VideoSelecctDialog.FROM_ALBUM:
148 | pictureSelector
149 | .openGallery(SelectMimeType.ofVideo())
150 | .setImageEngine(GlideEngine.createGlideEngine())
151 | .setSelectionMode(SelectModeConfig.SINGLE)
152 | .forResult(this);
153 | break;
154 | case VideoSelecctDialog.BY_CAMERA:
155 | pictureSelector
156 | .openCamera(SelectMimeType.ofVideo())
157 | .isCameraForegroundService(true)
158 | .forResult(this);
159 | break;
160 | default:
161 | break;
162 | }
163 | });
164 | videoSelecctDialog.show();
165 | }
166 |
167 | @NeedsPermission({Manifest.permission.CAMERA,
168 | Manifest.permission.READ_EXTERNAL_STORAGE,
169 | Manifest.permission.WRITE_EXTERNAL_STORAGE})
170 | public void initView() {
171 | insertDialog = new ProgressDialog(this);
172 | insertDialog.setMessage("正在插入图片...");
173 | insertDialog.setCanceledOnTouchOutside(false);
174 | initEvent();
175 | }
176 |
177 | private void initEvent() {
178 | mBinding.richEt.setOnFocusChangeListener((v, hasFocus) -> {
179 | mBinding.ibPic.setEnabled(hasFocus);
180 | mBinding.ibPic.setClickable(hasFocus);
181 | mBinding.ibVideo.setEnabled(hasFocus);
182 | mBinding.ibVideo.setClickable(hasFocus);
183 | });
184 | mBinding.ibPic.setOnClickListener(v -> uploadImage());
185 | mBinding.ibVideo.setOnClickListener(v -> uploadVideo());
186 | }
187 |
188 |
189 | /**
190 | * 异步方式插入图片
191 | *
192 | * @param imagePath 图片路径
193 | */
194 | private void insertImagesSync(final String imagePath) {
195 | insertDialog.show();
196 | Observable.create((ObservableOnSubscribe) subscriber -> {
197 | try {
198 |
199 | //Log.i("NewActivity", "###imagePath="+imagePath);
200 | subscriber.onNext(imagePath);
201 |
202 | subscriber.onComplete();
203 | } catch (Exception e) {
204 | e.printStackTrace();
205 | subscriber.onError(e);
206 | }
207 | })
208 | .subscribeOn(Schedulers.io())//生产事件在io
209 | .observeOn(AndroidSchedulers.mainThread())//消费事件在UI线程
210 | .subscribe(new Observer() {
211 | @Override
212 | public void onComplete() {
213 | insertDialog.dismiss();
214 | mBinding.richEt.addEditTextAtIndex(mBinding.richEt.getLastIndex(), " ");
215 | Toast.makeText(MainActivity.this, "图片插入成功", Toast.LENGTH_SHORT).show();
216 | }
217 |
218 | @Override
219 | public void onError(Throwable e) {
220 | insertDialog.dismiss();
221 | Toast.makeText(MainActivity.this, "图片插入失败:" + e.getMessage(), Toast.LENGTH_SHORT).show();
222 | }
223 |
224 | @Override
225 | public void onSubscribe(@NonNull Disposable d) {
226 |
227 | }
228 |
229 | @Override
230 | public void onNext(String imagePath) {
231 | mBinding.richEt.insertImage(imagePath, mBinding.richEt.getMeasuredWidth());
232 | }
233 | });
234 | }
235 |
236 | /**
237 | * 异步方式插入视频
238 | *
239 | * @param videoPath 视频路径
240 | */
241 | private void insertVideosSync(final String videoPath, final String firstImgUrl) {
242 | insertDialog.show();
243 |
244 | Observable.create((ObservableOnSubscribe) subscriber -> {
245 | try {
246 | subscriber.onNext(videoPath);
247 | subscriber.onComplete();
248 | } catch (Exception e) {
249 | e.printStackTrace();
250 | subscriber.onError(e);
251 | }
252 | })
253 | .subscribeOn(Schedulers.io())//生产事件在io
254 | .observeOn(AndroidSchedulers.mainThread())//消费事件在UI线程
255 | .subscribe(new Observer() {
256 | @Override
257 | public void onError(Throwable e) {
258 | insertDialog.dismiss();
259 | Toast.makeText(MainActivity.this, "视频插入失败:" + e.getMessage(), Toast.LENGTH_SHORT).show();
260 | }
261 |
262 | @Override
263 | public void onComplete() {
264 | insertDialog.dismiss();
265 | mBinding.richEt.addEditTextAtIndex(mBinding.richEt.getLastIndex(), " ");
266 | Toast.makeText(MainActivity.this, "视频插入成功", Toast.LENGTH_SHORT).show();
267 | }
268 |
269 | @Override
270 | public void onSubscribe(@NonNull Disposable d) {
271 |
272 | }
273 |
274 | @Override
275 | public void onNext(String videoPath) {
276 | mBinding.richEt.insertVideo(videoPath, firstImgUrl);
277 | }
278 | });
279 | }
280 |
281 | @Override
282 | protected void onDestroy() {
283 | super.onDestroy();
284 |
285 | }
286 |
287 | @Override
288 | public void onActivityResult(int requestCode, int resultCode, Intent data) {
289 |
290 | if (resultCode == Activity.RESULT_OK) {
291 | switch (requestCode) {
292 | case RichEditor.ROTATE_IMAGE:
293 | String imagePath = data.getStringExtra("imagePath");
294 | if (imagePath == null) {
295 | return;
296 | }
297 | Log.e("imagePath+++++", imagePath);
298 | int index = data.getIntExtra("index", 0);
299 | Log.e("index+++++", index + "");
300 | LinearLayout allLayout = (LinearLayout) mBinding.richEt.getChildAt(0);
301 | RelativeLayout childAt = (RelativeLayout) allLayout.getChildAt(index);
302 | ImageView open = childAt.findViewById(com.pppcar.richeditorlibary.R.id.iv_open);
303 | ImageView rotate = childAt.findViewById(com.pppcar.richeditorlibary.R.id.iv_rotate);
304 | ImageView delete = childAt.findViewById(com.pppcar.richeditorlibary.R.id.iv_delete);
305 | open.setImageResource(com.pppcar.richeditorlibary.R.mipmap.open);
306 | mBinding.richEt.hideMenu(open, delete, rotate);
307 | DataImageView imageView = (DataImageView) childAt.getChildAt(0);
308 | imageView.setAbsolutePath(imagePath);
309 | imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);//裁剪居中
310 | int imageHeight = allLayout.getWidth() * 3 / 5;
311 | //调整图片高度,这里是否有必要,如果出现微博长图,可能会很难看
312 | RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
313 | FrameLayout.LayoutParams.MATCH_PARENT, imageHeight);//设置图片固定高度
314 | lp.bottomMargin = 10;
315 | imageView.setLayoutParams(lp);
316 | Glide.with(this).load(imagePath).skipMemoryCache(true).diskCacheStrategy(DiskCacheStrategy.NONE).into(imageView);
317 | // imageView.setImageBitmap(mBinding.richEt.getScaledBitmap(imagePath, mBinding.richEt.getMeasuredWidth()));
318 | break;
319 | default:
320 | break;
321 | }
322 | }
323 | super.onActivityResult(requestCode, resultCode, data);
324 | }
325 |
326 | @Override
327 | public void onResult(ArrayList result) {
328 | for (LocalMedia media : result) {
329 | if (media.getWidth() == 0 || media.getHeight() == 0) {
330 | if (PictureMimeType.isHasImage(media.getMimeType())) {
331 | MediaExtraInfo imageExtraInfo = MediaUtils.getImageSize(media.getPath());
332 | media.setWidth(imageExtraInfo.getWidth());
333 | media.setHeight(imageExtraInfo.getHeight());
334 | } else if (PictureMimeType.isHasVideo(media.getMimeType())) {
335 | MediaExtraInfo videoExtraInfo = MediaUtils.getVideoSize(PictureAppMaster.getInstance().getAppContext(), media.getPath());
336 | media.setWidth(videoExtraInfo.getWidth());
337 | media.setHeight(videoExtraInfo.getHeight());
338 | }
339 | }
340 | String path = "";
341 | if (media.isCompressed()) {
342 | path = media.getCompressPath();
343 | } else if (media.isCut()) {
344 | path = media.getCutPath();
345 | } else if (media.isToSandboxPath()) {
346 | path = media.getSandboxPath();
347 | } else {
348 | path = media.getRealPath();
349 | }
350 | if (TextUtils.isEmpty(path)) {
351 | continue;
352 | }
353 | if (PictureMimeType.isHasImage(media.getMimeType())) {
354 | insertImagesSync(path);
355 | } else if (PictureMimeType.isHasVideo(media.getMimeType())) {
356 | Bitmap bitmap = ImageUtils.getFirstImg(path);
357 | String firstImgPath = ImageUtils.saveFirstBitmap(bitmap);
358 | insertVideosSync(path, firstImgPath);
359 | }
360 | }
361 | }
362 |
363 | @Override
364 | public void onCancel() {
365 |
366 | }
367 | }
368 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pppcar/richeditor/PictureSelecctDialog.java:
--------------------------------------------------------------------------------
1 | package com.pppcar.richeditor;
2 |
3 | import android.app.Dialog;
4 | import android.content.Context;
5 | import android.view.View;
6 | import android.widget.Button;
7 |
8 |
9 |
10 | public class PictureSelecctDialog extends Dialog {
11 |
12 | final public static int FROM_ALBUM=1;
13 | final public static int TAKE_PICTURE=2;
14 |
15 | Button mFromAlbum;
16 | Button mTakePhoto;
17 |
18 | private View.OnClickListener onClickListener;
19 |
20 | public void setOnClickListener(View.OnClickListener listener) {
21 | this.onClickListener = listener;
22 | }
23 |
24 | public PictureSelecctDialog(Context context, View.OnClickListener clickListener) {
25 | super(context, R.style.select_picture_dialog);
26 | this.onClickListener = clickListener;
27 | this.setContentView(R.layout.image_select_dialog);
28 | mFromAlbum = (Button) findViewById(R.id.from_album);
29 | mFromAlbum.setOnClickListener(new View.OnClickListener() {
30 | @Override
31 | public void onClick(View v) {
32 | v.setTag(FROM_ALBUM);
33 | onClickListener.onClick(v);
34 | dismiss();
35 | }
36 | });
37 | mTakePhoto = (Button) findViewById(R.id.take_photo);
38 | mTakePhoto.setOnClickListener(new View.OnClickListener() {
39 | @Override
40 | public void onClick(View v) {
41 | v.setTag(TAKE_PICTURE);
42 | onClickListener.onClick(v);
43 | dismiss();
44 | }
45 | });
46 |
47 |
48 | }
49 |
50 | @Override
51 | public void dismiss() {
52 | super.dismiss();
53 | }
54 |
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pppcar/richeditor/VideoSelecctDialog.java:
--------------------------------------------------------------------------------
1 | package com.pppcar.richeditor;
2 |
3 | import android.app.Dialog;
4 | import android.content.Context;
5 | import android.view.View;
6 | import android.widget.Button;
7 |
8 |
9 |
10 | public class VideoSelecctDialog extends Dialog {
11 |
12 | final public static int FROM_ALBUM=1;
13 | final public static int BY_CAMERA=2;
14 |
15 | Button mFromAlbum;
16 | Button mTakePhoto;
17 |
18 | private View.OnClickListener onClickListener;
19 |
20 | public void setOnClickListener(View.OnClickListener listener) {
21 | this.onClickListener = listener;
22 | }
23 |
24 | public VideoSelecctDialog(Context context, View.OnClickListener clickListener) {
25 | super(context, R.style.select_picture_dialog);
26 | this.onClickListener = clickListener;
27 | this.setContentView(R.layout.video_select_dialog);
28 | mFromAlbum = (Button) findViewById(R.id.from_album);
29 | mFromAlbum.setOnClickListener(new View.OnClickListener() {
30 | @Override
31 | public void onClick(View v) {
32 | v.setTag(FROM_ALBUM);
33 | onClickListener.onClick(v);
34 | dismiss();
35 | }
36 | });
37 | mTakePhoto = (Button) findViewById(R.id.take_photo);
38 | mTakePhoto.setOnClickListener(new View.OnClickListener() {
39 | @Override
40 | public void onClick(View v) {
41 | v.setTag(BY_CAMERA);
42 | onClickListener.onClick(v);
43 | dismiss();
44 | }
45 | });
46 |
47 |
48 | }
49 |
50 | @Override
51 | public void dismiss() {
52 | super.dismiss();
53 | }
54 |
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/dialog_button_bg_sl.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/left_right_corner_btn_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
9 |
10 |
11 | >
12 |
13 | -
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/user_type_select_dialog_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
19 |
20 |
28 |
36 |
37 |
38 |
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/image_select_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
20 |
21 |
26 |
27 |
28 |
41 |
42 |
47 |
48 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/video_select_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
20 |
21 |
26 |
27 |
28 |
41 |
42 |
47 |
48 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq2519157/RichEditor/8b982630c7162afbc1eacfb8773e235443f8807f/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq2519157/RichEditor/8b982630c7162afbc1eacfb8773e235443f8807f/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq2519157/RichEditor/8b982630c7162afbc1eacfb8773e235443f8807f/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq2519157/RichEditor/8b982630c7162afbc1eacfb8773e235443f8807f/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/photo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq2519157/RichEditor/8b982630c7162afbc1eacfb8773e235443f8807f/app/src/main/res/mipmap-xxhdpi/photo.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/video.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq2519157/RichEditor/8b982630c7162afbc1eacfb8773e235443f8807f/app/src/main/res/mipmap-xxhdpi/video.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq2519157/RichEditor/8b982630c7162afbc1eacfb8773e235443f8807f/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 | #FFEBEBEB
7 | #A9A9A9
8 | #FFFFFF
9 | #000000
10 |
11 | #D3D3D3
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 | 16dp
6 | 18sp
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | RichEditor
3 | 选择图像来源
4 | 选择视频来源
5 | 相册
6 | 拍照
7 | 摄像
8 | 请稍等
9 | 确定
10 | 取消
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
22 |
23 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/file_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/test/java/com/pppcar/richeditor/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.pppcar.richeditor;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | /**
3 | * You should use `apply false` in the top-level build.gradle file
4 | * to add a Gradle plugin as a build dependency, but not apply it to the
5 | * current (root) project. You should not use `apply false` in sub-projects.
6 | * For more information, see
7 | * Applying external plugins with same version to subprojects.
8 | */
9 | id 'com.android.application' version '7.1.2' apply false
10 | id 'com.android.library' version '7.1.2' apply false
11 | id 'com.vanniktech.maven.publish' version '0.18.0' apply false
12 | }
13 |
14 | task clean(type: Delete) {
15 | delete rootProject.buildDir
16 | }
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/richeditorlibary/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .gradle
3 | .git
4 | build
5 | local.properties
6 | gradle.properties
7 | *.iml
8 | *.project
9 | */*.project
10 | *.classpath
11 | */*.classpath
12 | .settings/*
13 | */.settings/*
14 | /app/pppscn.jks
15 | /app/release/*
16 | /app/build/*
17 | /psd
18 | /keystore/keystore.properties
19 | /app/release
20 | /keystore
21 | *.bak
22 | /pic/working_principle.drawio
23 | /app/debug
--------------------------------------------------------------------------------
/richeditorlibary/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'com.vanniktech.maven.publish'
4 | }
5 |
6 | android {
7 | compileSdk 31
8 | defaultConfig {
9 | minSdk 24
10 | }
11 | }
12 |
13 | allprojects {
14 | plugins.withId("com.vanniktech.maven.publish") {
15 | mavenPublish {
16 | sonatypeHost = "S01"
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | implementation fileTree(dir: 'libs', include: ['*.jar'])
23 | androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', {
24 | exclude group: 'com.android.support', module: 'support-annotations'
25 | })
26 | implementation 'androidx.appcompat:appcompat:1.4.0'
27 | testImplementation 'junit:junit:4.13.2'
28 | implementation 'cn.jzvd:jiaozivideoplayer:7.7.0'
29 | api 'com.github.bumptech.glide:glide:4.12.0'
30 | annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
31 | }
--------------------------------------------------------------------------------
/richeditorlibary/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:\AndroidSDK\Android\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 |
--------------------------------------------------------------------------------
/richeditorlibary/src/androidTest/java/com/pppcar/richeditorlibary/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.pppcar.richeditorlibary;
2 |
3 | import android.content.Context;
4 | import androidx.test.platform.app.InstrumentationRegistry;
5 | import androidx.test.ext.junit.runners.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.pppcar.richeditorlibiary", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/richeditorlibary/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/richeditorlibary/src/main/java/com/pppcar/richeditorlibary/activity/ImageRotateAct.java:
--------------------------------------------------------------------------------
1 | package com.pppcar.richeditorlibary.activity;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.graphics.Bitmap;
6 | import android.graphics.BitmapFactory;
7 | import android.graphics.Canvas;
8 | import android.graphics.Matrix;
9 | import android.graphics.Paint;
10 | import android.os.Bundle;
11 | import android.widget.ImageButton;
12 | import android.widget.ImageView;
13 | import android.widget.TextView;
14 | import android.widget.Toast;
15 |
16 | import com.bumptech.glide.Glide;
17 | import com.bumptech.glide.load.engine.DiskCacheStrategy;
18 | import com.bumptech.glide.request.target.SimpleTarget;
19 | import com.bumptech.glide.request.transition.Transition;
20 | import com.pppcar.richeditorlibary.R;
21 | import com.pppcar.richeditorlibary.utils.ImageUtils;
22 | import com.pppcar.richeditorlibary.utils.ScreenUtils;
23 |
24 | import java.io.IOException;
25 | import java.io.InputStream;
26 | import java.net.HttpURLConnection;
27 | import java.net.MalformedURLException;
28 | import java.net.URL;
29 |
30 | import androidx.annotation.NonNull;
31 | import androidx.annotation.Nullable;
32 |
33 | /**
34 | * 作者: Logan on 2017/11/30.
35 | * 邮箱: 490636907@qq.com
36 | * 描述: 图片旋转界面
37 | */
38 |
39 | @SuppressWarnings({"deprecation","unused"})
40 | public class ImageRotateAct extends Activity {
41 |
42 | private ImageButton mCancel;
43 | private TextView mSure;
44 | private ImageButton mRotate;
45 | private ImageView mPic;
46 | private String mImagePath;
47 | private Bitmap mSmallBitmap;
48 | private int mIndex;
49 |
50 | @Override
51 | protected void onCreate(Bundle savedInstanceState) {
52 | super.onCreate(savedInstanceState);
53 | setContentView(R.layout.act_rotate_img);
54 | initView();
55 | mImagePath = getIntent().getStringExtra("imagePath");
56 | mIndex = getIntent().getIntExtra("index", 0);
57 | checkImage(mImagePath);
58 | initEvent();
59 | }
60 |
61 | private void checkImage(String imagePath) {
62 | if (mImagePath.contains("content") || mImagePath.contains("storage") || mImagePath.contains("sdcard")) {
63 | //本地路径
64 | int width = ScreenUtils.getScreenWidth(this);
65 | int height = ScreenUtils.getScreenHeight(this);
66 | mSmallBitmap = ImageUtils.getSmallBitmap(mImagePath, width, height);
67 | if (mPic != null && mSmallBitmap != null) {
68 | mPic.setImageBitmap(mSmallBitmap);
69 | }
70 | } else {
71 | Glide.with(this).asBitmap().load(imagePath).skipMemoryCache(true).diskCacheStrategy(DiskCacheStrategy.NONE).into(new SimpleTarget() {
72 | @Override
73 | public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition super Bitmap> transition) {
74 | mSmallBitmap = resource;
75 | mPic.setImageBitmap(resource);
76 | }
77 | });
78 | //方法中设置asBitmap可以设置回调类型
79 | }
80 |
81 | }
82 |
83 | private void initEvent() {
84 | mRotate.setOnClickListener(v -> {
85 | if (mSmallBitmap != null) {
86 | int orientationDegree = 90 ;
87 | Bitmap newBitmap = adjustPhotoRotation(mSmallBitmap, orientationDegree);
88 | mPic.setImageBitmap(newBitmap);
89 | mSmallBitmap = newBitmap;
90 | }
91 | });
92 |
93 | mSure.setOnClickListener(v -> {
94 | if (mSmallBitmap != null) {
95 | String s = ImageUtils.saveBitmap(mSmallBitmap, mIndex);
96 | if ("保存出错".equals(s)) {
97 | Toast.makeText(ImageRotateAct.this, "临时图片保存出错", Toast.LENGTH_SHORT).show();
98 | } else {
99 | Intent intent=new Intent();
100 | intent.putExtra("index",mIndex);
101 | intent.putExtra("imagePath",s);
102 | setResult(RESULT_OK,intent);
103 | }
104 | finish();
105 | }
106 |
107 |
108 | });
109 |
110 | mCancel.setOnClickListener(v -> onBackPressed());
111 |
112 | }
113 |
114 |
115 |
116 |
117 |
118 | @Override
119 | protected void onDestroy() {
120 | super.onDestroy();
121 | mSmallBitmap.recycle();
122 | mSmallBitmap=null;
123 | }
124 |
125 | private void initView() {
126 | mCancel = findViewById(R.id.ib_cancel);
127 | mSure = findViewById(R.id.sure);
128 | mRotate = findViewById(R.id.ib_rotate);
129 | mPic = findViewById(R.id.iv);
130 | }
131 |
132 | private Bitmap adjustPhotoRotation(Bitmap bm, final int orientationDegree) {
133 |
134 | Matrix m = new Matrix();
135 | m.setRotate(orientationDegree, (float) bm.getWidth() / 2, (float) bm.getHeight() / 2);
136 | float targetX, targetY;
137 | if (orientationDegree == 90) {
138 | targetX = bm.getHeight();
139 | targetY = 0;
140 | } else {
141 | targetX = bm.getHeight();
142 | targetY = bm.getWidth();
143 | }
144 |
145 | final float[] values = new float[9];
146 | m.getValues(values);
147 |
148 | float x1 = values[Matrix.MTRANS_X];
149 | float y1 = values[Matrix.MTRANS_Y];
150 |
151 | m.postTranslate(targetX - x1, targetY - y1);
152 |
153 | Bitmap bm1 = Bitmap.createBitmap(bm.getHeight(), bm.getWidth(), Bitmap.Config.ARGB_8888);
154 | Paint paint = new Paint();
155 | Canvas canvas = new Canvas(bm1);
156 | canvas.drawBitmap(bm, m, paint);
157 |
158 | return bm1;
159 | }
160 |
161 | /**
162 | * 根据图片的url路径获得Bitmap对象
163 | *
164 | * @param url 图片的url
165 | * @return 返回bitmap
166 | */
167 | private Bitmap decodeUriAsBitmapFromNet(String url) {
168 | URL fileUrl = null;
169 | Bitmap bitmap = null;
170 |
171 | try {
172 | fileUrl = new URL(url);
173 | } catch (MalformedURLException e) {
174 | e.printStackTrace();
175 | }
176 |
177 | try {
178 | if (fileUrl==null) {
179 | return null;
180 | }
181 | HttpURLConnection conn = (HttpURLConnection) fileUrl
182 | .openConnection();
183 | conn.setDoInput(true);
184 | conn.connect();
185 | InputStream is = conn.getInputStream();
186 | bitmap = BitmapFactory.decodeStream(is);
187 | is.close();
188 | } catch (IOException e) {
189 | e.printStackTrace();
190 | }
191 | return bitmap;
192 | }
193 |
194 | }
195 |
--------------------------------------------------------------------------------
/richeditorlibary/src/main/java/com/pppcar/richeditorlibary/utils/ImageUtils.java:
--------------------------------------------------------------------------------
1 | package com.pppcar.richeditorlibary.utils;
2 |
3 | import android.content.ContentResolver;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.graphics.Bitmap;
7 | import android.graphics.BitmapFactory;
8 | import android.graphics.Matrix;
9 | import android.media.MediaMetadataRetriever;
10 | import android.net.Uri;
11 | import android.os.Environment;
12 | import android.util.Base64;
13 | import android.util.Log;
14 |
15 | import java.io.ByteArrayInputStream;
16 | import java.io.ByteArrayOutputStream;
17 | import java.io.File;
18 | import java.io.FileNotFoundException;
19 | import java.io.FileOutputStream;
20 | import java.io.IOException;
21 | import java.io.InputStream;
22 |
23 | /**
24 | * 作者: Logan on 2017/9/21.
25 | * 邮箱: 490636907@qq.com
26 | * 描述: 图片处理工具类
27 | */
28 | @SuppressWarnings({"deprecation", "unused", "IntegerDivisionInFloatingPointContext", "UnnecessaryLocalVariable", "ResultOfMethodCallIgnored"})
29 | public class ImageUtils {
30 | private static final String SAVE_PIC_PATH = Environment.getExternalStorageState().equalsIgnoreCase(Environment.MEDIA_MOUNTED) ? Environment.getExternalStorageDirectory().getAbsolutePath() : "/mnt/sdcard";
31 | public static final String SAVE_REAL_PATH = SAVE_PIC_PATH + "/good/RotatePic/";//保存的确切位置
32 | /**
33 | * 图片压缩处理,size参数为压缩比,比如size为2,则压缩为1/4
34 | **/
35 | public static Bitmap compressBitmap(String path, byte[] data, Context context, Uri uri, int size, boolean width) {
36 | BitmapFactory.Options options = null;
37 | if (size > 0) {
38 | BitmapFactory.Options info = new BitmapFactory.Options();
39 | /*如果设置true的时候,decode时候Bitmap返回的为数据将空*/
40 | info.inJustDecodeBounds = false;
41 | decodeBitmap(path, data, context, uri, info);
42 | int dim = info.outWidth;
43 | options = new BitmapFactory.Options();
44 | /*把图片宽高读取放在Options里*/
45 | options.inSampleSize = size;
46 | }
47 | Bitmap bm = null;
48 | try {
49 | bm = decodeBitmap(path, data, context, uri, options);
50 | } catch (Exception e) {
51 | e.printStackTrace();
52 | }
53 | return bm;
54 | }
55 |
56 |
57 | /**
58 | * 把byte数据解析成图片
59 | */
60 | private static Bitmap decodeBitmap(String path, byte[] data, Context context, Uri uri, BitmapFactory.Options options) {
61 | Bitmap result = null;
62 | if (path != null) {
63 | result = BitmapFactory.decodeFile(path, options);
64 | } else if (data != null) {
65 | result = BitmapFactory.decodeByteArray(data, 0, data.length, options);
66 | } else if (uri != null) {
67 | ContentResolver cr = context.getContentResolver();
68 | InputStream inputStream;
69 | try {
70 | inputStream = cr.openInputStream(uri);
71 | result = BitmapFactory.decodeStream(inputStream, null, options);
72 | if (inputStream != null) {
73 | inputStream.close();
74 | }
75 | } catch (Exception e) {
76 | e.printStackTrace();
77 | }
78 | }
79 | return result;
80 | }
81 |
82 |
83 | /**
84 | * 把bitmap转换成String
85 | */
86 | public static String bitmapToString(String filePath) {
87 |
88 | Bitmap bm = getSmallBitmap(filePath, 480, 800);
89 |
90 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
91 | bm.compress(Bitmap.CompressFormat.JPEG, 40, baos);
92 | byte[] b = baos.toByteArray();
93 |
94 | return Base64.encodeToString(b, Base64.DEFAULT);
95 |
96 | }
97 |
98 | /**
99 | * 计算图片的缩放值
100 | */
101 | public static int calculateInSampleSize(BitmapFactory.Options options,
102 | int reqWidth, int reqHeight) {
103 | // Raw height and width of image
104 | final int height = options.outHeight;
105 | final int width = options.outWidth;
106 | int inSampleSize = 1;
107 |
108 | if (height > reqHeight || width > reqWidth) {
109 |
110 | // Calculate ratios of height and width to requested height and
111 | // width
112 | final int heightRatio = Math.round((float) height / (float) reqHeight);
113 | final int widthRatio = Math.round((float) width / (float) reqWidth);
114 |
115 | // Choose the smallest ratio as inSampleSize value, this will
116 | // guarantee
117 | // a final image with both dimensions larger than or equal to the
118 | // requested height and width.
119 | inSampleSize = Math.min(heightRatio, widthRatio);
120 | }
121 |
122 | return inSampleSize;
123 | }
124 |
125 | /**
126 | * 根据路径获得突破并压缩返回bitmap用于显示
127 | */
128 | public static Bitmap getSmallBitmap(String filePath, int newWidth, int newHeight) {
129 | final BitmapFactory.Options options = new BitmapFactory.Options();
130 | options.inJustDecodeBounds = true;
131 | BitmapFactory.decodeFile(filePath, options);
132 |
133 | // Calculate inSampleSize
134 | options.inSampleSize = calculateInSampleSize(options, newWidth, newHeight);
135 |
136 | // Decode bitmap with inSampleSize set
137 | options.inJustDecodeBounds = false;
138 |
139 | Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);
140 | Bitmap newBitmap = compressImage(bitmap, 500);
141 | bitmap.recycle();
142 | return newBitmap;
143 | }
144 |
145 | /**
146 | * 根据路径删除图片
147 | */
148 | public static void deleteTempFile(String path) {
149 | File file = new File(path);
150 | if (file.exists()) {
151 | file.delete();
152 | }
153 | }
154 |
155 | /**
156 | * 获取视频第一帧图片
157 | */
158 | public static Bitmap getFirstImg(String videoPath) {
159 | /*
160 | MediaMetadataRetriever class provides a unified interface for retrieving
161 | frame and meta data from an input media file.
162 | */
163 | MediaMetadataRetriever mmr = new MediaMetadataRetriever();
164 | mmr.setDataSource(videoPath);
165 |
166 | Bitmap bitmap = mmr.getFrameAtTime();//获取第一帧图片
167 | mmr.release();//释放资源
168 | return bitmap;
169 | }
170 |
171 | /**
172 | * 添加到图库
173 | */
174 | public static void galleryAddPic(Context context, String path) {
175 | Intent mediaScanIntent = new Intent(
176 | Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
177 | File f = new File(path);
178 | Uri contentUri = Uri.fromFile(f);
179 | mediaScanIntent.setData(contentUri);
180 | context.sendBroadcast(mediaScanIntent);
181 | }
182 |
183 | //使用Bitmap加Matrix来缩放
184 | public static Bitmap resizeImage(Bitmap bitmapOrg, int newWidth, int newHeight)
185 | {
186 | // Bitmap bitmapOrg = BitmapFactory.decodeFile(imagePath);
187 | // 获取这个图片的宽和高
188 | int width = bitmapOrg.getWidth();
189 | int height = bitmapOrg.getHeight();
190 | //如果宽度为0 保持原图
191 | if(newWidth == 0){
192 | newWidth = width;
193 | newHeight = height;
194 | }
195 | // 创建操作图片用的matrix对象
196 | Matrix matrix = new Matrix();
197 | // 计算宽高缩放率
198 | float scaleWidth = newWidth / width;
199 | float scaleHeight = newHeight / height;
200 | // 缩放图片动作
201 | matrix.postScale(scaleWidth, scaleHeight);
202 | Bitmap resizedBitmap = Bitmap.createBitmap(bitmapOrg, 0, 0, newWidth,
203 | newHeight, matrix, true);
204 | //Log.e("###newWidth=", resizedBitmap.getWidth()+"");
205 | //Log.e("###newHeight=", resizedBitmap.getHeight()+"");
206 | resizedBitmap = compressImage(resizedBitmap, 100);//质量压缩
207 | return resizedBitmap;
208 | }
209 |
210 | //使用BitmapFactory.Options的inSampleSize参数来缩放
211 | public static Bitmap resizeImage2(String path, int width, int height)
212 | {
213 | BitmapFactory.Options options = new BitmapFactory.Options();
214 | options.inJustDecodeBounds = true;//不加载bitmap到内存中
215 | BitmapFactory.decodeFile(path,options);
216 | int outWidth = options.outWidth;
217 | int outHeight = options.outHeight;
218 | options.inDither = false;
219 | options.inPreferredConfig = Bitmap.Config.ARGB_8888;
220 | options.inSampleSize = 1;
221 |
222 | if (outWidth != 0 && outHeight != 0 && width != 0 && height != 0)
223 | {
224 | int sampleSize=(outWidth/width+outHeight/height)/2;
225 | Log.d("###", "sampleSize = " + sampleSize);
226 | options.inSampleSize = sampleSize;
227 | }
228 |
229 | options.inJustDecodeBounds = false;
230 | return BitmapFactory.decodeFile(path, options);
231 | }
232 |
233 | /**
234 | * 通过像素压缩图片,将修改图片宽高,适合获得缩略图,Used to get thumbnail
235 | */
236 | public static Bitmap compressBitmapByPath(String srcPath, float pixelW, float pixelH) {
237 | BitmapFactory.Options newOpts = new BitmapFactory.Options();
238 | //开始读入图片,此时把options.inJustDecodeBounds 设回true了
239 | newOpts.inJustDecodeBounds = true;
240 | newOpts.inPreferredConfig = Bitmap.Config.RGB_565;
241 | newOpts.inJustDecodeBounds = false;
242 | int w = newOpts.outWidth;
243 | int h = newOpts.outHeight;
244 | //现在主流手机比较多是800*480分辨率,所以高和宽我们设置为
245 | float hh = pixelH;//这里设置高度为800f
246 | float ww = pixelW;//这里设置宽度为480f
247 | //缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
248 | int be = 1;//be=1表示不缩放
249 | if (w > h && w > ww) {//如果宽度大的话根据宽度固定大小缩放
250 | be = (int) (newOpts.outWidth / ww);
251 | } else if (w < h && h > hh) {//如果高度高的话根据宽度固定大小缩放
252 | be = (int) (newOpts.outHeight / hh);
253 | }
254 | if (be <= 0)
255 | be = 1;
256 | newOpts.inSampleSize = be;//设置缩放比例
257 | //重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了
258 | Bitmap bitmap = BitmapFactory.decodeFile(srcPath, newOpts);
259 | // return compress(bitmap, maxSize); // 这里再进行质量压缩的意义不大,反而耗资源,删除
260 | return bitmap;
261 | }
262 |
263 | /**
264 | * 通过大小压缩,将修改图片宽高,适合获得缩略图,Used to get thumbnail
265 | */
266 | public static Bitmap compressBitmapByBmp(Bitmap image, float pixelW, float pixelH) {
267 | ByteArrayOutputStream os = new ByteArrayOutputStream();
268 | image.compress(Bitmap.CompressFormat.JPEG, 100, os);
269 | if( os.toByteArray().length / 1024>1024) {//判断如果图片大于1M,进行压缩避免在生成图片(BitmapFactory.decodeStream)时溢出
270 | os.reset();//重置baos即清空baos
271 | image.compress(Bitmap.CompressFormat.JPEG, 50, os);//这里压缩50%,把压缩后的数据存放到baos中
272 | }
273 | ByteArrayInputStream is ;
274 | BitmapFactory.Options newOpts = new BitmapFactory.Options();
275 | //开始读入图片,此时把options.inJustDecodeBounds 设回true了
276 | newOpts.inJustDecodeBounds = true;
277 | newOpts.inPreferredConfig = Bitmap.Config.RGB_565;
278 | newOpts.inJustDecodeBounds = false;
279 | int w = newOpts.outWidth;
280 | int h = newOpts.outHeight;
281 | float hh = pixelH;// 设置高度为240f时,可以明显看到图片缩小了
282 | float ww = pixelW;// 设置宽度为120f,可以明显看到图片缩小了
283 | //缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
284 | int be = 1;//be=1表示不缩放
285 | if (w > h && w > ww) {//如果宽度大的话根据宽度固定大小缩放
286 | be = (int) (newOpts.outWidth / ww);
287 | } else if (w < h && h > hh) {//如果高度高的话根据宽度固定大小缩放
288 | be = (int) (newOpts.outHeight / hh);
289 | }
290 | if (be <= 0) be = 1;
291 | newOpts.inSampleSize = be;//设置缩放比例
292 | //重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了
293 | is = new ByteArrayInputStream(os.toByteArray());
294 | Bitmap bitmap = BitmapFactory.decodeStream(is, null, newOpts);
295 | int desWidth = w / be;
296 | int desHeight = h / be;
297 | if (bitmap != null) {
298 | bitmap = Bitmap.createScaledBitmap(bitmap, desWidth, desHeight, true);
299 | }
300 | //压缩好比例大小后再进行质量压缩
301 | // return compress(bitmap, maxSize); // 这里再进行质量压缩的意义不大,反而耗资源,删除
302 | return bitmap;
303 | }
304 |
305 | /**
306 | * 质量压缩
307 | */
308 | public static Bitmap compressImage(Bitmap image, int maxSize){
309 | ByteArrayOutputStream os = new ByteArrayOutputStream();
310 | // scale
311 | int options = 80;
312 | // Store the bitmap into output stream(no compress)
313 | image.compress(Bitmap.CompressFormat.JPEG, options, os);
314 | // Compress by loop
315 | while ( os.toByteArray().length / 1024 > maxSize) {
316 | // Clean up os
317 | os.reset();
318 | // interval 10
319 | options -= 10;
320 | image.compress(Bitmap.CompressFormat.JPEG, options, os);
321 | }
322 |
323 | Bitmap bitmap = null;
324 | byte[] b = os.toByteArray();
325 | if (b.length != 0) {
326 | bitmap = BitmapFactory.decodeByteArray(b, 0, b.length);
327 | }
328 | return bitmap;
329 | }
330 |
331 |
332 | /**
333 | * 对图片进行缩放
334 | */
335 | public static Bitmap zoomImage(Bitmap bgimage, double newWidth, double newHeight) {
336 | // //使用方式
337 | // Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.default_img);
338 | // int paddingLeft = getPaddingLeft();
339 | // int paddingRight = getPaddingRight();
340 | // int bmWidth = bitmap.getWidth();//图片高度
341 | // int bmHeight = bitmap.getHeight();//图片宽度
342 | // int zoomWidth = getWidth() - (paddingLeft + paddingRight);
343 | // int zoomHeight = (int) (((float)zoomWidth / (float)bmWidth) * bmHeight);
344 | // Bitmap newBitmap = zoomImage(bitmap, zoomWidth,zoomHeight);
345 | // 获取这个图片的宽和高
346 | float width = bgimage.getWidth();
347 | float height = bgimage.getHeight();
348 | //如果宽度为0 保持原图
349 | if(newWidth == 0){
350 | newWidth = width;
351 | newHeight = height;
352 | }
353 | // 创建操作图片用的matrix对象
354 | Matrix matrix = new Matrix();
355 | // 计算宽高缩放率
356 | float scaleWidth = ((float) newWidth) / width;
357 | float scaleHeight = ((float) newHeight) / height;
358 | // 缩放图片动作
359 | matrix.postScale(scaleWidth, scaleHeight);
360 | Bitmap bitmap = Bitmap.createBitmap(bgimage, 0, 0, (int) width,
361 | (int) height, matrix, true);
362 | bitmap = compressImage(bitmap, 100);//质量压缩
363 | return bitmap;
364 | }
365 |
366 | /** 保存方法 */
367 | public static String saveBitmap(Bitmap bmp, int index) {
368 | if (bmp == null) {
369 | return "保存出错";
370 | }
371 |
372 | File appDir = new File(SAVE_REAL_PATH);
373 | if (!appDir.exists()) {
374 | appDir.mkdirs();
375 | }
376 | String fileName = null;
377 | if (appDir.isDirectory()) {
378 | fileName = "tempRotate"+index+".jpg";
379 | File file = new File(appDir, fileName);
380 | if (file.exists()) {
381 | file.delete();
382 | }
383 | try {
384 | FileOutputStream fos = new FileOutputStream(file);
385 | bmp.compress(Bitmap.CompressFormat.JPEG, 100, fos);
386 | fos.flush();
387 | fos.close();
388 | } catch (FileNotFoundException e) {
389 | // showToast("文件未发现");
390 | e.printStackTrace();
391 | } catch (IOException e) {
392 | // showToast("保存出错");
393 | } catch (Exception e) {
394 | // showToast("保存出错");
395 | e.printStackTrace();
396 | }
397 |
398 | }
399 | return SAVE_REAL_PATH+fileName;
400 | // return SAVE_REAL_PATH+"tempRotate.jpg";
401 | }
402 |
403 |
404 | /** 保存方法 */
405 | public static String saveFirstBitmap(Bitmap bmp) {
406 | if (bmp == null) {
407 | // showToast("保存出错");
408 | return "保存出错";
409 | }
410 | // 首先保存图片
411 | // File appDir = new File(Environment.getExternalStorageDirectory() + "/DCIM/Camera/", "pppcar");
412 | File appDir = new File(SAVE_REAL_PATH);
413 | if (!appDir.exists()) {
414 | appDir.mkdirs();
415 | }
416 | String fileName = null;
417 | if (appDir.isDirectory()) {
418 | fileName = "firstImg.jpg";
419 | File file = new File(appDir, fileName);
420 | if (file.exists()) {
421 | file.delete();
422 | }
423 | try {
424 | FileOutputStream fos = new FileOutputStream(file);
425 | bmp.compress(Bitmap.CompressFormat.JPEG, 100, fos);
426 | fos.flush();
427 | fos.close();
428 | } catch (FileNotFoundException e) {
429 | // showToast("文件未发现");
430 | e.printStackTrace();
431 | } catch (IOException e) {
432 | // showToast("保存出错");
433 | } catch (Exception e) {
434 | // showToast("保存出错");
435 | e.printStackTrace();
436 | }
437 |
438 | }
439 | return SAVE_REAL_PATH+fileName;
440 | // return SAVE_REAL_PATH+"tempRotate.jpg";
441 | }
442 |
443 |
444 | }
445 |
--------------------------------------------------------------------------------
/richeditorlibary/src/main/java/com/pppcar/richeditorlibary/utils/SDCardUtil.java:
--------------------------------------------------------------------------------
1 | package com.pppcar.richeditorlibary.utils;
2 |
3 | import android.content.ContentResolver;
4 | import android.content.Context;
5 | import android.database.Cursor;
6 | import android.graphics.Bitmap;
7 | import android.net.Uri;
8 | import android.os.Environment;
9 | import android.provider.MediaStore;
10 |
11 | import java.io.File;
12 | import java.io.FileNotFoundException;
13 | import java.io.FileOutputStream;
14 | import java.io.IOException;
15 |
16 | /**
17 | * 作者: Logan on 2017/11/30.
18 | * 邮箱: 490636907@qq.com
19 | * 描述: SD卡工具类
20 | */
21 | public class SDCardUtil {
22 | public static String SDCardRoot = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator;
23 | /**
24 | * 检查是否存在SDCard
25 | * @return
26 | */
27 | public static boolean hasSdcard(){
28 | String state = Environment.getExternalStorageState();
29 | if(state.equals(Environment.MEDIA_MOUNTED)){
30 | return true;
31 | }else{
32 | return false;
33 | }
34 | }
35 |
36 | /**
37 | * 获得文章图片保存路径
38 | * @return
39 | */
40 | public static String getPictureDir(){
41 | String imageCacheUrl = SDCardRoot + "XRichText" + File.separator ;
42 | File file = new File(imageCacheUrl);
43 | if(!file.exists())
44 | file.mkdir(); //如果不存在则创建
45 | return imageCacheUrl;
46 | }
47 |
48 | /**
49 | * 图片保存到SD卡
50 | * @param bitmap
51 | * @return
52 | */
53 | public static String saveToSdCard(Bitmap bitmap) {
54 | String imageUrl = getPictureDir() + System.currentTimeMillis() + "-";
55 | File file = new File(imageUrl);
56 | try {
57 | FileOutputStream out = new FileOutputStream(file);
58 | if (bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out)) {
59 | out.flush();
60 | out.close();
61 | }
62 | } catch (FileNotFoundException e) {
63 | e.printStackTrace();
64 | } catch (IOException e) {
65 | e.printStackTrace();
66 | }
67 | return file.getAbsolutePath();
68 | }
69 |
70 | /**
71 | * 判断是否是emoji表情
72 | * @param codePoint
73 | * @return
74 | */
75 | public static boolean isEmojiCharacter(char codePoint) {
76 | return !((codePoint == 0x0) || (codePoint == 0x9) || (codePoint == 0xA) || (codePoint == 0xD) || ((codePoint >= 0x20) && codePoint <= 0xD7FF)) || ((codePoint >= 0xE000) && (codePoint <= 0xFFFD)) || ((codePoint >= 0x10000) && (codePoint <= 0x10FFFF));
77 | }
78 |
79 |
80 | /** 保存方法 */
81 | public static String saveBitmap(Bitmap bmp) {
82 | if (bmp == null) {
83 | // showToast("保存出错");
84 | return "保存出错";
85 | }
86 | // 首先保存图片
87 | // File appDir = new File(Environment.getExternalStorageDirectory() + "/DCIM/Camera/", "pppcar");
88 | String path= Environment.getExternalStorageState().equalsIgnoreCase(Environment.MEDIA_MOUNTED) ? Environment.getExternalStorageDirectory().getAbsolutePath() : "/mnt/sdcard/good/TempPic/";
89 | File appDir = new File(path);
90 | if (!appDir.exists()) {
91 | appDir.mkdirs();
92 | }
93 | String fileName = null;
94 | if (appDir.isDirectory()) {
95 | fileName = "tempRotate.jpg";
96 | File file = new File(appDir, fileName);
97 | if (file.exists()) {
98 | file.delete();
99 | }
100 | try {
101 | FileOutputStream fos = new FileOutputStream(file);
102 | bmp.compress(Bitmap.CompressFormat.JPEG, 100, fos);
103 | fos.flush();
104 | fos.close();
105 | } catch (FileNotFoundException e) {
106 | // showToast("文件未发现");
107 | e.printStackTrace();
108 | } catch (IOException e) {
109 | // showToast("保存出错");
110 | } catch (Exception e) {
111 | // showToast("保存出错");
112 | e.printStackTrace();
113 | }
114 |
115 | }
116 | return path+fileName;
117 | // return SAVE_REAL_PATH+"tempRotate.jpg";
118 | }
119 |
120 | /**
121 | * 保存到指定路径,笔记中插入图片
122 | * @param bitmap
123 | * @param path
124 | * @return
125 | */
126 | public static String saveToSdCard(Bitmap bitmap, String path) {
127 | File file = new File(path);
128 | try {
129 | FileOutputStream out = new FileOutputStream(file);
130 | if (bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out)) {
131 | out.flush();
132 | out.close();
133 | }
134 | } catch (FileNotFoundException e) {
135 | e.printStackTrace();
136 | } catch (IOException e) {
137 | e.printStackTrace();
138 | }
139 | //System.out.println("文件保存路径:"+ file.getAbsolutePath());
140 | return file.getAbsolutePath();
141 | }
142 |
143 | /** 删除文件 **/
144 | public static void deleteFile(String filePath) {
145 | File file = new File(filePath);
146 | if (file.isFile() && file.exists())
147 | file.delete(); // 删除文件
148 | }
149 |
150 | /**
151 | * 根据Uri获取图片文件的绝对路径
152 | */
153 | public static String getFilePathByUri(Context context, final Uri uri) {
154 | if (null == uri) {
155 | return null;
156 | }
157 | final String scheme = uri.getScheme();
158 | String data = null;
159 | if (scheme == null) {
160 | data = uri.getPath();
161 | } else if (ContentResolver.SCHEME_FILE.equals(scheme)) {
162 | data = uri.getPath();
163 | } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)) {
164 | Cursor cursor = context.getContentResolver().query(uri,
165 | new String[] { MediaStore.Images.ImageColumns.DATA }, null, null, null);
166 | if (null != cursor) {
167 | if (cursor.moveToFirst()) {
168 | int index = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
169 | if (index > -1) {
170 | data = cursor.getString(index);
171 | }
172 | }
173 | cursor.close();
174 | }
175 | }
176 | return data;
177 | }
178 |
179 | }
180 |
--------------------------------------------------------------------------------
/richeditorlibary/src/main/java/com/pppcar/richeditorlibary/utils/ScreenUtils.java:
--------------------------------------------------------------------------------
1 | package com.pppcar.richeditorlibary.utils;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.graphics.Bitmap;
6 | import android.graphics.Rect;
7 | import android.util.DisplayMetrics;
8 | import android.view.View;
9 | import android.view.WindowManager;
10 |
11 |
12 | @SuppressWarnings("unused")
13 | public class ScreenUtils {
14 |
15 | /**
16 | * 获得屏幕宽度
17 | */
18 | public static int getScreenWidth(Context context)
19 | {
20 | WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
21 | DisplayMetrics outMetrics = new DisplayMetrics();
22 | wm.getDefaultDisplay().getMetrics(outMetrics);
23 | return outMetrics.widthPixels;
24 | }
25 |
26 | /**
27 | * 获得屏幕高度
28 | */
29 | public static int getScreenHeight(Context context) {
30 | WindowManager wm = (WindowManager) context
31 | .getSystemService(Context.WINDOW_SERVICE);
32 | DisplayMetrics outMetrics = new DisplayMetrics();
33 | wm.getDefaultDisplay().getMetrics(outMetrics);
34 | return outMetrics.heightPixels;
35 | }
36 |
37 | /**
38 | * 获得状态栏高度
39 | */
40 | public static int getStatusHeight(Context context) {
41 | int statusHeight = -1;
42 | try {
43 | Class> clazz = Class.forName("com.android.internal.R$dimen");
44 | Object object = clazz.newInstance();
45 | int height = Integer.parseInt(clazz.getField("status_bar_height")
46 | .get(object).toString());
47 | statusHeight = context.getResources().getDimensionPixelSize(height);
48 | } catch (Exception e) {
49 | e.printStackTrace();
50 | }
51 | return statusHeight;
52 | }
53 |
54 | /**
55 | * 获取当前屏幕截图,包含状态栏
56 | */
57 | public static Bitmap snapShotWithStatusBar(Activity activity){
58 | View view = activity.getWindow().getDecorView();
59 | view.setDrawingCacheEnabled(true);
60 | view.buildDrawingCache();
61 | Bitmap bmp = view.getDrawingCache();
62 | int width = getScreenWidth(activity);
63 | int height = getScreenHeight(activity);
64 | Bitmap bp = null;
65 | bp = Bitmap.createBitmap(bmp, 0, 0, width, height);
66 | view.destroyDrawingCache();
67 | return bp;
68 | }
69 |
70 | /**
71 | * 获取当前屏幕截图,不包含状态栏
72 | *
73 | */
74 | public static Bitmap snapShotWithoutStatusBar(Activity activity){
75 | View view = activity.getWindow().getDecorView();
76 | view.setDrawingCacheEnabled(true);
77 | view.buildDrawingCache();
78 | Bitmap bmp = view.getDrawingCache();
79 | Rect frame = new Rect();
80 | activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
81 | int statusBarHeight = frame.top;
82 | int width = getScreenWidth(activity);
83 | int height = getScreenHeight(activity);
84 | Bitmap bp = null;
85 | bp = Bitmap.createBitmap(bmp, 0, statusBarHeight, width, height
86 | - statusBarHeight);
87 | view.destroyDrawingCache();
88 | return bp;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/richeditorlibary/src/main/java/com/pppcar/richeditorlibary/utils/UriUtil.java:
--------------------------------------------------------------------------------
1 | package com.pppcar.richeditorlibary.utils;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.ContentUris;
5 | import android.content.Context;
6 | import android.database.Cursor;
7 | import android.net.Uri;
8 | import android.os.Build;
9 | import android.os.Environment;
10 | import android.provider.DocumentsContract;
11 | import android.provider.MediaStore;
12 |
13 | /**
14 | * 作者: Logan on 2017/9/21.
15 | * 邮箱: 490636907@qq.com
16 | * 描述: uri转换工具类
17 | */
18 | @SuppressLint("NewApi")
19 | public class UriUtil {
20 | /**
21 | * Get a file path from a Uri. This will get the the path for Storage Access
22 | * Framework Documents, as well as the _data field for the MediaStore and
23 | * other file-based ContentProviders.
24 | *
25 | * @param context The context.
26 | * @param uri The Uri to query.
27 | */
28 | public static String getPath(final Context context, final Uri uri) {
29 |
30 | final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
31 |
32 | // DocumentProvider
33 | if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
34 | // ExternalStorageProvider
35 | if (isExternalStorageDocument(uri)) {
36 | final String docId = DocumentsContract.getDocumentId(uri);
37 | final String[] split = docId.split(":");
38 | final String type = split[0];
39 |
40 | if ("primary".equalsIgnoreCase(type)) {
41 | return Environment.getExternalStorageDirectory() + "/" + split[1];
42 | }
43 |
44 | // TODO handle non-primary volumes
45 | }
46 | // DownloadsProvider
47 | else if (isDownloadsDocument(uri)) {
48 |
49 | final String id = DocumentsContract.getDocumentId(uri);
50 | final Uri contentUri = ContentUris.withAppendedId(
51 | Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
52 |
53 | return getDataColumn(context, contentUri, null, null);
54 | }
55 | // MediaProvider
56 | else if (isMediaDocument(uri)) {
57 | final String docId = DocumentsContract.getDocumentId(uri);
58 | final String[] split = docId.split(":");
59 | final String type = split[0];
60 |
61 | Uri contentUri = null;
62 | if ("image".equals(type)) {
63 | contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
64 | } else if ("video".equals(type)) {
65 | contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
66 | } else if ("audio".equals(type)) {
67 | contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
68 | }
69 |
70 | final String selection = "_id=?";
71 | final String[] selectionArgs = new String[] {
72 | split[1]
73 | };
74 |
75 | return getDataColumn(context, contentUri, selection, selectionArgs);
76 | }
77 | }
78 | // MediaStore (and general)
79 | else if ("content".equalsIgnoreCase(uri.getScheme())) {
80 | return getDataColumn(context, uri, null, null);
81 | }
82 | // File
83 | else if ("file".equalsIgnoreCase(uri.getScheme())) {
84 | return uri.getPath();
85 | }
86 |
87 | return null;
88 | }
89 |
90 | /**
91 | * Get the value of the data column for this Uri. This is useful for
92 | * MediaStore Uris, and other file-based ContentProviders.
93 | *
94 | * @param context The context.
95 | * @param uri The Uri to query.
96 | * @param selection (Optional) Filter used in the query.
97 | * @param selectionArgs (Optional) Selection arguments used in the query.
98 | * @return The value of the _data column, which is typically a file path.
99 | */
100 | public static String getDataColumn(Context context, Uri uri, String selection,
101 | String[] selectionArgs) {
102 |
103 | Cursor cursor = null;
104 | final String column = "_data";
105 | final String[] projection = {
106 | column
107 | };
108 |
109 | try {
110 | cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
111 | null);
112 | if (cursor != null && cursor.moveToFirst()) {
113 | final int column_index = cursor.getColumnIndexOrThrow(column);
114 | return cursor.getString(column_index);
115 | }
116 | } finally {
117 | if (cursor != null)
118 | cursor.close();
119 | }
120 | return null;
121 | }
122 |
123 |
124 | /**
125 | * @param uri The Uri to check.
126 | * @return Whether the Uri authority is ExternalStorageProvider.
127 | */
128 | public static boolean isExternalStorageDocument(Uri uri) {
129 | return "com.android.externalstorage.documents".equals(uri.getAuthority());
130 | }
131 |
132 | /**
133 | * @param uri The Uri to check.
134 | * @return Whether the Uri authority is DownloadsProvider.
135 | */
136 | public static boolean isDownloadsDocument(Uri uri) {
137 | return "com.android.providers.downloads.documents".equals(uri.getAuthority());
138 | }
139 |
140 | /**
141 | * @param uri The Uri to check.
142 | * @return Whether the Uri authority is MediaProvider.
143 | */
144 | public static boolean isMediaDocument(Uri uri) {
145 | return "com.android.providers.media.documents".equals(uri.getAuthority());
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/richeditorlibary/src/main/java/com/pppcar/richeditorlibary/view/DataImageView.java:
--------------------------------------------------------------------------------
1 | package com.pppcar.richeditorlibary.view;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.graphics.drawable.Drawable;
6 | import android.util.AttributeSet;
7 | import android.widget.ImageView;
8 |
9 | /**
10 | * 作者: Logan on 2017/11/30.
11 | * 邮箱: 490636907@qq.com
12 | * 描述: 带有数据的imageView
13 | */
14 |
15 | public class DataImageView extends ImageView {
16 | private String absolutePath;
17 | private Bitmap bitmap;
18 |
19 | public DataImageView(Context context) {
20 | this(context, null);
21 | }
22 |
23 | public DataImageView(Context context, AttributeSet attrs) {
24 | this(context, attrs, 0);
25 | }
26 |
27 | public DataImageView(Context context, AttributeSet attrs, int defStyle) {
28 | super(context, attrs, defStyle);
29 | }
30 |
31 | public String getAbsolutePath() {
32 | return absolutePath;
33 | }
34 |
35 | public void setAbsolutePath(String absolutePath) {
36 | this.absolutePath = absolutePath;
37 | }
38 |
39 | public Bitmap getBitmap() {
40 | return bitmap;
41 | }
42 |
43 | public void setBitmap(Bitmap bitmap) {
44 | this.bitmap = bitmap;
45 | }
46 |
47 |
48 | @Override
49 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
50 | Drawable d = getDrawable();
51 |
52 | if(d!=null){
53 | // ceil not round - avoid thin vertical gaps along the left/right edges
54 | int width = MeasureSpec.getSize(widthMeasureSpec);
55 | //高度根据使得图片的宽度充满屏幕计算而得
56 | int height = (int) Math.ceil((float) width * (float) d.getIntrinsicHeight() / (float) d.getIntrinsicWidth());
57 | setMeasuredDimension(width, height);
58 | }else{
59 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/richeditorlibary/src/main/java/com/pppcar/richeditorlibary/view/DataVideoView.java:
--------------------------------------------------------------------------------
1 | package com.pppcar.richeditorlibary.view;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 |
6 | import cn.jzvd.JzvdStd;
7 |
8 | /**
9 | * 作者: Logan on 2017/11/30.
10 | * 邮箱: 490636907@qq.com
11 | * 描述: 带数据的视频播放器
12 | */
13 |
14 | public class DataVideoView extends JzvdStd {
15 | private String absolutePath;
16 | private String url;
17 |
18 | public DataVideoView(Context context) {
19 | super(context);
20 | }
21 |
22 | public DataVideoView(Context context, AttributeSet attrs) {
23 | super(context, attrs);
24 | }
25 |
26 |
27 | public String getAbsolutePath() {
28 | return absolutePath;
29 | }
30 |
31 | public void setAbsolutePath(String absolutePath) {
32 | this.absolutePath = absolutePath;
33 | }
34 |
35 | public String getUrl() {
36 | return url;
37 | }
38 |
39 | public void setUrl(String url) {
40 | this.url = url;
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/richeditorlibary/src/main/java/com/pppcar/richeditorlibary/view/DeletableEditText.java:
--------------------------------------------------------------------------------
1 | package com.pppcar.richeditorlibary.view;
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 | public class DeletableEditText extends EditText {
17 |
18 | public DeletableEditText(Context context, AttributeSet attrs, int defStyle) {
19 | super(context, attrs, defStyle);
20 | }
21 |
22 | public DeletableEditText(Context context, AttributeSet attrs) {
23 | super(context, attrs);
24 | }
25 |
26 | public DeletableEditText(Context context) {
27 | super(context);
28 | }
29 |
30 | @Override
31 | public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
32 | return new DeleteInputConnection(super.onCreateInputConnection(outAttrs),
33 | true);
34 | }
35 |
36 | private class DeleteInputConnection extends InputConnectionWrapper {
37 |
38 | public DeleteInputConnection(InputConnection target, boolean mutable) {
39 | super(target, mutable);
40 | }
41 |
42 | @Override
43 | public boolean sendKeyEvent(KeyEvent event) {
44 | return super.sendKeyEvent(event);
45 | }
46 |
47 | @Override
48 | public boolean deleteSurroundingText(int beforeLength, int afterLength) {
49 | if (beforeLength == 1 && afterLength == 0) {
50 | return sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN,
51 | KeyEvent.KEYCODE_DEL))
52 | && sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP,
53 | KeyEvent.KEYCODE_DEL));
54 | }
55 |
56 | return super.deleteSurroundingText(beforeLength, afterLength);
57 | }
58 |
59 | }
60 | }
--------------------------------------------------------------------------------
/richeditorlibary/src/main/java/com/pppcar/richeditorlibary/view/DragLinearLayout.java:
--------------------------------------------------------------------------------
1 | package com.pppcar.richeditorlibary.view;
2 |
3 | import android.animation.Animator;
4 | import android.animation.AnimatorListenerAdapter;
5 | import android.animation.LayoutTransition;
6 | import android.animation.ObjectAnimator;
7 | import android.animation.ValueAnimator;
8 | import android.content.Context;
9 | import android.content.res.Resources;
10 | import android.content.res.TypedArray;
11 | import android.graphics.Bitmap;
12 | import android.graphics.Canvas;
13 | import android.graphics.Rect;
14 | import android.graphics.drawable.BitmapDrawable;
15 | import android.graphics.drawable.Drawable;
16 | import androidx.annotation.NonNull;
17 | import androidx.core.content.ContextCompat;
18 | import androidx.core.view.MotionEventCompat;
19 | import android.util.AttributeSet;
20 | import android.util.Log;
21 | import android.util.SparseArray;
22 | import android.view.MotionEvent;
23 | import android.view.View;
24 | import android.view.ViewConfiguration;
25 | import android.view.ViewGroup;
26 | import android.view.ViewTreeObserver;
27 | import android.widget.LinearLayout;
28 | import android.widget.ScrollView;
29 |
30 | import com.pppcar.richeditorlibary.R;
31 |
32 | /**
33 | * 作者: Logan on 2017/11/30.
34 | * 邮箱: 490636907@qq.com
35 | * 描述: 控件可拖拽的线性布局
36 | */
37 |
38 | public class DragLinearLayout extends LinearLayout {
39 | private static final String LOG_TAG = DragLinearLayout.class.getSimpleName();
40 | private static final long NOMINAL_SWITCH_DURATION = 150;
41 | private static final long MIN_SWITCH_DURATION = NOMINAL_SWITCH_DURATION;
42 | private static final long MAX_SWITCH_DURATION = NOMINAL_SWITCH_DURATION * 2;
43 | private static final float NOMINAL_DISTANCE = 20;
44 | private final float nominalDistanceScaled;
45 | private View handleView;
46 | public interface OnViewSwapListener {
47 | /**
48 | * Invoked right before the two items are swapped due to a drag event.
49 | * After the swap, the firstView will be in the secondPosition, and vice versa.
50 | *
51 | * No guarantee is made as to which of the two has a lesser/greater position.
52 | */
53 | void onSwap(View firstView, int firstPosition, View secondView, int secondPosition);
54 | }
55 |
56 | private OnViewSwapListener swapListener;
57 |
58 | private LayoutTransition layoutTransition;
59 |
60 | /**
61 | * Mapping from child index to drag-related info container.
62 | * Presence of mapping implies the child can be dragged, and is considered for swaps with the
63 | * currently dragged item.
64 | */
65 | private final SparseArray draggableChildren;
66 |
67 | private class DraggableChild {
68 | /**
69 | * If non-null, a reference to an on-going position animation.
70 | */
71 | private ValueAnimator swapAnimation;
72 |
73 | public void endExistingAnimation() {
74 | if (null != swapAnimation) swapAnimation.end();
75 | }
76 |
77 | public void cancelExistingAnimation() {
78 | if (null != swapAnimation) swapAnimation.cancel();
79 | }
80 | }
81 |
82 | /**
83 | * Holds state information about the currently dragged item.
84 | *
85 | * Rough lifecycle:
86 | * #startDetectingOnPossibleDrag - #detecting == true
87 | * if drag is recognised, #onDragStart - #dragging == true
88 | * if drag ends, #onDragStop - #dragging == false, #settling == true
89 | * if gesture ends without drag, or settling finishes, #stopDetecting - #detecting == false
90 | */
91 | private class DragItem {
92 | private View view;
93 | private int startVisibility;
94 | private BitmapDrawable viewDrawable;
95 | private int position;
96 | private int startTop;
97 | private int height;
98 | private int totalDragOffset;
99 | private int targetTopOffset;
100 | private ValueAnimator settleAnimation;
101 |
102 | private boolean detecting;
103 | private boolean dragging;
104 |
105 | public DragItem() {
106 | stopDetecting();
107 | }
108 |
109 | public void startDetectingOnPossibleDrag(final View view, final int position) {
110 | this.view = view;
111 | this.startVisibility = view.getVisibility();
112 | this.viewDrawable = getDragDrawable(view);
113 | this.position = position;
114 | this.startTop = view.getTop();
115 | this.height = view.getHeight();
116 | this.totalDragOffset = 0;
117 | this.targetTopOffset = 0;
118 | this.settleAnimation = null;
119 |
120 | this.detecting = true;
121 | }
122 |
123 | public void onDragStart() {
124 | view.setVisibility(View.INVISIBLE);
125 | this.dragging = true;
126 | }
127 |
128 | public void setTotalOffset(int offset) {
129 | totalDragOffset = offset;
130 | updateTargetTop();
131 | }
132 |
133 | public void updateTargetTop() {
134 | targetTopOffset = startTop - view.getTop() + totalDragOffset;
135 | }
136 |
137 | public void onDragStop() {
138 | this.dragging = false;
139 | }
140 |
141 | public boolean settling() {
142 | return null != settleAnimation;
143 | }
144 |
145 | public void stopDetecting() {
146 | this.detecting = false;
147 | if (null != view) view.setVisibility(startVisibility);
148 | view = null;
149 | startVisibility = -1;
150 | viewDrawable = null;
151 | position = -1;
152 | startTop = -1;
153 | height = -1;
154 | totalDragOffset = 0;
155 | targetTopOffset = 0;
156 | if (null != settleAnimation) settleAnimation.end();
157 | settleAnimation = null;
158 | }
159 | }
160 |
161 |
162 | private final DragItem draggedItem;
163 | private final int slop;
164 |
165 | private static final int INVALID_POINTER_ID = -1;
166 | private int downY = -1;
167 | private int activePointerId = INVALID_POINTER_ID;
168 |
169 | /**
170 | * The shadow to be drawn above the {@link #draggedItem}.
171 | */
172 | private final Drawable dragTopShadowDrawable;
173 | /**
174 | * The shadow to be drawn below the {@link #draggedItem}.
175 | */
176 | private final Drawable dragBottomShadowDrawable;
177 | private final int dragShadowHeight;
178 |
179 | /**
180 | * See {@link #setContainerScrollView(android.widget.ScrollView)}.
181 | */
182 | private ScrollView containerScrollView;
183 | private int scrollSensitiveAreaHeight;
184 | private static final int DEFAULT_SCROLL_SENSITIVE_AREA_HEIGHT_DP = 48;
185 | private static final int MAX_DRAG_SCROLL_SPEED = 16;
186 |
187 | public DragLinearLayout(Context context) {
188 | this(context, null);
189 | }
190 |
191 | public DragLinearLayout(Context context, AttributeSet attrs) {
192 | super(context, attrs);
193 | handleView=new View(context);
194 | setOrientation(LinearLayout.VERTICAL);
195 |
196 | draggableChildren = new SparseArray<>();
197 |
198 | draggedItem = new DragItem();
199 | ViewConfiguration vc = ViewConfiguration.get(context);
200 | slop = vc.getScaledTouchSlop();
201 |
202 | final Resources resources = getResources();
203 | dragTopShadowDrawable = ContextCompat.getDrawable(context, R.drawable.ab_solid_shadow_holo_flipped);
204 | dragBottomShadowDrawable = ContextCompat.getDrawable(context, R.drawable.ab_solid_shadow_holo);
205 | dragShadowHeight = resources.getDimensionPixelSize(R.dimen.downwards_drop_shadow_height);
206 |
207 | TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.DragLinearLayout, 0, 0);
208 | try {
209 | scrollSensitiveAreaHeight = a.getDimensionPixelSize(R.styleable.DragLinearLayout_scrollSensitiveHeight,
210 | (int) (DEFAULT_SCROLL_SENSITIVE_AREA_HEIGHT_DP * resources.getDisplayMetrics().density + 0.5f));
211 | } finally {
212 | a.recycle();
213 | }
214 |
215 | nominalDistanceScaled = (int) (NOMINAL_DISTANCE * resources.getDisplayMetrics().density + 0.5f);
216 | }
217 |
218 | @Override
219 | public void setOrientation(int orientation) {
220 | // enforce VERTICAL orientation; remove if HORIZONTAL support is ever added
221 | if (LinearLayout.HORIZONTAL == orientation) {
222 | throw new IllegalArgumentException("DragLinearLayout must be VERTICAL.");
223 | }
224 | super.setOrientation(orientation);
225 | }
226 |
227 | /**
228 | * Calls {@link #addView(android.view.View)} followed by {@link #setViewDraggable(android.view.View, android.view.View)}.
229 | */
230 | public void addDragView(View child, View dragHandle) {
231 | addView(child);
232 | setViewDraggable(child, dragHandle);
233 | }
234 |
235 | public void addDragView(View child) {
236 | addView(child);
237 | setViewDraggable(child, handleView);
238 | }
239 |
240 | public void addDragView(View child, ViewGroup.LayoutParams params) {
241 | addView(child,params);
242 | setViewDraggable(child, handleView);
243 | }
244 |
245 | /**
246 | * Calls {@link #addView(android.view.View, int)} followed by
247 | * {@link #setViewDraggable(android.view.View, android.view.View)} and correctly updates the
248 | * drag-ability state of all existing views.
249 | */
250 | public void addDragView(View child, View dragHandle, int index) {
251 | addView(child, index);
252 |
253 | // update drag-able children mappings
254 | final int numMappings = draggableChildren.size();
255 | for (int i = numMappings - 1; i >= 0; i--) {
256 | final int key = draggableChildren.keyAt(i);
257 | if (key >= index) {
258 | draggableChildren.put(key + 1, draggableChildren.get(key));
259 | }
260 | }
261 |
262 | setViewDraggable(child, dragHandle);
263 | }
264 |
265 | public void addDragView(View child, int index) {
266 | addView(child, index);
267 |
268 | // update drag-able children mappings
269 | final int numMappings = draggableChildren.size();
270 | for (int i = numMappings - 1; i >= 0; i--) {
271 | final int key = draggableChildren.keyAt(i);
272 | if (key >= index) {
273 | draggableChildren.put(key + 1, draggableChildren.get(key));
274 | }
275 | }
276 |
277 | setViewDraggable(child, handleView);
278 | }
279 |
280 |
281 |
282 |
283 | /**
284 | * Makes the child a candidate for dragging. Must be an existing child of this layout.
285 | */
286 | public void setViewDraggable(View child, View dragHandle) {
287 | if (null == child || null == dragHandle) {
288 | throw new IllegalArgumentException(
289 | "Draggable children and their drag handles must not be null.");
290 | }
291 |
292 | if (this == child.getParent()) {
293 | dragHandle.setOnTouchListener(new DragHandleOnTouchListener(child));
294 | draggableChildren.put(indexOfChild(child), new DraggableChild());
295 | } else {
296 | Log.e(LOG_TAG, child + " is not a child, cannot make draggable.");
297 | }
298 | }
299 |
300 | /**
301 | * Calls {@link #removeView(android.view.View)} and correctly updates the drag-ability state of
302 | * all remaining views.
303 | */
304 | @SuppressWarnings("UnusedDeclaration")
305 | public void removeDragView(View child) {
306 | if (this == child.getParent()) {
307 | final int index = indexOfChild(child);
308 | removeView(child);
309 |
310 | // update drag-able children mappings
311 | final int mappings = draggableChildren.size();
312 | for (int i = 0; i < mappings; i++) {
313 | final int key = draggableChildren.keyAt(i);
314 | if (key >= index) {
315 | DraggableChild next = draggableChildren.get(key + 1);
316 | if (null == next) {
317 | draggableChildren.delete(key);
318 | } else {
319 | draggableChildren.put(key, next);
320 | }
321 | }
322 | }
323 | }
324 | }
325 |
326 | @Override
327 | public void removeAllViews() {
328 | super.removeAllViews();
329 | draggableChildren.clear();
330 | }
331 |
332 | /**
333 | * If this layout is within a {@link android.widget.ScrollView}, register it here so that it
334 | * can be scrolled during item drags.
335 | */
336 | public void setContainerScrollView(ScrollView scrollView) {
337 | this.containerScrollView = scrollView;
338 | }
339 |
340 | /**
341 | * Sets the height from upper / lower edge at which a container {@link android.widget.ScrollView},
342 | * if one is registered via {@link #setContainerScrollView(android.widget.ScrollView)},
343 | * is scrolled.
344 | */
345 | @SuppressWarnings("UnusedDeclaration")
346 | public void setScrollSensitiveHeight(int height) {
347 | this.scrollSensitiveAreaHeight = height;
348 | }
349 |
350 | @SuppressWarnings("UnusedDeclaration")
351 | public int getScrollSensitiveHeight() {
352 | return scrollSensitiveAreaHeight;
353 | }
354 |
355 |
356 | public void setOnViewSwapListener(OnViewSwapListener swapListener) {
357 | this.swapListener = swapListener;
358 | }
359 |
360 | /**
361 | * A linear relationship b/w distance and duration, bounded.
362 | */
363 | private long getTranslateAnimationDuration(float distance) {
364 | return Math.min(MAX_SWITCH_DURATION, Math.max(MIN_SWITCH_DURATION,
365 | (long) (NOMINAL_SWITCH_DURATION * Math.abs(distance) / nominalDistanceScaled)));
366 | }
367 |
368 |
369 | private void startDetectingDrag(View child) {
370 |
371 | if (draggedItem.detecting)
372 | return; // existing drag in process, only one at a time is allowed
373 |
374 | final int position = indexOfChild(child);
375 | Log.e("position",position+"-----------------------------------------------");
376 |
377 | // complete any existing animations, both for the newly selected child and the previous dragged one
378 |
379 | draggableChildren.get(position).endExistingAnimation();
380 |
381 | draggedItem.startDetectingOnPossibleDrag(child, position);
382 | if (containerScrollView != null) {
383 | containerScrollView.requestDisallowInterceptTouchEvent(true);
384 | }
385 | }
386 |
387 | private void startDrag() {
388 | // remove layout transition, it conflicts with drag animation
389 | // we will restore it after drag animation end, see onDragStop()
390 | layoutTransition = getLayoutTransition();
391 | if (layoutTransition != null) {
392 | setLayoutTransition(null);
393 | }
394 |
395 | draggedItem.onDragStart();
396 | requestDisallowInterceptTouchEvent(true);
397 | }
398 |
399 | /**
400 | * Animates the dragged item to its final resting position.
401 | */
402 | private void onDragStop() {
403 | draggedItem.settleAnimation = ValueAnimator.ofFloat(draggedItem.totalDragOffset,
404 | draggedItem.totalDragOffset - draggedItem.targetTopOffset)
405 | .setDuration(getTranslateAnimationDuration(draggedItem.targetTopOffset));
406 | draggedItem.settleAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
407 | @Override
408 | public void onAnimationUpdate(ValueAnimator animation) {
409 | if (!draggedItem.detecting) return; // already stopped
410 |
411 | draggedItem.setTotalOffset(((Float) animation.getAnimatedValue()).intValue());
412 |
413 | final int shadowAlpha = (int) ((1 - animation.getAnimatedFraction()) * 255);
414 | if (null != dragTopShadowDrawable) dragTopShadowDrawable.setAlpha(shadowAlpha);
415 | dragBottomShadowDrawable.setAlpha(shadowAlpha);
416 | invalidate();
417 | }
418 | });
419 | draggedItem.settleAnimation.addListener(new AnimatorListenerAdapter() {
420 | @Override
421 | public void onAnimationStart(Animator animation) {
422 | draggedItem.onDragStop();
423 | }
424 |
425 | @Override
426 | public void onAnimationEnd(Animator animation) {
427 | if (!draggedItem.detecting) {
428 | return; // already stopped
429 | }
430 |
431 | draggedItem.settleAnimation = null;
432 | draggedItem.stopDetecting();
433 |
434 | if (null != dragTopShadowDrawable) dragTopShadowDrawable.setAlpha(255);
435 | dragBottomShadowDrawable.setAlpha(255);
436 |
437 | // restore layout transition
438 | if (layoutTransition != null && getLayoutTransition() == null) {
439 | setLayoutTransition(layoutTransition);
440 | }
441 | }
442 | });
443 | draggedItem.settleAnimation.start();
444 | }
445 |
446 | /**
447 | * Updates the dragged item with the given total offset from its starting position.
448 | * Evaluates and executes draggable view swaps.
449 | */
450 | private void onDrag(final int offset) {
451 | draggedItem.setTotalOffset(offset);
452 | invalidate();
453 |
454 | int currentTop = draggedItem.startTop + draggedItem.totalDragOffset;
455 |
456 | handleContainerScroll(currentTop);
457 |
458 | int belowPosition = nextDraggablePosition(draggedItem.position);
459 | int abovePosition = previousDraggablePosition(draggedItem.position);
460 |
461 | View belowView = getChildAt(belowPosition);
462 | View aboveView = getChildAt(abovePosition);
463 |
464 | final boolean isBelow = (belowView != null) &&
465 | (currentTop + draggedItem.height > belowView.getTop() + belowView.getHeight() / 2);
466 | final boolean isAbove = (aboveView != null) &&
467 | (currentTop < aboveView.getTop() + aboveView.getHeight() / 2);
468 |
469 | if (isBelow || isAbove) {
470 | final View switchView = isBelow ? belowView : aboveView;
471 |
472 | // swap elements
473 | final int originalPosition = draggedItem.position;
474 | final int switchPosition = isBelow ? belowPosition : abovePosition;
475 |
476 | draggableChildren.get(switchPosition).cancelExistingAnimation();
477 | final float switchViewStartY = switchView.getY();
478 |
479 | if (null != swapListener) {
480 | swapListener.onSwap(draggedItem.view, draggedItem.position, switchView, switchPosition);
481 | }
482 |
483 | if (isBelow) {
484 | removeViewAt(originalPosition);
485 | removeViewAt(switchPosition - 1);
486 |
487 | addView(belowView, originalPosition);
488 | addView(draggedItem.view, switchPosition);
489 | } else {
490 | removeViewAt(switchPosition);
491 | removeViewAt(originalPosition - 1);
492 |
493 | addView(draggedItem.view, switchPosition);
494 | addView(aboveView, originalPosition);
495 | }
496 | draggedItem.position = switchPosition;
497 |
498 | final ViewTreeObserver switchViewObserver = switchView.getViewTreeObserver();
499 | switchViewObserver.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
500 | @Override
501 | public boolean onPreDraw() {
502 | switchViewObserver.removeOnPreDrawListener(this);
503 |
504 | final ObjectAnimator switchAnimator = ObjectAnimator.ofFloat(switchView, "y",
505 | switchViewStartY, switchView.getTop())
506 | .setDuration(getTranslateAnimationDuration(switchView.getTop() - switchViewStartY));
507 | switchAnimator.addListener(new AnimatorListenerAdapter() {
508 | @Override
509 | public void onAnimationStart(Animator animation) {
510 | draggableChildren.get(originalPosition).swapAnimation = switchAnimator;
511 | }
512 |
513 | @Override
514 | public void onAnimationEnd(Animator animation) {
515 | draggableChildren.get(originalPosition).swapAnimation = null;
516 | }
517 | });
518 | switchAnimator.start();
519 |
520 | return true;
521 | }
522 | });
523 |
524 | final ViewTreeObserver observer = draggedItem.view.getViewTreeObserver();
525 | observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
526 | @Override
527 | public boolean onPreDraw() {
528 | observer.removeOnPreDrawListener(this);
529 | draggedItem.updateTargetTop();
530 |
531 | // TODO test if still necessary..
532 | // because draggedItem#view#getTop() is only up-to-date NOW
533 | // (and not right after the #addView() swaps above)
534 | // we may need to update an ongoing settle animation
535 | if (draggedItem.settling()) {
536 | Log.d(LOG_TAG, "Updating settle animation");
537 | draggedItem.settleAnimation.removeAllListeners();
538 | draggedItem.settleAnimation.cancel();
539 | onDragStop();
540 | }
541 | return true;
542 | }
543 | });
544 | }
545 | }
546 |
547 | private int previousDraggablePosition(int position) {
548 | int startIndex = draggableChildren.indexOfKey(position);
549 | if (startIndex < 1 || startIndex > draggableChildren.size()) return -1;
550 | return draggableChildren.keyAt(startIndex - 1);
551 | }
552 |
553 | private int nextDraggablePosition(int position) {
554 | int startIndex = draggableChildren.indexOfKey(position);
555 | if (startIndex < -1 || startIndex > draggableChildren.size() - 2) return -1;
556 | return draggableChildren.keyAt(startIndex + 1);
557 | }
558 |
559 | private Runnable dragUpdater;
560 |
561 | private void handleContainerScroll(final int currentTop) {
562 | if (null != containerScrollView) {
563 | final int startScrollY = containerScrollView.getScrollY();
564 | final int absTop = getTop() - startScrollY + currentTop;
565 | final int height = containerScrollView.getHeight();
566 |
567 | final int delta;
568 |
569 | if (absTop < scrollSensitiveAreaHeight) {
570 | delta = (int) (-MAX_DRAG_SCROLL_SPEED * smootherStep(scrollSensitiveAreaHeight, 0, absTop));
571 | } else if (absTop > height - scrollSensitiveAreaHeight) {
572 | delta = (int) (MAX_DRAG_SCROLL_SPEED * smootherStep(height - scrollSensitiveAreaHeight, height, absTop));
573 | } else {
574 | delta = 0;
575 | }
576 |
577 | containerScrollView.removeCallbacks(dragUpdater);
578 | containerScrollView.smoothScrollBy(0, delta);
579 | dragUpdater = new Runnable() {
580 | @Override
581 | public void run() {
582 | if (draggedItem.dragging && startScrollY != containerScrollView.getScrollY()) {
583 | onDrag(draggedItem.totalDragOffset + delta);
584 | }
585 | }
586 | };
587 | containerScrollView.post(dragUpdater);
588 | }
589 | }
590 |
591 | /**
592 | * By Ken Perlin. See Smoothstep - Wikipedia.
593 | */
594 | private static float smootherStep(float edge1, float edge2, float val) {
595 | val = Math.max(0, Math.min((val - edge1) / (edge2 - edge1), 1));
596 | return val * val * val * (val * (val * 6 - 15) + 10);
597 | }
598 |
599 | @Override
600 | protected void dispatchDraw(@NonNull Canvas canvas) {
601 | super.dispatchDraw(canvas);
602 |
603 | if (draggedItem.detecting && (draggedItem.dragging || draggedItem.settling())) {
604 | canvas.save();
605 | canvas.translate(0, draggedItem.totalDragOffset);
606 | draggedItem.viewDrawable.draw(canvas);
607 |
608 | final int left = draggedItem.viewDrawable.getBounds().left;
609 | final int right = draggedItem.viewDrawable.getBounds().right;
610 | final int top = draggedItem.viewDrawable.getBounds().top;
611 | final int bottom = draggedItem.viewDrawable.getBounds().bottom;
612 |
613 | dragBottomShadowDrawable.setBounds(left, bottom, right, bottom + dragShadowHeight);
614 | dragBottomShadowDrawable.draw(canvas);
615 |
616 | if (null != dragTopShadowDrawable) {
617 | dragTopShadowDrawable.setBounds(left, top - dragShadowHeight, right, top);
618 | dragTopShadowDrawable.draw(canvas);
619 | }
620 |
621 | canvas.restore();
622 | }
623 | }
624 |
625 | /*
626 | * Note regarding touch handling:
627 | * In general, we have three cases -
628 | * 1) User taps outside any children.
629 | * #onInterceptTouchEvent receives DOWN
630 | * #onTouchEvent receives DOWN
631 | * draggedItem.detecting == false, we return false and no further events are received
632 | * 2) User taps on non-interactive drag handle / child, e.g. TextView or ImageView.
633 | * #onInterceptTouchEvent receives DOWN
634 | * DragHandleOnTouchListener (attached to each draggable child) #onTouch receives DOWN
635 | * #startDetectingDrag is called, draggedItem is now detecting
636 | * view does not handle touch, so our #onTouchEvent receives DOWN
637 | * draggedItem.detecting == true, we #startDrag() and proceed to handle the drag
638 | * 3) User taps on interactive drag handle / child, e.g. Button.
639 | * #onInterceptTouchEvent receives DOWN
640 | * DragHandleOnTouchListener (attached to each draggable child) #onTouch receives DOWN
641 | * #startDetectingDrag is called, draggedItem is now detecting
642 | * view handles touch, so our #onTouchEvent is not called yet
643 | * #onInterceptTouchEvent receives ACTION_MOVE
644 | * if dy > touch slop, we assume user wants to drag and intercept the event
645 | * #onTouchEvent receives further ACTION_MOVE events, proceed to handle the drag
646 | *
647 | * For cases 2) and 3), lifting the active pointer at any point in the sequence of events
648 | * triggers #onTouchEnd and the draggedItem, if detecting, is #stopDetecting.
649 | */
650 |
651 | @Override
652 | public boolean onInterceptTouchEvent(MotionEvent event) {
653 | switch (MotionEventCompat.getActionMasked(event)) {
654 | case MotionEvent.ACTION_DOWN: {
655 | if (draggedItem.detecting) return false; // an existing item is (likely) settling
656 |
657 | /*if ( draggedItem.view instanceof RelativeLayout) {
658 | if ("image".equals( draggedItem.view.getTag(R.id.richEditor))) {
659 | //继续操作
660 | }else {
661 | return true;
662 | }
663 | }else if(draggedItem.view instanceof EditText){
664 | return true;
665 | }*/
666 | downY = (int) MotionEventCompat.getY(event, 0);
667 | activePointerId = MotionEventCompat.getPointerId(event, 0);
668 | break;
669 | }
670 | case MotionEvent.ACTION_MOVE: {
671 | if (!draggedItem.detecting) return false;
672 | if (INVALID_POINTER_ID == activePointerId) break;
673 | final int pointerIndex = event.findPointerIndex(activePointerId);
674 | final float y = MotionEventCompat.getY(event, pointerIndex);
675 | final float dy = y - downY;
676 | if (Math.abs(dy) > slop) {
677 | startDrag();
678 | return true;
679 | }
680 | return false;
681 | }
682 | case MotionEvent.ACTION_POINTER_UP: {
683 | final int pointerIndex = MotionEventCompat.getActionIndex(event);
684 | final int pointerId = MotionEventCompat.getPointerId(event, pointerIndex);
685 |
686 | if (pointerId != activePointerId)
687 | break; // if active pointer, fall through and cancel!
688 | }
689 | case MotionEvent.ACTION_CANCEL:
690 | case MotionEvent.ACTION_UP: {
691 | onTouchEnd();
692 |
693 | if (draggedItem.detecting) draggedItem.stopDetecting();
694 | break;
695 | }
696 | }
697 |
698 | return false;
699 | }
700 |
701 | @Override
702 | public boolean onTouchEvent(@NonNull MotionEvent event) {
703 | switch (MotionEventCompat.getActionMasked(event)) {
704 | case MotionEvent.ACTION_DOWN: {
705 | if (!draggedItem.detecting || draggedItem.settling()) return false;
706 | startDrag();
707 | return true;
708 | }
709 | case MotionEvent.ACTION_MOVE: {
710 | if (!draggedItem.dragging) break;
711 | if (INVALID_POINTER_ID == activePointerId) break;
712 |
713 | int pointerIndex = event.findPointerIndex(activePointerId);
714 | int lastEventY = (int) MotionEventCompat.getY(event, pointerIndex);
715 | int deltaY = lastEventY - downY;
716 |
717 | onDrag(deltaY);
718 | return true;
719 | }
720 | case MotionEvent.ACTION_POINTER_UP: {
721 | final int pointerIndex = MotionEventCompat.getActionIndex(event);
722 | final int pointerId = MotionEventCompat.getPointerId(event, pointerIndex);
723 |
724 | if (pointerId != activePointerId)
725 | break; // if active pointer, fall through and cancel!
726 | }
727 | case MotionEvent.ACTION_CANCEL:
728 | case MotionEvent.ACTION_UP: {
729 | onTouchEnd();
730 |
731 | if (draggedItem.dragging) {
732 | onDragStop();
733 | } else if (draggedItem.detecting) {
734 | draggedItem.stopDetecting();
735 | }
736 | return true;
737 | }
738 | }
739 | return false;
740 | }
741 |
742 | private void onTouchEnd() {
743 | downY = -1;
744 | activePointerId = INVALID_POINTER_ID;
745 | }
746 |
747 | private class DragHandleOnTouchListener implements OnTouchListener {
748 | private final View view;
749 |
750 | public DragHandleOnTouchListener(final View view) {
751 | this.view = view;
752 | }
753 |
754 | @Override
755 | public boolean onTouch(View v, MotionEvent event) {
756 | if (MotionEvent.ACTION_DOWN == MotionEventCompat.getActionMasked(event)) {
757 | startDetectingDrag(view);
758 | }
759 | return false;
760 | }
761 | }
762 |
763 | private BitmapDrawable getDragDrawable(View view) {
764 | int top = view.getTop();
765 | int left = view.getLeft();
766 |
767 | Bitmap bitmap = getBitmapFromView(view);
768 |
769 | BitmapDrawable drawable = new BitmapDrawable(getResources(), bitmap);
770 |
771 | drawable.setBounds(new Rect(left, top, left + view.getWidth(), top + view.getHeight()));
772 |
773 | return drawable;
774 | }
775 |
776 | /**
777 | * @return a bitmap showing a screenshot of the view passed in.
778 | */
779 | private static Bitmap getBitmapFromView(View view) {
780 | Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
781 | Canvas canvas = new Canvas(bitmap);
782 | view.draw(canvas);
783 | return bitmap;
784 | }
785 | }
786 |
--------------------------------------------------------------------------------
/richeditorlibary/src/main/java/com/pppcar/richeditorlibary/view/RichEditor.java:
--------------------------------------------------------------------------------
1 | package com.pppcar.richeditorlibary.view;
2 |
3 | import android.animation.Animator;
4 | import android.animation.LayoutTransition;
5 | import android.animation.ValueAnimator;
6 | import android.annotation.SuppressLint;
7 | import android.app.Activity;
8 | import android.content.Context;
9 | import android.content.Intent;
10 | import android.graphics.Bitmap;
11 | import android.graphics.BitmapFactory;
12 | import android.text.TextUtils;
13 | import android.util.AttributeSet;
14 | import android.view.KeyEvent;
15 | import android.view.LayoutInflater;
16 | import android.view.View;
17 | import android.view.ViewGroup;
18 | import android.view.inputmethod.InputMethodManager;
19 | import android.widget.EditText;
20 | import android.widget.FrameLayout;
21 | import android.widget.ImageView;
22 | import android.widget.LinearLayout;
23 | import android.widget.RelativeLayout;
24 | import android.widget.ScrollView;
25 |
26 | import com.bumptech.glide.Glide;
27 | import com.bumptech.glide.load.engine.DiskCacheStrategy;
28 | import com.bumptech.glide.request.transition.DrawableCrossFadeFactory;
29 | import com.pppcar.richeditorlibary.R;
30 | import com.pppcar.richeditorlibary.activity.ImageRotateAct;
31 | import com.pppcar.richeditorlibary.utils.SDCardUtil;
32 |
33 | import java.util.ArrayList;
34 | import java.util.List;
35 |
36 | import androidx.annotation.NonNull;
37 | import cn.jzvd.Jzvd;
38 |
39 | import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
40 |
41 | /**
42 | * 作者: Logan on 2017/11/30.
43 | * 邮箱: 490636907@qq.com
44 | * 描述: 可编辑富文本
45 | */
46 | @SuppressLint({"InflateParams","SetTextI18n"})
47 | @SuppressWarnings("unused")
48 | public class RichEditor extends ScrollView {
49 |
50 | private static final int EDIT_PADDING = 10; // edittext常规padding是10dp
51 | private final OnClickListener btnImgClickListener;
52 | private static final int IMG_TEXT = 0;
53 | private static final int VIDEO = 1;
54 | private int viewTagIndex = 1; // 新生的view都会打一个tag,对每个view来说,这个tag是唯一的。
55 | private DragLinearLayout allLayout; // 这个是所有子view的容器,scrollView内部的唯一一个ViewGroup
56 | private LayoutInflater inflater;
57 | private OnKeyListener keyListener; // 所有EditText的软键盘监听器
58 | private OnClickListener btnListener; // 图片右上角红叉按钮监听器
59 | private OnClickListener btnVideoListener; // 视频右上角红叉按钮监听器
60 | private OnFocusChangeListener focusListener; // 所有EditText的焦点监听listener
61 | private EditText lastFocusEdit; // 最近被聚焦的EditText
62 | private int disappearingIndex = 0;
63 | private Context mContext;
64 | private OnClickListener btnRotateListener;
65 | public static final int ROTATE_IMAGE = 101;
66 | public static final int VIDEO_REQUEST = 102;
67 | private boolean isMenuShow;
68 | private int type;
69 |
70 |
71 | public RichEditor(Context context) {
72 | this(context, null);
73 | }
74 |
75 | public RichEditor(Context context, AttributeSet attrs) {
76 | this(context, attrs, 0);
77 | }
78 |
79 | public RichEditor(Context context, AttributeSet attrs, int defStyleAttr) {
80 | super(context, attrs, defStyleAttr);
81 | inflater = LayoutInflater.from(context);
82 | this.mContext = context;
83 | // 1. 初始化allLayout
84 | // allLayout = new LinearLayout(context);
85 | allLayout = new DragLinearLayout(context);
86 | allLayout.setOrientation(LinearLayout.VERTICAL);
87 | allLayout.setContainerScrollView(this);
88 | //子控件拖拽监听
89 | allLayout.setOnViewSwapListener(new DragLinearLayout.OnViewSwapListener() {
90 | @Override
91 | public void onSwap(View firstView, int firstPosition, View secondView, int secondPosition) {
92 | //移除FirstView
93 | allLayout.removeDragView(firstView);
94 | //移除SecondView
95 | if (secondView instanceof RelativeLayout) {
96 | if ("image".equals(secondView.getTag(R.id.richEditor))) {
97 | allLayout.removeDragView(secondView);
98 | } else {
99 | allLayout.removeView(secondView);
100 | }
101 | } else {
102 | allLayout.removeView(secondView);
103 | }
104 | if (firstPosition >= secondPosition) {
105 | //底下的View往上拖,先添加firstView
106 | allLayout.addDragView(firstView, firstView.findViewById(R.id.move), secondPosition);
107 | //添加SecondView
108 | if (secondView instanceof RelativeLayout) {
109 | if ("image".equals(secondView.getTag(R.id.richEditor))) {
110 | allLayout.addDragView(secondView, secondView.findViewById(R.id.move), firstPosition);
111 | } else {
112 | // allLayout.addView(secondView,firstPosition);
113 | //findViewById(R.id.video_move)
114 | // allLayout.addDragView(secondView,firstPosition);
115 | allLayout.addDragView(secondView, secondView.findViewById(R.id.video_move), firstPosition);
116 | }
117 | } else {
118 | // allLayout.addView(secondView,firstPosition);
119 | allLayout.addDragView(secondView, firstPosition);
120 | }
121 | } else {
122 | //上面往底下拖,先添加SecondView
123 | if (secondView instanceof RelativeLayout) {
124 | if ("image".equals(secondView.getTag(R.id.richEditor))) {
125 | allLayout.addDragView(secondView, secondView.findViewById(R.id.move), firstPosition);
126 | } else {
127 | allLayout.addDragView(secondView, secondView.findViewById(R.id.video_move), firstPosition);
128 | }
129 | } else {
130 | allLayout.addDragView(secondView, firstPosition);
131 | }
132 | allLayout.addDragView(firstView, firstView.findViewById(R.id.move), secondPosition);
133 | }
134 |
135 |
136 | }
137 | });
138 | //allLayout.setBackgroundColor(Color.WHITE);
139 | setupLayoutTransitions();
140 | LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,
141 | LayoutParams.WRAP_CONTENT);
142 | allLayout.setPadding(50, 15, 50, 15);//设置间距,防止生成图片时文字太靠边,不能用margin,否则有黑边
143 | addView(allLayout, layoutParams);
144 |
145 | // 2. 初始化键盘退格监听
146 | // 主要用来处理点击回删按钮时,view的一些列合并操作
147 | keyListener = new OnKeyListener() {
148 |
149 | @Override
150 | public boolean onKey(View v, int keyCode, KeyEvent event) {
151 | if (event.getAction() == KeyEvent.ACTION_DOWN
152 | && event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
153 | EditText edit = (EditText) v;
154 | onBackspacePress(edit);
155 | }
156 | return false;
157 | }
158 | };
159 |
160 | // 3. 图片叉掉处理
161 | btnListener = new OnClickListener() {
162 |
163 | @Override
164 | public void onClick(View v) {
165 | FrameLayout view = (FrameLayout) v.getParent();
166 | RelativeLayout parentView = (RelativeLayout) view.getParent();
167 | onImageCloseClick(parentView);
168 | }
169 | };
170 | btnRotateListener = new OnClickListener() {
171 | @Override
172 | public void onClick(View v) {
173 | //图片旋转
174 | FrameLayout view = (FrameLayout) v.getParent();
175 | RelativeLayout parentView = (RelativeLayout) view.getParent();
176 | goToRotateAct(parentView);
177 | }
178 | };
179 | btnImgClickListener = new OnClickListener() {
180 | @Override
181 | public void onClick(View v) {
182 | //点击图片
183 | RelativeLayout parentView = (RelativeLayout) v.getParent();
184 | ImageView openMenu = parentView.findViewById(R.id.iv_open);
185 | ImageView delete = parentView.findViewById(R.id.iv_delete);
186 | ImageView rotate = parentView.findViewById(R.id.iv_rotate);
187 | hideMenu(openMenu, delete, rotate);
188 | goToRotateAct(parentView);
189 | }
190 | };
191 | btnVideoListener = new OnClickListener() {
192 | @Override
193 | public void onClick(View v) {
194 | RelativeLayout parentView = (RelativeLayout) v.getParent();
195 | onVideoCloseClick(parentView);
196 | }
197 | };
198 | focusListener = new OnFocusChangeListener() {
199 |
200 | @Override
201 | public void onFocusChange(View v, boolean hasFocus) {
202 | if (hasFocus) {
203 | lastFocusEdit = (EditText) v;
204 | }
205 | }
206 | };
207 |
208 | LinearLayout.LayoutParams firstEditParam = new LinearLayout.LayoutParams(
209 | LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
210 | //editNormalPadding = dip2px(EDIT_PADDING);
211 | final EditText firstEdit = createEditText("请输入正文", dip2px(context, EDIT_PADDING));
212 | firstEdit.setHintTextColor(getResources().getColor(R.color.main_bg_gray_));
213 | allLayout.addDragView(firstEdit, firstEditParam);
214 | lastFocusEdit = firstEdit;
215 | }
216 |
217 | private void goToRotateAct(RelativeLayout parentView) {
218 | int index = allLayout.indexOfChild(parentView);
219 | DataImageView childAt = (DataImageView) parentView.getChildAt(0);
220 | String path = childAt.getAbsolutePath();
221 | Intent intent = new Intent(mContext, ImageRotateAct.class);
222 | intent.putExtra("imagePath", path);
223 | intent.putExtra("index", index);
224 | ((Activity) mContext).startActivityForResult(intent, ROTATE_IMAGE);
225 | }
226 |
227 | /**
228 | * 初始化transition动画
229 | */
230 | private void setupLayoutTransitions() {
231 | // 只在图片View添加或remove时,触发transition动画
232 | LayoutTransition transitioner = new LayoutTransition();
233 | allLayout.setLayoutTransition(transitioner);
234 | transitioner.addTransitionListener(new LayoutTransition.TransitionListener() {
235 |
236 | @Override
237 | public void startTransition(LayoutTransition transition,
238 | ViewGroup container, View view, int transitionType) {
239 |
240 | }
241 |
242 | @Override
243 | public void endTransition(LayoutTransition transition,
244 | ViewGroup container, View view, int transitionType) {
245 | transition.isRunning();
246 | }
247 | });
248 | transitioner.setDuration(300);
249 | }
250 |
251 | public int dip2px(Context context, float dipValue) {
252 | float m = context.getResources().getDisplayMetrics().density;
253 | return (int) (dipValue * m + 0.5f);
254 | }
255 |
256 | /**
257 | * 处理软键盘backSpace回退事件
258 | *
259 | * @param editTxt 光标所在的文本输入框
260 | */
261 | private void onBackspacePress(EditText editTxt) {
262 | int startSelection = editTxt.getSelectionStart();
263 | // 只有在光标已经顶到文本输入框的最前方,在判定是否删除之前的图片,或两个View合并
264 | if (startSelection == 0) {
265 | int editIndex = allLayout.indexOfChild(editTxt);
266 | View preView = allLayout.getChildAt(editIndex - 1); // 如果editIndex-1<0,
267 | // 则返回的是null
268 | if (null != preView) {
269 | if (preView instanceof RelativeLayout) {
270 | if ("image".equals(preView.getTag(R.id.richEditor))) {
271 | // 光标EditText的上一个view对应的是图片
272 | onImageCloseClick(preView);
273 | } else {
274 | // 光标EditText的上一个view对应的是视频
275 | onVideoCloseClick(preView);
276 | }
277 | } else if (preView instanceof EditText) {
278 | // 光标EditText的上一个view对应的还是文本框EditText
279 | String str1 = editTxt.getText().toString();
280 | EditText preEdit = (EditText) preView;
281 | String str2 = preEdit.getText().toString();
282 |
283 | // allLayout.removeView(editTxt);
284 | allLayout.removeDragView(editTxt);
285 |
286 | // 文本合并
287 | preEdit.setText(str2 + str1);
288 | preEdit.requestFocus();
289 | preEdit.setSelection(str2.length(), str2.length());
290 | lastFocusEdit = preEdit;
291 | }
292 | }
293 | }
294 | }
295 |
296 | /**
297 | * 处理图片叉掉的点击事件
298 | *
299 | * @param view 整个image对应的relativeLayout view
300 | * 删除类型 0代表backspace删除 1代表按红叉按钮删除
301 | */
302 | private void onImageCloseClick(View view) {
303 | disappearingIndex = allLayout.indexOfChild(view);
304 | //删除文件夹里的图片
305 | List dataList = buildEditData();
306 | EditData editData = dataList.get(disappearingIndex);
307 | //Log.i("", "editData: "+editData);
308 | if (editData.imagePath != null) {
309 | SDCardUtil.deleteFile(editData.imagePath);
310 | }
311 | allLayout.removeView(view);
312 | // allLayout.removeDragView(view);
313 | }
314 |
315 | /**
316 | * 处理视频叉掉的点击事件
317 | *
318 | * @param view 整个video对应的relativeLayout view
319 | * 删除类型 0代表backspace删除 1代表按红叉按钮删除
320 | */
321 | private void onVideoCloseClick(View view) {
322 | disappearingIndex = allLayout.indexOfChild(view);
323 | //删除文件夹里的图片
324 | List dataList = buildEditData();
325 | EditData editData = dataList.get(disappearingIndex);
326 | allLayout.removeView(view);
327 | type = IMG_TEXT;
328 |
329 | }
330 |
331 | public void clearAllLayout() {
332 | allLayout.removeAllViews();
333 | }
334 |
335 | public int getLastIndex() {
336 | return allLayout.getChildCount();
337 | }
338 |
339 | /**
340 | * 生成文本输入框
341 | */
342 | public EditText createEditText(String hint, int paddingTop) {
343 | final EditText editText = (EditText) inflater.inflate(R.layout.rich_edittext, null);
344 | editText.setOnKeyListener(keyListener);
345 | editText.setTag(viewTagIndex++);
346 | //
347 | int editNormalPadding = 0;
348 | editText.setPadding(editNormalPadding, paddingTop, editNormalPadding, paddingTop);
349 | editText.setHint(hint);
350 | editText.setOnFocusChangeListener(focusListener);
351 | return editText;
352 | }
353 |
354 | /**
355 | * 生成图片View
356 | */
357 | private RelativeLayout createImageLayout() {
358 | RelativeLayout layout = (RelativeLayout) inflater.inflate(
359 | R.layout.edit_imageview, null);
360 | layout.setTag(viewTagIndex++);
361 | final ImageView openMenu = layout.findViewById(R.id.iv_open);
362 | final ImageView delete = layout.findViewById(R.id.iv_delete);
363 | final ImageView rotate = layout.findViewById(R.id.iv_rotate);
364 | delete.setTag(layout.getTag());
365 | delete.setOnClickListener(btnListener);
366 | rotate.setOnClickListener(btnRotateListener);
367 | openMenu.setOnClickListener(new OnClickListener() {
368 | @Override
369 | public void onClick(View v) {
370 | if (isMenuShow) {
371 | openMenu.setImageResource(R.mipmap.open);
372 |
373 | hideMenu(openMenu, delete, rotate);
374 |
375 | } else {
376 | openMenu.setImageResource(R.mipmap.close);
377 | delete.setVisibility(VISIBLE);
378 | rotate.setVisibility(VISIBLE);
379 | openMenu(openMenu, delete, rotate);
380 |
381 | }
382 | }
383 | });
384 | return layout;
385 | }
386 |
387 |
388 | private void openMenu(ImageView openMenu, final ImageView delete, final ImageView rotate) {
389 | isMenuShow = true;
390 | int distance1 = 150;
391 | int x = (int) openMenu.getX();
392 | int y = (int) openMenu.getY();
393 | ValueAnimator v1 = ValueAnimator.ofInt(x, x - distance1);
394 | v1.setDuration(300);
395 | v1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
396 | @Override
397 | public void onAnimationUpdate(ValueAnimator animation) {
398 | int l = (int) animation.getAnimatedValue();
399 | int t = (int) delete.getY();
400 | int r = delete.getWidth() + l;
401 | int b = delete.getHeight() + t;
402 | delete.layout(l, t, r, b);
403 | }
404 | });
405 | int distance2 = 300;
406 | ValueAnimator v2 = ValueAnimator.ofInt(x, x - distance2);
407 | v2.setDuration(300).addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
408 | @Override
409 | public void onAnimationUpdate(ValueAnimator animation) {
410 | int l = (int) animation.getAnimatedValue();
411 | int t = (int) rotate.getY();
412 | int r = rotate.getWidth() + l;
413 | int b = rotate.getHeight() + t;
414 | rotate.layout(l, t, r, b);
415 | }
416 | });
417 |
418 |
419 | v1.start();
420 | v2.start();
421 | }
422 |
423 | public void hideMenu(ImageView openMenu, final ImageView delete, final ImageView rotate) {
424 | isMenuShow = false;
425 | int x = (int) delete.getX();
426 | ValueAnimator v1 = ValueAnimator.ofInt(x, (int) openMenu.getX());
427 | v1.setDuration(300);
428 | v1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
429 | @Override
430 | public void onAnimationUpdate(ValueAnimator animation) {
431 | int l = (int) animation.getAnimatedValue();
432 | int t = (int) delete.getY();
433 | int r = delete.getWidth() + l;
434 | int b = delete.getHeight() + t;
435 | delete.layout(l, t, r, b);
436 | }
437 | });
438 | x = (int) rotate.getX();
439 | ValueAnimator v2 = ValueAnimator.ofInt(x, (int) openMenu.getX());
440 | v2.setDuration(300).addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
441 | @Override
442 | public void onAnimationUpdate(ValueAnimator animation) {
443 | int l = (int) animation.getAnimatedValue();
444 | int t = (int) rotate.getY();
445 | int r = rotate.getWidth() + l;
446 | int b = rotate.getHeight() + t;
447 | rotate.layout(l, t, r, b);
448 | }
449 | });
450 |
451 | v1.start();
452 | v2.start();
453 | v1.addListener(new Animator.AnimatorListener() {
454 | @Override
455 | public void onAnimationStart(Animator animation) {
456 |
457 | }
458 |
459 | @Override
460 | public void onAnimationEnd(Animator animation) {
461 | delete.setVisibility(GONE);
462 |
463 | }
464 |
465 | @Override
466 | public void onAnimationCancel(Animator animation) {
467 |
468 | }
469 |
470 | @Override
471 | public void onAnimationRepeat(Animator animation) {
472 |
473 | }
474 | });
475 | v2.addListener(new Animator.AnimatorListener() {
476 | @Override
477 | public void onAnimationStart(Animator animation) {
478 |
479 | }
480 |
481 | @Override
482 | public void onAnimationEnd(Animator animation) {
483 | rotate.setVisibility(GONE);
484 | }
485 |
486 | @Override
487 | public void onAnimationCancel(Animator animation) {
488 |
489 | }
490 |
491 | @Override
492 | public void onAnimationRepeat(Animator animation) {
493 |
494 | }
495 | });
496 | }
497 |
498 |
499 | /**
500 | * 生成视频View
501 | */
502 | private RelativeLayout createVideoLayout() {
503 | RelativeLayout layout = (RelativeLayout) inflater.inflate(
504 | R.layout.edit_videoview, null);
505 | //不允许视频拖拽
506 | layout.setOnDragListener(null);
507 | layout.setTag(viewTagIndex++);
508 | View closeView = layout.findViewById(R.id.video_close);
509 | //closeView.setVisibility(GONE);
510 | closeView.setTag(layout.getTag());
511 | closeView.setOnClickListener(btnVideoListener);
512 | return layout;
513 | }
514 |
515 |
516 | /**
517 | * 根据绝对路径添加view
518 | *
519 | * @param imagePath 绝对路径
520 | */
521 | public void insertImage(String imagePath, int width) {
522 | // Bitmap bmp = getScaledBitmap(imagePath, width);
523 | insertImage(imagePath);
524 | }
525 |
526 |
527 | /**
528 | * 插入一张图片
529 | */
530 | public void insertImage(Bitmap bitmap, String imagePath) {
531 | String lastEditStr = lastFocusEdit.getText().toString();
532 | int cursorIndex = lastFocusEdit.getSelectionStart();
533 | String editStr1 = lastEditStr.substring(0, cursorIndex).trim();
534 | int lastEditIndex = allLayout.indexOfChild(lastFocusEdit);
535 |
536 | if (lastEditStr.length() == 0 || editStr1.length() == 0) {
537 | if (lastEditIndex != 0 && allLayout.getChildAt(lastEditIndex - 1) instanceof RelativeLayout) {
538 | addEditTextAtIndex(lastEditIndex, "");
539 | addImageViewAtIndex(lastEditIndex + 1, imagePath);
540 | } else {
541 | // 如果EditText为空,或者光标已经顶在了editText的最前面,则直接插入图片,并且EditText下移即可
542 | addImageViewAtIndex(lastEditIndex, imagePath);
543 | }
544 | } else {
545 | // 如果EditText非空且光标不在最顶端,则需要添加新的imageView和EditText
546 | lastFocusEdit.setText(editStr1);
547 | String editStr2 = lastEditStr.substring(cursorIndex).trim();
548 | if (editStr2.length() == 0) {
549 | editStr2 = " ";
550 | }
551 | if (allLayout.getChildCount() - 1 == lastEditIndex) {
552 | addEditTextAtIndex(lastEditIndex + 1, editStr2);
553 | }
554 |
555 | addImageViewAtIndex(lastEditIndex + 1, imagePath);
556 | lastFocusEdit.requestFocus();
557 | lastFocusEdit.setSelection(editStr1.length(), editStr1.length());//TODO
558 | }
559 | hideKeyBoard();
560 | }
561 |
562 | /**
563 | * 插入一张图片
564 | */
565 | public void insertImage(String imagePath) {
566 | String lastEditStr = lastFocusEdit.getText().toString();
567 | int cursorIndex = lastFocusEdit.getSelectionStart();
568 | String editStr1 = lastEditStr.substring(0, cursorIndex).trim();
569 | int lastEditIndex = allLayout.indexOfChild(lastFocusEdit);
570 |
571 | if (lastEditStr.length() == 0 || editStr1.length() == 0) {
572 | if (lastEditIndex != 0 && allLayout.getChildAt(lastEditIndex - 1) instanceof RelativeLayout) {
573 | addEditTextAtIndex(lastEditIndex, "");
574 | addImageViewAtIndex(lastEditIndex + 1, imagePath);
575 | } else {
576 | // 如果EditText为空,或者光标已经顶在了editText的最前面,则直接插入图片,并且EditText下移即可
577 | addImageViewAtIndex(lastEditIndex, imagePath);
578 | }
579 | } else {
580 | // 如果EditText非空且光标不在最顶端,则需要添加新的imageView和EditText
581 | lastFocusEdit.setText(editStr1);
582 | String editStr2 = lastEditStr.substring(cursorIndex).trim();
583 | if (editStr2.length() == 0) {
584 | editStr2 = " ";
585 | }
586 | if (allLayout.getChildCount() - 1 == lastEditIndex) {
587 | addEditTextAtIndex(lastEditIndex + 1, editStr2);
588 | }
589 |
590 | addImageViewAtIndex(lastEditIndex + 1, imagePath);
591 | lastFocusEdit.requestFocus();
592 | lastFocusEdit.setSelection(editStr1.length(), editStr1.length());//TODO
593 | }
594 | hideKeyBoard();
595 | }
596 |
597 | /**
598 | * 插入一个视频
599 | */
600 | public void insertVideo(String videoPath, String firstImgUrl) {
601 | String lastEditStr = lastFocusEdit.getText().toString();
602 | int cursorIndex = lastFocusEdit.getSelectionStart();
603 | String editStr1 = lastEditStr.substring(0, cursorIndex).trim();
604 | int lastEditIndex = allLayout.indexOfChild(lastFocusEdit);
605 |
606 | if (lastEditStr.length() == 0 || editStr1.length() == 0) {
607 | // 如果EditText为空,或者光标已经顶在了editText的最前面,则直接插入图片,并且EditText下移即可
608 | addVideoViewAtIndex(lastEditIndex, videoPath, firstImgUrl);
609 | } else {
610 | // 如果EditText非空且光标不在最顶端,则需要添加新的videoView和EditText
611 | lastFocusEdit.setText(editStr1);
612 | String editStr2 = lastEditStr.substring(cursorIndex).trim();
613 | if (editStr2.length() == 0) {
614 | editStr2 = " ";
615 | }
616 | if (allLayout.getChildCount() - 1 == lastEditIndex) {
617 | addEditTextAtIndex(lastEditIndex + 1, editStr2);
618 | }
619 |
620 | addVideoViewAtIndex(lastEditIndex + 1, videoPath, firstImgUrl);
621 | lastFocusEdit.requestFocus();
622 | lastFocusEdit.setSelection(editStr1.length(), editStr1.length());//TODO
623 | }
624 | hideKeyBoard();
625 | }
626 |
627 | /**
628 | * 隐藏小键盘
629 | */
630 | public void hideKeyBoard() {
631 | InputMethodManager imm = (InputMethodManager) getContext()
632 | .getSystemService(Context.INPUT_METHOD_SERVICE);
633 | if (imm != null) {
634 | imm.hideSoftInputFromWindow(lastFocusEdit.getWindowToken(), 0);
635 | }
636 | }
637 |
638 | /**
639 | * 在特定位置插入EditText
640 | *
641 | * @param index 位置
642 | * @param editStr EditText显示的文字
643 | */
644 | public void addEditTextAtIndex(final int index, CharSequence editStr) {
645 | EditText editText2 = createEditText("", EDIT_PADDING);
646 | editText2.setHint(R.string.say_somthing);
647 | editText2.setText(editStr);
648 | editText2.setOnFocusChangeListener(focusListener);
649 | allLayout.addDragView(editText2, index);
650 | }
651 |
652 | /**
653 | * 插入EditText
654 | *
655 | * @param editStr EditText显示的文字
656 | */
657 | public void addEditText(CharSequence editStr) {
658 | EditText editText2 = createEditText("", EDIT_PADDING);
659 | editText2.setText(editStr);
660 | editText2.setOnFocusChangeListener(focusListener);
661 | allLayout.addDragView(editText2);
662 | }
663 |
664 |
665 | /**
666 | * 清除特定位置的ImageView
667 | */
668 | public void removeImageViewAtIndex(int index) {
669 | View childAt = allLayout.getChildAt(index);
670 | onImageCloseClick(childAt);
671 |
672 | }
673 |
674 | /**
675 | * 在特定位置添加ImageView
676 | */
677 | public void addImageViewAtIndex(final int index, String imagePath) {
678 | final RelativeLayout imageLayout = createImageLayout();
679 | imageLayout.setTag(R.id.richEditor, "image");
680 | DataImageView imageView = imageLayout.findViewById(R.id.edit_imageView);
681 | ImageView move = imageLayout.findViewById(R.id.move);
682 | imageView.setOnClickListener(btnImgClickListener);
683 | DrawableCrossFadeFactory factory =
684 | new DrawableCrossFadeFactory.Builder().setCrossFadeEnabled(true).build();
685 | Glide.with(getContext()).load(imagePath).skipMemoryCache(true).diskCacheStrategy(DiskCacheStrategy.NONE).transition(withCrossFade(factory)).centerCrop().into(imageView);
686 | imageView.setAbsolutePath(imagePath);//保留这句,后面保存数据会用
687 | imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);//裁剪剧中
688 |
689 | // 调整imageView的高度,根据宽度来调整高度
690 | // Bitmap bmp = BitmapFactory.decodeFile(imagePath);
691 | // int imageHeight = 300;
692 | int imageHeight = allLayout.getWidth() * 3 / 5;
693 | /*if (bmp != null) {
694 | // imageHeight = allLayout.getWidth() * bmp.getHeight() / bmp.getWidth();
695 | imageHeight = allLayout.getWidth() * 3 / 5;
696 | bmp.recycle();
697 | }*/
698 | // 调整图片高度,这里是否有必要,如果出现微博长图,可能会很难看
699 | RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, imageHeight);//设置图片固定高度
700 | lp.bottomMargin = 10;
701 | imageView.setLayoutParams(lp);
702 | // allLayout.addView(imageLayout, index);
703 | allLayout.addDragView(imageLayout, move, index);
704 | }
705 |
706 |
707 | /**
708 | * 在特定位置添加VideoView
709 | */
710 | public void addVideoViewAtIndex(final int index, String videoPath, String firstImgUrl) {
711 | final RelativeLayout videoLayout = createVideoLayout();
712 | videoLayout.setTag(R.id.richEditor, "video");
713 | // DataVideoView videoView = (DataVideoView) videoLayout.findViewById(R.id.edit_videoView);
714 | DataVideoView videoView = videoLayout.findViewById(R.id.edit_videoView);
715 | ImageView videoMove = videoLayout.findViewById(R.id.video_move);
716 | /*videoView.setVideoPath(videoPath);
717 | videoView.setMediaController(new MediaController(mContext));*/
718 | videoView.setUp(videoPath, "", Jzvd.SCREEN_NORMAL);
719 | Glide.with(mContext).load(firstImgUrl).skipMemoryCache(true).diskCacheStrategy(DiskCacheStrategy.NONE).into(videoView.posterImageView);
720 | videoView.setAbsolutePath(videoPath);//保留这句,后面保存数据会用
721 |
722 | // 调整imageView的高度,根据宽度来调整高度
723 | Bitmap bmp = BitmapFactory.decodeFile(videoPath);
724 | int imageHeight = allLayout.getWidth() * 9 / 16;
725 | //16:9
726 | RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
727 | LayoutParams.MATCH_PARENT, imageHeight);//设置视频固定高度
728 | lp.addRule(RelativeLayout.CENTER_IN_PARENT);
729 | lp.bottomMargin = 10;
730 | videoView.setLayoutParams(lp);
731 |
732 | // allLayout.addView(videoLayout, index);
733 | // allLayout.addDragView(videoLayout, index);
734 | allLayout.addDragView(videoLayout, videoMove, index);
735 | type = VIDEO;
736 | }
737 |
738 | /**
739 | * 获取当前的类型是否是视频类型
740 | */
741 | public boolean isVideoType() {
742 | return type == VIDEO;
743 | }
744 |
745 | /**
746 | * 根据view的宽度,动态缩放bitmap尺寸
747 | *
748 | * @param width view的宽度
749 | */
750 | public Bitmap getScaledBitmap(String filePath, int width) {
751 | BitmapFactory.Options options = new BitmapFactory.Options();
752 | options.inJustDecodeBounds = true;
753 | BitmapFactory.decodeFile(filePath, options);
754 | int sampleSize = options.outWidth > width ? options.outWidth / width
755 | + 1 : 1;
756 | options.inJustDecodeBounds = false;
757 | options.inSampleSize = sampleSize;
758 | return BitmapFactory.decodeFile(filePath, options);
759 | }
760 |
761 |
762 | /**
763 | * 对外提供的接口, 生成编辑数据上传
764 | */
765 | public List buildEditData() {
766 | List dataList = new ArrayList<>();
767 | int num = allLayout.getChildCount();
768 | for (int index = 0; index < num; index++) {
769 | View itemView = allLayout.getChildAt(index);
770 | EditData itemData = new EditData();
771 | if (itemView instanceof EditText) {
772 | EditText item = (EditText) itemView;
773 | String trim = item.getText().toString().trim();
774 | if (TextUtils.isEmpty(trim)) {
775 | continue;
776 | }
777 | itemData.inputStr = item.getText().toString();
778 | } else if (itemView instanceof RelativeLayout) {
779 | if ("image".equals(itemView.getTag(R.id.richEditor))) {
780 | DataImageView item = itemView.findViewById(R.id.edit_imageView);
781 | itemData.imagePath = item.getAbsolutePath();
782 | }
783 |
784 | if ("video".equals(itemView.getTag(R.id.richEditor))) {
785 | DataVideoView item = itemView.findViewById(R.id.edit_videoView);
786 | itemData.videoPath = item.getAbsolutePath();
787 | }
788 |
789 | }
790 | dataList.add(itemData);
791 | }
792 |
793 | return dataList;
794 | }
795 |
796 | public class EditData {
797 | String inputStr;
798 | String imagePath;
799 | String videoPath;
800 |
801 | @NonNull
802 | @Override
803 | public String toString() {
804 | return "EditData{" +
805 | "inputStr='" + inputStr + '\'' +
806 | ", imagePath='" + imagePath + '\'' +
807 | ", videoPath='" + videoPath + '\'' +
808 | '}';
809 | }
810 | }
811 | }
812 |
--------------------------------------------------------------------------------
/richeditorlibary/src/main/java/com/pppcar/richeditorlibary/wrapper/SimpleTextWatcher.java:
--------------------------------------------------------------------------------
1 | package com.pppcar.richeditorlibary.wrapper;
2 |
3 | import android.text.Editable;
4 | import android.text.TextWatcher;
5 |
6 | /**
7 | * 作者: Logan on 17/5/3 0003.
8 | * 邮箱: 490636907@qq.com
9 | * 描述: TextWatcher的包装类
10 | */
11 |
12 | public class SimpleTextWatcher implements TextWatcher {
13 | @Override
14 | public void beforeTextChanged(CharSequence s, int start, int count, int after) {
15 |
16 | }
17 |
18 | @Override
19 | public void onTextChanged(CharSequence s, int start, int before, int count) {
20 |
21 | }
22 |
23 | @Override
24 | public void afterTextChanged(Editable s) {
25 |
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/richeditorlibary/src/main/res/drawable-hdpi/ab_solid_shadow_holo.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq2519157/RichEditor/8b982630c7162afbc1eacfb8773e235443f8807f/richeditorlibary/src/main/res/drawable-hdpi/ab_solid_shadow_holo.9.png
--------------------------------------------------------------------------------
/richeditorlibary/src/main/res/drawable-mdpi/ab_solid_shadow_holo.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq2519157/RichEditor/8b982630c7162afbc1eacfb8773e235443f8807f/richeditorlibary/src/main/res/drawable-mdpi/ab_solid_shadow_holo.9.png
--------------------------------------------------------------------------------
/richeditorlibary/src/main/res/drawable-xhdpi/ab_solid_shadow_holo.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq2519157/RichEditor/8b982630c7162afbc1eacfb8773e235443f8807f/richeditorlibary/src/main/res/drawable-xhdpi/ab_solid_shadow_holo.9.png
--------------------------------------------------------------------------------
/richeditorlibary/src/main/res/drawable-xxhdpi/ab_solid_shadow_holo.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq2519157/RichEditor/8b982630c7162afbc1eacfb8773e235443f8807f/richeditorlibary/src/main/res/drawable-xxhdpi/ab_solid_shadow_holo.9.png
--------------------------------------------------------------------------------
/richeditorlibary/src/main/res/drawable/ab_solid_shadow_holo_flipped.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
--------------------------------------------------------------------------------
/richeditorlibary/src/main/res/layout/act_rotate_img.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
12 |
15 |
19 |
27 |
28 |
38 |
39 |
40 |
41 |
42 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/richeditorlibary/src/main/res/layout/edit_imageview.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
25 |
26 |
27 |
28 |
35 |
48 |
49 |
62 |
63 |
64 |
76 |
77 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/richeditorlibary/src/main/res/layout/edit_videoview.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
15 |
27 |
28 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/richeditorlibary/src/main/res/layout/rich_edittext.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/richeditorlibary/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq2519157/RichEditor/8b982630c7162afbc1eacfb8773e235443f8807f/richeditorlibary/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/richeditorlibary/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq2519157/RichEditor/8b982630c7162afbc1eacfb8773e235443f8807f/richeditorlibary/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/richeditorlibary/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq2519157/RichEditor/8b982630c7162afbc1eacfb8773e235443f8807f/richeditorlibary/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/richeditorlibary/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq2519157/RichEditor/8b982630c7162afbc1eacfb8773e235443f8807f/richeditorlibary/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/richeditorlibary/src/main/res/mipmap-xxxhdpi/close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq2519157/RichEditor/8b982630c7162afbc1eacfb8773e235443f8807f/richeditorlibary/src/main/res/mipmap-xxxhdpi/close.png
--------------------------------------------------------------------------------
/richeditorlibary/src/main/res/mipmap-xxxhdpi/delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq2519157/RichEditor/8b982630c7162afbc1eacfb8773e235443f8807f/richeditorlibary/src/main/res/mipmap-xxxhdpi/delete.png
--------------------------------------------------------------------------------
/richeditorlibary/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq2519157/RichEditor/8b982630c7162afbc1eacfb8773e235443f8807f/richeditorlibary/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/richeditorlibary/src/main/res/mipmap-xxxhdpi/open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq2519157/RichEditor/8b982630c7162afbc1eacfb8773e235443f8807f/richeditorlibary/src/main/res/mipmap-xxxhdpi/open.png
--------------------------------------------------------------------------------
/richeditorlibary/src/main/res/mipmap-xxxhdpi/rotate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq2519157/RichEditor/8b982630c7162afbc1eacfb8773e235443f8807f/richeditorlibary/src/main/res/mipmap-xxxhdpi/rotate.png
--------------------------------------------------------------------------------
/richeditorlibary/src/main/res/mipmap-xxxhdpi/up_down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq2519157/RichEditor/8b982630c7162afbc1eacfb8773e235443f8807f/richeditorlibary/src/main/res/mipmap-xxxhdpi/up_down.png
--------------------------------------------------------------------------------
/richeditorlibary/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/richeditorlibary/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/richeditorlibary/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 | #00000000
7 | #000000
8 | #cccccc
9 |
10 |
--------------------------------------------------------------------------------
/richeditorlibary/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 | 16dp
6 |
7 |
--------------------------------------------------------------------------------
/richeditorlibary/src/main/res/values/ids.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/richeditorlibary/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | RichEditorLibiary
3 | 说点什么吧
4 |
5 |
--------------------------------------------------------------------------------
/richeditorlibary/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/richeditorlibary/src/test/java/com/pppcar/richeditorlibary/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.pppcar.richeditorlibary;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 | rootProject.name = "RichEditor"
16 | include ':app',':richeditorlibary'
17 |
--------------------------------------------------------------------------------