From aea71da6c6b51d9ce238313aff558b56a72d0d5a Mon Sep 17 00:00:00 2001 From: Ztiany Date: Fri, 22 Nov 2019 19:31:56 +0800 Subject: [PATCH] fix MediaSelector --- .../main/java/com/android/base/data/State.kt | 10 + .../android/sdk/mediaselector/StyleUtils.java | 28 +++ .../mediaselector/SystemMediaSelector.java | 154 +++++++++----- .../com/android/sdk/mediaselector/Utils.java | 188 +++++++++++++++--- 4 files changed, 302 insertions(+), 78 deletions(-) create mode 100644 lib_media_selector/src/main/java/com/android/sdk/mediaselector/StyleUtils.java diff --git a/lib_base/src/main/java/com/android/base/data/State.kt b/lib_base/src/main/java/com/android/base/data/State.kt index 2a8a1f3..627272d 100644 --- a/lib_base/src/main/java/com/android/base/data/State.kt +++ b/lib_base/src/main/java/com/android/base/data/State.kt @@ -1,5 +1,7 @@ package com.android.base.data +import androidx.lifecycle.MutableLiveData + /** * @author Ztiany @@ -200,4 +202,12 @@ inline fun State.onSuccessWithData(onSuccess: (data: T) -> Unit): State MutableLiveData>.postLoading() { + postValue(State.loading()) +} + +fun MutableLiveData>.postError(error: Throwable) { + postValue(State.error(error)) } \ No newline at end of file diff --git a/lib_media_selector/src/main/java/com/android/sdk/mediaselector/StyleUtils.java b/lib_media_selector/src/main/java/com/android/sdk/mediaselector/StyleUtils.java new file mode 100644 index 0000000..347cd85 --- /dev/null +++ b/lib_media_selector/src/main/java/com/android/sdk/mediaselector/StyleUtils.java @@ -0,0 +1,28 @@ +package com.android.sdk.mediaselector; + +import android.content.Context; +import android.os.Build; +import android.util.TypedValue; + +/** + * @author Ztiany + * Email: ztiany3@gmail.com + * Date : 2019-11-22 11:36 + */ +class StyleUtils { + + //https://stackoverflow.com/questions/27611173/how-to-get-accent-color-programmatically + static int fetchPrimaryColor(Context context) { + int colorAttr; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + colorAttr = android.R.attr.colorPrimary; + } else { + //Get colorAccent defined for AppCompat + colorAttr = context.getResources().getIdentifier("colorPrimary", "attr", context.getPackageName()); + } + TypedValue outValue = new TypedValue(); + context.getTheme().resolveAttribute(colorAttr, outValue, true); + return outValue.data; + } + +} \ No newline at end of file diff --git a/lib_media_selector/src/main/java/com/android/sdk/mediaselector/SystemMediaSelector.java b/lib_media_selector/src/main/java/com/android/sdk/mediaselector/SystemMediaSelector.java index 00ba5a1..b8df925 100644 --- a/lib_media_selector/src/main/java/com/android/sdk/mediaselector/SystemMediaSelector.java +++ b/lib_media_selector/src/main/java/com/android/sdk/mediaselector/SystemMediaSelector.java @@ -9,6 +9,7 @@ import android.util.Log; import java.io.File; +import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; /** @@ -29,6 +30,7 @@ public class SystemMediaSelector { private static final int REQUEST_CROP = 197; private static final int REQUEST_ALBUM = 198; private static final int REQUEST_FILE = 199; + private static final int REQUEST_LINRARY_CROP = 200; private static final String POSTFIX = ".file.provider"; private String mAuthority; @@ -37,11 +39,18 @@ public class SystemMediaSelector { private Activity mActivity; private Fragment mFragment; - private String mSavePhotoPath; - private CropOptions mCropOptions; private final CropOptions mDefaultOptions = new CropOptions(); private String mCropTitle; private boolean mNeedCrop; + private CropOptions mCropOptions; + + /*使用内部的裁剪库(推荐,系统裁剪兼容性差)*/ + private boolean mUseInnerCrop = true; + + /*用于保存图片*/ + private String mSavePhotoPath; + /*某些手机,拍照和裁剪不能是同一个路径*/ + private String mSavePhotoPathForCropCamera; public SystemMediaSelector(Activity activity, MediaSelectorCallback systemMediaSelectorCallback) { mActivity = activity; @@ -57,10 +66,10 @@ public class SystemMediaSelector { private void init() { mAuthority = getContext().getPackageName().concat(POSTFIX); - mDefaultOptions.setAspectX(1); - mDefaultOptions.setAspectY(1); - mDefaultOptions.setOutputX(1000); - mDefaultOptions.setOutputY(1000); + mDefaultOptions.setAspectX(0); + mDefaultOptions.setAspectY(0); + mDefaultOptions.setOutputX(0); + mDefaultOptions.setOutputY(0); } private Context getContext() { @@ -95,55 +104,50 @@ public class SystemMediaSelector { return mCropOptions == null ? mDefaultOptions : mCropOptions; } + public void setUseInnerCrop(boolean useInnerCrop) { + mUseInnerCrop = useInnerCrop; + } + /////////////////////////////////////////////////////////////////////////// - // Take method + // take photo from camera /////////////////////////////////////////////////////////////////////////// public boolean takePhotoFromCamera(String savePath) { mSavePhotoPath = savePath; mNeedCrop = false; - return toCamera(); + return toCamera(mSavePhotoPath); } /** * 为了保证裁裁剪图片不出问题,务必指定CropOptions中的各个参数(不要为0,比如魅族手机如果指定OutputX和OutputY为0,则只会裁减出一个像素),否则可能出现问题 */ - public boolean takePhotoFromCameraAndCrop(String savePath, CropOptions cropOptions, String cropTitle) { + public boolean takePhotoFromCameraAndCrop(String savePath, @Nullable CropOptions cropOptions, String cropTitle) { mSavePhotoPath = savePath; + mSavePhotoPathForCropCamera = Utils.addFilePostfix(mSavePhotoPath, "camera"); mNeedCrop = true; mCropOptions = cropOptions; mCropTitle = cropTitle; - return toCamera(); - } - - public boolean takePhotoFormAlbum() { - mNeedCrop = false; - try { - startActivityForResult(Utils.makeAlbumIntent(), REQUEST_ALBUM); - return true; - } catch (Exception e) { - return false; - } + return toCamera(mSavePhotoPathForCropCamera); } - public boolean takePhotoFormAlbumAndCrop(String savePhotoPath, CropOptions cropOptions, String cropTitle) { - mNeedCrop = true; - mSavePhotoPath = savePhotoPath; - mCropOptions = cropOptions; - mCropTitle = cropTitle; + private boolean toCropPhotoFromCamera() { + File targetFile = new File(mSavePhotoPath); + File srcFile = new File(mSavePhotoPathForCropCamera); + Intent intent = Utils.makeCropIntentNoCovering(getContext(), srcFile, targetFile, mAuthority, getCropOptions(), mCropTitle); try { - startActivityForResult(Utils.makeAlbumIntent(), REQUEST_ALBUM); + startActivityForResult(intent, REQUEST_CROP); return true; } catch (Exception e) { - return false; + Log.e(TAG, "toCropPhotoFromAlbum error", e); } + return false; } - private boolean toCamera() { + private boolean toCamera(String savePhotoPath) { if (!Utils.hasCamera(getContext())) { return false; } - File targetFile = new File(mSavePhotoPath); + File targetFile = new File(savePhotoPath); Intent intent = Utils.makeCaptureIntent(getContext(), targetFile, mAuthority); try { startActivityForResult(intent, REQUEST_CAMERA); @@ -154,30 +158,50 @@ public class SystemMediaSelector { return false; } - private boolean toCrop() { - File targetFile = new File(mSavePhotoPath); - Intent intent = Utils.makeCropIntent(getContext(), targetFile, mAuthority, getCropOptions(), mCropTitle); + /////////////////////////////////////////////////////////////////////////// + // take photo from album + /////////////////////////////////////////////////////////////////////////// + + public boolean takePhotoFormAlbum() { + mNeedCrop = false; try { - startActivityForResult(intent, REQUEST_CROP); + startActivityForResult(Utils.makeAlbumIntent(), REQUEST_ALBUM); return true; } catch (Exception e) { - Log.e(TAG, "toCrop error", e); + return false; + } + } + + public boolean takePhotoFormAlbumAndCrop(String savePhotoPath, @Nullable CropOptions cropOptions, String cropTitle) { + mNeedCrop = true; + mSavePhotoPath = savePhotoPath; + mCropOptions = cropOptions; + mCropTitle = cropTitle; + try { + startActivityForResult(Utils.makeAlbumIntent(), REQUEST_ALBUM); + return true; + } catch (Exception e) { + return false; } - return false; } - private boolean toCrop(Uri uri) { + private boolean toCropPhotoFromAlbum(Uri uri) { File targetFile = new File(mSavePhotoPath); - Intent intent = Utils.makeCropIntent(getContext(), uri, targetFile, mAuthority, getCropOptions(), mCropTitle); + File srcFile = new File(Utils.getAbsolutePath(getContext(), uri)); + Intent intent = Utils.makeCropIntentNoCovering(getContext(), srcFile, targetFile, mAuthority, getCropOptions(), mCropTitle); try { startActivityForResult(intent, REQUEST_CROP); return true; } catch (Exception e) { - Log.e(TAG, "toCrop error", e); + Log.e(TAG, "toCropPhotoFromAlbum error", e); } return false; } + /////////////////////////////////////////////////////////////////////////// + // take photo from album + /////////////////////////////////////////////////////////////////////////// + public boolean takeFile() { return takeFile(null); } @@ -192,6 +216,7 @@ public class SystemMediaSelector { } return true; } + /////////////////////////////////////////////////////////////////////////// // Process Result /////////////////////////////////////////////////////////////////////////// @@ -205,6 +230,17 @@ public class SystemMediaSelector { processAlbumResult(resultCode, data); } else if (requestCode == REQUEST_FILE) { processFileResult(resultCode, data); + } else if (requestCode == REQUEST_LINRARY_CROP) { + processUCropResult(data); + } + } + + private void processUCropResult(Intent data) { + Uri uCropResult = Utils.getUCropResult(data); + if (uCropResult == null) { + mMediaSelectorCallback.onTakeFail(); + } else { + mMediaSelectorCallback.onTakeSuccess(Utils.getAbsolutePath(getContext(), uCropResult)); } } @@ -227,9 +263,13 @@ public class SystemMediaSelector { if (data != null && data.getData() != null) { Uri uri = data.getData(); if (mNeedCrop) { - boolean success = toCrop(uri); - if (!success) { - mMediaSelectorCallback.onTakeSuccess(Utils.getAbsolutePath(getContext(), uri)); + if (mUseInnerCrop) { + Utils.toUCrop(getContext(), mFragment, Utils.getAbsolutePath(getContext(), uri), mSavePhotoPath, getCropOptions(), REQUEST_LINRARY_CROP); + } else { + boolean success = toCropPhotoFromAlbum(uri); + if (!success) { + mMediaSelectorCallback.onTakeSuccess(Utils.getAbsolutePath(getContext(), uri)); + } } } else { mMediaSelectorCallback.onTakeSuccess(Utils.getAbsolutePath(getContext(), uri)); @@ -260,18 +300,31 @@ public class SystemMediaSelector { private void processCameraResult(int resultCode, @SuppressWarnings("unused") Intent data) { if (resultCode == Activity.RESULT_OK) { - //检测图片是否被保存下来 - if (!new File(mSavePhotoPath).exists()) { - mMediaSelectorCallback.onTakeFail(); - return; - } //需要裁减,可以裁减则进行裁减,否则直接返回 if (mNeedCrop) { - boolean success = toCrop(); - if (!success) { - mMediaSelectorCallback.onTakeSuccess(mSavePhotoPath); + //检测图片是否被保存下来 + File photoPath = new File(mSavePhotoPathForCropCamera); + if (!photoPath.exists()) { + mMediaSelectorCallback.onTakeFail(); + return; } + + if (mUseInnerCrop) { + Utils.toUCrop(getContext(), mFragment, mSavePhotoPathForCropCamera, mSavePhotoPath, getCropOptions(), REQUEST_LINRARY_CROP); + } else { + boolean success = toCropPhotoFromCamera(); + if (!success) { + photoPath.renameTo(new File(mSavePhotoPath)); + mMediaSelectorCallback.onTakeSuccess(mSavePhotoPath); + } + } + } else { + //检测图片是否被保存下来 + if (!new File(mSavePhotoPath).exists()) { + mMediaSelectorCallback.onTakeFail(); + return; + } mMediaSelectorCallback.onTakeSuccess(mSavePhotoPath); } } @@ -291,5 +344,4 @@ public class SystemMediaSelector { } - -} +} \ No newline at end of file diff --git a/lib_media_selector/src/main/java/com/android/sdk/mediaselector/Utils.java b/lib_media_selector/src/main/java/com/android/sdk/mediaselector/Utils.java index cf9bf43..479e6e5 100644 --- a/lib_media_selector/src/main/java/com/android/sdk/mediaselector/Utils.java +++ b/lib_media_selector/src/main/java/com/android/sdk/mediaselector/Utils.java @@ -16,13 +16,18 @@ import android.os.Environment; import android.provider.DocumentsContract; import android.provider.MediaStore; import android.text.TextUtils; +import android.util.Log; + +import com.yalantis.ucrop.UCrop; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.List; +import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.FileProvider; +import androidx.fragment.app.Fragment; /** * See: @@ -31,11 +36,13 @@ import androidx.core.content.FileProvider; * * * @author Ztiany - * Email: ztiany3@gmail.com - * Date : 2017-08-09 10:54 + * Email: ztiany3@gmail.com + * Date : 2017-08-09 10:54 */ final class Utils { + private static final String TAG = "Utils"; + private Utils() { throw new UnsupportedOperationException("Utils"); } @@ -93,9 +100,9 @@ final class Utils { /** * @param targetFile 源文件,裁剪之后新的图片覆盖此文件 */ - static Intent makeCropIntent(Context context, File targetFile, String authority, CropOptions cropOptions, String title) { + static Intent makeCropIntentCovering(Context context, File targetFile, String authority, CropOptions cropOptions, String title) { + Log.d(TAG, "makeCropIntentCovering() called with: context = [" + context + "], targetFile = [" + targetFile + "], authority = [" + authority + "], cropOptions = [" + cropOptions + "], title = [" + title + "]"); - makeFilePath(targetFile); Intent intent = new Intent("com.android.camera.action.CROP"); Uri fileUri; @@ -117,29 +124,43 @@ final class Utils { intent.putExtra("return-data", false); intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()); intent.putExtra("noFaceDetection", true); + + List resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); + for (ResolveInfo resolveInfo : resInfoList) { + String packageName = resolveInfo.activityInfo.packageName; + context.grantUriPermission(packageName, fileUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + intent = Intent.createChooser(intent, title); return intent; } /** * @param targetFile 目标文件,裁剪之后新的图片保存到此文件 - * @param src 源文件 + * @param srcFile 源文件 */ - static Intent makeCropIntent(Context context, Uri src, File targetFile, String authority, CropOptions cropOptions, String title) { + static Intent makeCropIntentNoCovering(Context context, File srcFile, File targetFile, String authority, CropOptions cropOptions, String title) { + + makeFilePath(targetFile); Intent intent = new Intent("com.android.camera.action.CROP"); - intent.setDataAndType(src, "image/*"); - Uri fileUri; + Uri outputUri; + Uri srcUri; + if (Build.VERSION.SDK_INT < 24) { - fileUri = Uri.fromFile(targetFile); + outputUri = Uri.fromFile(targetFile); + srcUri = Uri.fromFile(srcFile); } else { - fileUri = FileProvider.getUriForFile(context, authority, targetFile); + outputUri = FileProvider.getUriForFile(context, authority, targetFile); + srcUri = FileProvider.getUriForFile(context, authority, srcFile); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); } - intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); + intent.setDataAndType(srcUri, "image/*"); + intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri); + intent.putExtra("aspectX", cropOptions.getAspectX()); intent.putExtra("aspectY", cropOptions.getAspectY()); intent.putExtra("outputX", cropOptions.getOutputX()); @@ -148,22 +169,83 @@ final class Utils { intent.putExtra("return-data", false); intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()); intent.putExtra("noFaceDetection", true); + + List resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); + for (ResolveInfo resolveInfo : resInfoList) { + String packageName = resolveInfo.activityInfo.packageName; + context.grantUriPermission(packageName, outputUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + intent = Intent.createChooser(intent, title); return intent; } /////////////////////////////////////////////////////////////////////////// - // Album + // UCrop /////////////////////////////////////////////////////////////////////////// + static void toUCrop(Context context, Fragment fragment, String srcPath, String targetPath, CropOptions cropConfig, int requestCode) { + Uri srcUri = new Uri.Builder() + .scheme("file") + .appendPath(srcPath) + .build(); + + Uri targetUri = new Uri.Builder() + .scheme("file") + .appendPath(targetPath) + .build(); + + //参数 + UCrop.Options crop = new UCrop.Options(); + crop.setCompressionFormat(Bitmap.CompressFormat.JPEG); + crop.withMaxResultSize(cropConfig.getOutputX(), cropConfig.getAspectY()); + crop.withAspectRatio(cropConfig.getAspectX(), cropConfig.getAspectY()); + + //颜色 + int color = StyleUtils.fetchPrimaryColor(context); + crop.setToolbarColor(color); + crop.setStatusBarColor(color); + + //开始裁减 + if (fragment != null) { + UCrop.of(srcUri, targetUri) + .withOptions(crop) + .start(context, fragment, requestCode); + } else { + if (!(context instanceof AppCompatActivity)) { + throw new IllegalArgumentException("the context must be instance of AppCompatActivity"); + } + UCrop.of(srcUri, targetUri) + .withOptions(crop) + .start((AppCompatActivity) context, requestCode); + } + } + + public static Uri getUCropResult(Intent data) { + if (data == null) { + return null; + } + Throwable throwable = UCrop.getError(data); + if (throwable != null) { + return null; + } + return UCrop.getOutput(data); + } + + /////////////////////////////////////////////////////////////////////////// + // Album + /////////////////////////////////////////////////////////////////////////// static Intent makeAlbumIntent() { return new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); } - /////////////////////////////////////////////////////////////////////////// // 从各种Uri中获取真实的路径 /////////////////////////////////////////////////////////////////////////// + + /** + * @see "https://stackoverflow.com/questions/20067508/get-real-path-from-uri-android-kitkat-new-storage-access-framework/20559175" + */ static String getAbsolutePath(final Context context, final Uri uri) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(context, uri)) { // ExternalStorageProvider @@ -213,14 +295,14 @@ final class Utils { } /** - * Get the value of the data column for this Uri. This is useful for - * MediaStore Uris, and other file-based ContentProviders. + * Get the value of the data column for this Uri. This is useful for MediaStore Uris, and other file-based ContentProviders. * * @param context The context. * @param uri The Uri to query. * @param selection (Optional) Filter used in the query. * @param selectionArgs (Optional) Selection arguments used in the query. * @return The value of the _data column, which is typically a file path. + * @see "https://stackoverflow.com/questions/20067508/get-real-path-from-uri-android-kitkat-new-storage-access-framework/20559175" */ private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { Cursor cursor = null; @@ -328,18 +410,6 @@ final class Utils { return returnBm; } - /////////////////////////////////////////////////////////////////////////// - // FileUtils - /////////////////////////////////////////////////////////////////////////// - - private static boolean makeFilePath(File file) { - if (file == null) { - return false; - } - File parent = file.getParentFile(); - return parent.exists() || parent.mkdirs(); - } - /////////////////////////////////////////////////////////////////////////// //如果照片保存的文件目录是由 getExternalFilesDir() 所提供的,那么,媒体扫描器是不能访问这些文件的,因为照片对于你的APP来说是私有的。 /////////////////////////////////////////////////////////////////////////// @@ -366,4 +436,68 @@ final class Utils { context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + photoPath))); } + /////////////////////////////////////////////////////////////////////////// + // FileUtils + /////////////////////////////////////////////////////////////////////////// + + private static boolean makeFilePath(File file) { + if (file == null) { + return false; + } + File parent = file.getParentFile(); + if (parent == null) { + return false; + } + return parent.exists() || parent.mkdirs(); + } + + private static void makeNewFile(File file) { + makeFilePath(file); + try { + if (file.exists()) { + file.delete(); + } + file.createNewFile(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static boolean isSpace(final String s) { + if (s == null) return true; + for (int i = 0, len = s.length(); i < len; ++i) { + if (!Character.isWhitespace(s.charAt(i))) { + return false; + } + } + return true; + } + + public static String getFileNameNoExtension(final String filePath) { + if (isSpace(filePath)) return ""; + int lastPoi = filePath.lastIndexOf('.'); + int lastSep = filePath.lastIndexOf(File.separator); + if (lastSep == -1) { + return (lastPoi == -1 ? filePath : filePath.substring(0, lastPoi)); + } + if (lastPoi == -1 || lastSep > lastPoi) { + return filePath.substring(lastSep + 1); + } + return filePath.substring(lastSep + 1, lastPoi); + } + + public static String getFileExtension(final String filePath) { + if (isSpace(filePath)) return ""; + int lastPoi = filePath.lastIndexOf('.'); + int lastSep = filePath.lastIndexOf(File.separator); + if (lastPoi == -1 || lastSep >= lastPoi) return ""; + return filePath.substring(lastPoi + 1); + } + + public static String addFilePostfix(final String filePath, String postfix) { + if (isSpace(filePath)) return ""; + File file = new File(filePath); + return file.getParentFile().getAbsolutePath() + File.separator + getFileNameNoExtension(filePath) + postfix + "." + getFileExtension(filePath); + } + } \ No newline at end of file