├── .gitignore ├── .idea ├── .name ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── gradle.xml ├── jarRepositories.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── aar └── crop_image.aar ├── app ├── .gitignore ├── build.gradle ├── libs │ └── crop_image.aar ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── org │ │ └── liaohailong │ │ └── cropimageview │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── org │ │ │ └── liaohailong │ │ │ └── cropimageview │ │ │ ├── ContentKits.java │ │ │ ├── MainActivity.kt │ │ │ └── PermissionUtil.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ └── provider_paths.xml │ └── test │ └── java │ └── org │ └── liaohailong │ └── cropimageview │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── example01.gif ├── example02.gif └── example03.png ├── library ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── org │ │ └── liaohailong │ │ └── library │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── org │ │ │ └── liaohailong │ │ │ └── library │ │ │ ├── CropImageActivity.java │ │ │ ├── CropOptions.java │ │ │ └── CropPhotoView.java │ └── res │ │ ├── drawable │ │ ├── selector_green_item_click_bg.xml │ │ ├── selector_item_click_bg.xml │ │ ├── shape_round_rect_green.xml │ │ └── view_progress_bg.xml │ │ ├── layout │ │ └── activity_crop_image.xml │ │ └── values │ │ ├── colors.xml │ │ └── styles.xml │ └── test │ └── java │ └── org │ └── liaohailong │ └── library │ └── ExampleUnitTest.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | CropImageView -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 20 | 22 | 23 | 24 | 26 | 27 | 28 |
29 | 30 | 31 | 32 | xmlns:android 33 | 34 | ^$ 35 | 36 | 37 | 38 |
39 |
40 | 41 | 42 | 43 | xmlns:.* 44 | 45 | ^$ 46 | 47 | 48 | BY_NAME 49 | 50 |
51 |
52 | 53 | 54 | 55 | .*:id 56 | 57 | http://schemas.android.com/apk/res/android 58 | 59 | 60 | 61 |
62 |
63 | 64 | 65 | 66 | .*:name 67 | 68 | http://schemas.android.com/apk/res/android 69 | 70 | 71 | 72 |
73 |
74 | 75 | 76 | 77 | name 78 | 79 | ^$ 80 | 81 | 82 | 83 |
84 |
85 | 86 | 87 | 88 | style 89 | 90 | ^$ 91 | 92 | 93 | 94 |
95 |
96 | 97 | 98 | 99 | .* 100 | 101 | ^$ 102 | 103 | 104 | BY_NAME 105 | 106 |
107 |
108 | 109 | 110 | 111 | .* 112 | 113 | http://schemas.android.com/apk/res/android 114 | 115 | 116 | ANDROID_ATTRIBUTE_ORDER 117 | 118 |
119 |
120 | 121 | 122 | 123 | .* 124 | 125 | .* 126 | 127 | 128 | BY_NAME 129 | 130 |
131 |
132 |
133 |
134 | 135 | 137 |
138 |
-------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 22 | 23 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CropImageView 2 | 仿微信图片裁剪功能,支持对图片两指缩放,旋转 3 | 4 | 图片裁剪成正方形 5 | 6 | 科学上网 或 Github 加速器 7 | 8 | 图片裁剪成长方形 9 | 10 | 科学上网 或 Github 加速器 11 | 12 | 使用方式:下载aar包,项目依赖比较快:https://github.com/liaohailong190/CropImageView/tree/master/aar 13 | 14 | 科学上网 或 Github 加速器 15 | 16 | 在Activity中调用以下代码,调起图片裁剪界面 17 | ```kotlin 18 | val uri = Uri.fromFile(this) // 资源图片uri 19 | val output = Uri.fromFile(generateImageFile()) // 输出图片uri 20 | val width = resources.displayMetrics.widthPixels // 输出宽度 px 21 | val height = (width * cropRatio).toInt() // 输出高度 px 22 | // 裁剪的宽高比例,通过width和height来控制->width/height 23 | val options: CropOptions = CropOptions.Factory.create( 24 | uri, 25 | output, 26 | width, 27 | height, 28 | Bitmap.CompressFormat.JPEG 29 | ) 30 | CropImageActivity.showForResult(this@MainActivity, options, REQUEST_CROP) 31 | ``` 32 | 33 | 在调起的Activitiy中复写onActivityResult函数,获取裁剪返回值 34 | ```kotlin 35 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 36 | super.onActivityResult(requestCode, resultCode, data) 37 | 38 | if (resultCode == Activity.RESULT_OK) { 39 | when (requestCode) { 40 | REQUEST_CROP -> { 41 | data?.apply { 42 | var uri = getData() 43 | // uri is the crop result , do something you want... 44 | } 45 | } 46 | } 47 | } 48 | } 49 | ``` 50 | -------------------------------------------------------------------------------- /aar/crop_image.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liaohailong123/CropImageView/1ff2496771606f7600b2987b0bdde7c4b63cd648/aar/crop_image.aar -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion 29 7 | 8 | defaultConfig { 9 | applicationId "org.liaohailong.cropimageview" 10 | minSdkVersion 21 11 | targetSdkVersion 29 12 | versionCode 1 13 | versionName "1.0" 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | } 16 | 17 | compileOptions { 18 | sourceCompatibility JavaVersion.VERSION_1_8 19 | targetCompatibility JavaVersion.VERSION_1_8 20 | } 21 | 22 | buildTypes { 23 | release { 24 | minifyEnabled false 25 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 26 | } 27 | } 28 | } 29 | 30 | repositories { 31 | flatDir { 32 | dirs 'libs' 33 | } 34 | } 35 | 36 | dependencies { 37 | implementation fileTree(dir: "libs", include: ["*.jar"]) 38 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 39 | implementation 'androidx.core:core-ktx:1.3.1' 40 | implementation 'androidx.appcompat:appcompat:1.2.0' 41 | implementation 'androidx.constraintlayout:constraintlayout:2.0.1' 42 | testImplementation 'junit:junit:4.12' 43 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 44 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 45 | 46 | // implementation(name:'crop_image', ext:'aar') 47 | implementation project(path: ':library') 48 | } -------------------------------------------------------------------------------- /app/libs/crop_image.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liaohailong123/CropImageView/1ff2496771606f7600b2987b0bdde7c4b63cd648/app/libs/crop_image.aar -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/org/liaohailong/cropimageview/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package org.liaohailong.cropimageview 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("org.liaohailong.cropimageview", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 29 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/java/org/liaohailong/cropimageview/ContentKits.java: -------------------------------------------------------------------------------- 1 | package org.liaohailong.cropimageview; 2 | 3 | import android.content.ContentUris; 4 | import android.content.ContentValues; 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 | import androidx.annotation.NonNull; 14 | 15 | import java.io.File; 16 | 17 | /** 18 | * 根据不同的uri 类型 获取资源路径工具类 19 | * 从android官方demo中找到的工具类 20 | *

