├── .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 | [![996.icu](https://img.shields.io/badge/link-996.icu-red.svg)](https://996.icu) 7 | *** 8 | * ### 介绍 9 | 富文本编辑器,可添加视频和图片,带图片拖动功能 10 | ![](https://github.com/qq2519157/RichEditor/blob/master/app/src/main/assets/fly.gif) 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 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 |