21 | * Created by Tesla on 4/7/2017. 22 | */ 23 | public class ContentKits { 24 | 25 | private ContentKits() { 26 | } 27 | 28 | /** 29 | * Get a file path from a Uri. This will get the the path for Storage Access 30 | * Framework Documents, as well as the _data field for the MediaStore and 31 | * other file-based ContentProviders. 32 | * 33 | * @param context The context. 34 | * @param uri The Uri to query. 35 | * @author paulburke 36 | */ 37 | public static String getPath(final Context context, final Uri uri) { 38 | if (null == uri) { 39 | return ""; 40 | } 41 | // DocumentProvider 42 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && 43 | DocumentsContract.isDocumentUri(context, uri)) { 44 | // ExternalStorageProvider 45 | if (isExternalStorageDocument(uri)) { 46 | final String docId = DocumentsContract.getDocumentId(uri); 47 | final String[] split = docId.split(":"); 48 | final String type = split[0]; 49 | 50 | if ("primary".equalsIgnoreCase(type)) { 51 | return Environment.getExternalStorageDirectory() + "/" + split[1]; 52 | } 53 | } else if (isDownloadsDocument(uri)) { 54 | // DownloadsProvider 55 | final String id = DocumentsContract.getDocumentId(uri); 56 | final Uri contentUri = ContentUris.withAppendedId( 57 | Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); 58 | return getDataColumn(context, contentUri, null, null); 59 | } else if (isMediaDocument(uri)) { 60 | // MediaProvider 61 | final String docId = DocumentsContract.getDocumentId(uri); 62 | final String[] split = docId.split(":"); 63 | final String type = split[0]; 64 | 65 | Uri contentUri = null; 66 | if ("image".equals(type)) { 67 | contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; 68 | } else if ("video".equals(type)) { 69 | contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; 70 | } else if ("audio".equals(type)) { 71 | contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; 72 | } 73 | 74 | final String selection = "_id=?"; 75 | final String[] selectionArgs = new String[]{ 76 | split[1] 77 | }; 78 | 79 | return getDataColumn(context, contentUri, selection, selectionArgs); 80 | } 81 | } else if ("content".equalsIgnoreCase(uri.getScheme())) { 82 | // MediaStore (and general) 83 | // Return the remote address 84 | if (isGooglePhotosUri(uri)) 85 | return uri.getLastPathSegment(); 86 | 87 | return getDataColumn(context, uri, null, null); 88 | } else if ("file".equalsIgnoreCase(uri.getScheme())) { 89 | // File 90 | return uri.getPath(); 91 | } 92 | 93 | return ""; 94 | } 95 | 96 | /** 97 | * * 98 | * Get the value of the data column for this Uri. This is useful for 99 | * MediaStore Uris, and other file-based ContentProviders. 100 | * 101 | * @param context The context. 102 | * @param uri The Uri to query. 103 | * @param selection (Optional) Filter used in the query. 104 | * @param selectionArgs (Optional) Selection arguments used in the query. 105 | * @return The value of the _data column, which is typically a file path. 106 | */ 107 | public static String getDataColumn(Context context, Uri uri, String selection, 108 | String[] selectionArgs) { 109 | Cursor cursor = null; 110 | final String column = "_data"; 111 | final String[] projection = { 112 | column 113 | }; 114 | try { 115 | cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, 116 | null); 117 | if (cursor != null && cursor.moveToFirst()) { 118 | final int index = cursor.getColumnIndexOrThrow(column); 119 | return cursor.getString(index); 120 | } 121 | } finally { 122 | if (cursor != null) { 123 | cursor.close(); 124 | } 125 | } 126 | return ""; 127 | } 128 | 129 | /** 130 | * @param uri The Uri to check. 131 | * @return Whether the Uri authority is ExternalStorageProvider. 132 | */ 133 | public static boolean isExternalStorageDocument(Uri uri) { 134 | return "com.android.externalstorage.documents".equals(uri.getAuthority()); 135 | } 136 | 137 | /** 138 | * @param uri The Uri to check. 139 | * @return Whether the Uri authority is DownloadsProvider. 140 | */ 141 | public static boolean isDownloadsDocument(Uri uri) { 142 | return "com.android.providers.downloads.documents".equals(uri.getAuthority()); 143 | } 144 | 145 | /** 146 | * @param uri The Uri to check. 147 | * @return Whether the Uri authority is MediaProvider. 148 | */ 149 | public static boolean isMediaDocument(Uri uri) { 150 | return "com.android.providers.media.documents".equals(uri.getAuthority()); 151 | } 152 | 153 | /** 154 | * @param uri The Uri to check. 155 | * @return Whether the Uri authority is Google Photos. 156 | */ 157 | public static boolean isGooglePhotosUri(Uri uri) { 158 | return "com.google.android.apps.photos.content".equals(uri.getAuthority()); 159 | } 160 | 161 | /** 162 | * 转换 content:// uri 163 | * 164 | * @param imageFile 文件 165 | */ 166 | public static Uri getImageContentUri(@NonNull Context context, File imageFile) { 167 | String filePath = imageFile.getAbsolutePath(); 168 | Cursor cursor = context.getContentResolver().query( 169 | MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 170 | new String[]{MediaStore.Images.Media._ID}, 171 | MediaStore.Images.Media.DATA + "=? ", 172 | new String[]{filePath}, null); 173 | if (cursor != null && cursor.moveToFirst()) { 174 | int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID)); 175 | Uri baseUri = Uri.parse("content://media/external/images/media"); 176 | cursor.close(); 177 | return Uri.withAppendedPath(baseUri, "" + id); 178 | } else { 179 | if (imageFile.exists()) { 180 | ContentValues values = new ContentValues(); 181 | values.put(MediaStore.Images.Media.DATA, filePath); 182 | return context.getContentResolver().insert( 183 | MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); 184 | } else { 185 | return null; 186 | } 187 | } 188 | } 189 | 190 | /** 191 | * 是否是来自存储卡上 192 | * @param uri 文件uri 193 | * @return true表示是内存路径 194 | */ 195 | public static boolean isStorage(Uri uri) { 196 | String path = uri.getPath(); 197 | return path != null && path.startsWith("/storage"); 198 | } 199 | } -------------------------------------------------------------------------------- /app/src/main/java/org/liaohailong/cropimageview/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package org.liaohailong.cropimageview 2 | 3 | import android.Manifest 4 | import android.app.Activity 5 | import android.app.AlertDialog 6 | import android.content.Context 7 | import android.content.Intent 8 | import android.graphics.Bitmap 9 | import android.graphics.BitmapFactory 10 | import android.net.Uri 11 | import android.os.* 12 | import android.provider.MediaStore 13 | import android.widget.Toast 14 | import androidx.core.content.FileProvider 15 | import androidx.fragment.app.FragmentActivity 16 | import kotlinx.android.synthetic.main.activity_main.* 17 | import org.liaohailong.library.CropImageActivity 18 | import org.liaohailong.library.CropOptions 19 | import java.io.File 20 | import java.io.FileInputStream 21 | import java.io.FileOutputStream 22 | 23 | /** 24 | * Describe: 25 | * 26 | * 27 | * @author liaohailong 28 | * Time: 2020/9/29 13:56 29 | */ 30 | class MainActivity : FragmentActivity() { 31 | companion object { 32 | const val REQUEST_ALBUM = 0 33 | const val REQUEST_CAPTURE = 1 34 | const val REQUEST_CROP = 2 35 | } 36 | 37 | private var displayFile: File? = null 38 | private var captureFile: File? = null 39 | private var cropUri: Uri? = null 40 | private val mainHandler = Handler(Looper.getMainLooper()) 41 | 42 | override fun onCreate(savedInstanceState: Bundle?) { 43 | super.onCreate(savedInstanceState) 44 | setContentView(R.layout.activity_main) 45 | 46 | val permissions = arrayOf( 47 | Manifest.permission.WRITE_EXTERNAL_STORAGE, 48 | Manifest.permission.CAMERA 49 | ) 50 | PermissionUtil.requestIfNot( 51 | this, 52 | listOf(*permissions), 53 | 0 54 | ) 55 | 56 | open_album.setOnClickListener { 57 | val intent = Intent() 58 | intent.action = Intent.ACTION_PICK 59 | intent.type = "image/*" 60 | startActivityForResult(intent, REQUEST_ALBUM) 61 | } 62 | jump_crop.setOnClickListener { 63 | displayFile?.apply { 64 | val items = arrayOf("1:1", "16:9", "9:16", "4:3", "3:4", "1:2", "2:1") 65 | AlertDialog.Builder(this@MainActivity, 0) 66 | .setTitle("请选择裁剪宽高比") 67 | .setIcon(R.mipmap.ic_launcher) 68 | .setSingleChoiceItems(items, 0) { dialog, which -> 69 | val cropRatio = when (which) { 70 | 0 -> 1f / 1f 71 | 1 -> 16f / 9f 72 | 2 -> 9f / 16f 73 | 3 -> 4f / 3f 74 | 4 -> 3f / 4f 75 | 5 -> 1f / 2f 76 | 6 -> 2f / 1f 77 | else -> 1.0f 78 | } 79 | dialog.dismiss() 80 | val uri = Uri.fromFile(this) // 资源图片uri 81 | val output = Uri.fromFile(generateImageFile()) // 输出图片uri 82 | val width = resources.displayMetrics.widthPixels // 输出宽度 px 83 | val height = (width * cropRatio).toInt() // 输出高度 px 84 | // 裁剪的宽高比例,通过width和height来控制->width/height 85 | val options: CropOptions = CropOptions.Factory.create( 86 | uri, 87 | output, 88 | width, 89 | height, 90 | Bitmap.CompressFormat.JPEG 91 | ) 92 | CropImageActivity.showForResult(this@MainActivity, options, REQUEST_CROP) 93 | }.create().show() 94 | } 95 | } 96 | take_photo.setOnClickListener { 97 | //创建打开本地相机的意图对象 98 | val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) 99 | //设置图片的保存位置(兼容Android7.0) 100 | captureFile = generateImageFile() 101 | captureFile?.apply { 102 | val fileUri = getUriForFile(this@MainActivity, this) 103 | //指定图片保存位置 104 | intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri) 105 | } 106 | //开启意图 107 | startActivityForResult(intent, REQUEST_CAPTURE) 108 | } 109 | } 110 | 111 | private fun setImageByPath(path: String) { 112 | val bitmap = BitmapFactory.decodeFile(path) 113 | crop_img.setImageBitmap(bitmap) 114 | } 115 | 116 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 117 | super.onActivityResult(requestCode, resultCode, data) 118 | 119 | if (resultCode == Activity.RESULT_OK) { 120 | when (requestCode) { 121 | REQUEST_ALBUM -> { 122 | data?.apply { 123 | val uri = getData() ?: return 124 | AsyncTask.THREAD_POOL_EXECUTOR.execute { 125 | displayFile = generateImageFile() 126 | if (copyResult(uri, displayFile!!)) { 127 | open_album.post { 128 | setImageByPath(displayFile!!.absolutePath) 129 | } 130 | } 131 | } 132 | } 133 | } 134 | REQUEST_CAPTURE -> { 135 | AsyncTask.THREAD_POOL_EXECUTOR.execute { 136 | val uri = Uri.fromFile(captureFile!!) 137 | displayFile = generateImageFile() 138 | if (copyResult(uri, displayFile!!)) { 139 | open_album.post { 140 | setImageByPath(displayFile!!.absolutePath) 141 | } 142 | } 143 | } 144 | } 145 | REQUEST_CROP -> { 146 | data?.apply { 147 | var uri = getData() 148 | uri = uri ?: cropUri 149 | displayFile = generateImageFile() 150 | if (copyResult(uri!!, displayFile!!)) { 151 | open_album.post { 152 | setImageByPath(displayFile!!.absolutePath) 153 | } 154 | } 155 | } 156 | } 157 | } 158 | } 159 | } 160 | 161 | private fun generateImageFile(): File { 162 | val dir = externalCacheDir 163 | val fileName = "IMG_${System.currentTimeMillis()}.jpg" 164 | return File(dir, fileName) 165 | } 166 | 167 | private fun copyResult(uri: Uri, dstFile: File): Boolean { 168 | val storage = ContentKits.isStorage(uri) 169 | val path = if (storage) uri.path else ContentKits.getPath(this, uri) 170 | if (!File(path!!).exists()) { 171 | mainHandler.post { 172 | Toast.makeText(this, "原图已被删除,请选择其他图片", Toast.LENGTH_SHORT).show() 173 | } 174 | return false 175 | } 176 | 177 | var fis: FileInputStream? = null 178 | var fos: FileOutputStream? = null 179 | try { 180 | fis = if (storage) FileInputStream(File(path)) else { 181 | val pfd: ParcelFileDescriptor = contentResolver.openFileDescriptor(uri, "r")!! 182 | val fd = pfd.fileDescriptor 183 | FileInputStream(fd) 184 | } 185 | fos = FileOutputStream(dstFile) 186 | val readBytes = fis.readBytes() 187 | fos.write(readBytes) 188 | return true 189 | } catch (e: Exception) { 190 | e.printStackTrace() 191 | try { 192 | fis?.close() 193 | } catch (ex: Exception) { 194 | ex.printStackTrace() 195 | } 196 | try { 197 | fos?.close() 198 | } catch (ex: Exception) { 199 | ex.printStackTrace() 200 | } 201 | return false 202 | } 203 | } 204 | 205 | private fun getUriForFile(context: Context, file: File): Uri? { 206 | return if (Build.VERSION.SDK_INT >= 24) { 207 | //参数:authority 需要和清单文件中配置的保持完全一致:${applicationId}.provider 208 | FileProvider.getUriForFile(context, context.packageName + ".provider", file) 209 | } else { 210 | Uri.fromFile(file) 211 | } 212 | } 213 | } -------------------------------------------------------------------------------- /app/src/main/java/org/liaohailong/cropimageview/PermissionUtil.kt: -------------------------------------------------------------------------------- 1 | package org.liaohailong.cropimageview 2 | 3 | import android.app.Activity 4 | import android.content.pm.PackageManager 5 | import androidx.core.app.ActivityCompat 6 | 7 | /** 8 | * Author: liaohailong 9 | * Date: 2019/3/18 10 | * Time: 8:08 PM 11 | * Description: 权限请求 12 | **/ 13 | object PermissionUtil { 14 | 15 | fun requestIfNot(activity: Activity, permission: String, requestCode: Int): Boolean { 16 | return PermissionUtil.requestIfNot(activity, listOf(permission), requestCode) 17 | } 18 | 19 | fun requestIfNot(activity: Activity, permissions: List, requestCode: Int): Boolean { 20 | val deniedPermissions = mutableListOf() 21 | for (permission in permissions) { 22 | if (PackageManager.PERMISSION_DENIED == ActivityCompat.checkSelfPermission(activity, permission)) { 23 | deniedPermissions.add(permission) 24 | } 25 | } 26 | if (deniedPermissions.isEmpty()) return true 27 | ActivityCompat.requestPermissions(activity, deniedPermissions.toTypedArray(), requestCode) 28 | return false 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 |