关于调用Android项目
关于Android中调用相机拍照、录像,调用相册选图等是比较繁琐的,为了减少代码冗余,肯定需要封装成工具类,最终使用大概如下,大部分代码使用Java
编写,因为需要照顾到不适用kotlin
的伸手党
mediaHelpe = new MediaBuilder().setImageMaxSelectedCount(5)//图片最大可选数量.setVideoMaxSelectedCount(2)//视频最大可选数量.setMediaMaxSelectedCount(6)//图片和视频同时选择的最大数量.setMaxVideoTime(30)//视频最大可拍摄时长,单位:秒.setShowLoading(true)//是否展示压缩等操作的dialog.setSavePublic(true)//是保存到App的私有目录还是公共目录.setFileMaxSelectedCount(9)//文件可选择的最大数量.setVideoQuality(VideoQualityEnum.HIGH)//视频压缩质量.setChooseType(MediaPickerTypeEnum.PICK)//选图类型,Pick新版的选图,Default是老版的,默认为老版.setWaterMark("仅测试使用")//图片水印.setMediaListener(new MediaListener() {@Overridepublic int onSelectedFileCount() {return fileList.size();}@Overridepublic int onSelectedAudioCount() {return audioList.size();}@Overridepublic int onSelectedImageCount() {return imageAddAdapter.getList().size();}@Overridepublic int onSelectedVideoCount() {return videoAddAdapter.getList().size();}@Overridepublic int onSelectedMediaCount() {return mediaAddAdapter.getList().size();}})//监听已选数量.setImageQualityCompress(200)//图片压缩质量,单位为k.bindLifeCycle(this)//绑定生命周期,这个必须调用.builder();//创建MediaHelper类
选择结果的回调
//图片、视频选择结果回调通知mediaHelper.getMutableLiveData().observe(this, mediaBean -> {if (mediaBean.getMediaType() == MediaTypeEnum.IMAGE) {if (mediaBean.getMediaList() != null && mediaBean.getMediaList().size() > 0) {binding.setSourceImagePath(mediaBean.getMediaList().get(0));}imageAddAdapter.getList().addAll(AttachmentUtil.uriListToAttachmentList(mediaBean.getMediaList()));imageAddAdapter.notifyDataSetChanged();binding.tvImage.setText("图片选择(" + imageAddAdapter.getList().size() + "/" + mediaHelper.getMediaBuilder().getImageMaxSelectedCount() + ")");} else if (mediaBean.getMediaType() == MediaTypeEnum.VIDEO) {videoAddAdapter.getList().addAll(AttachmentUtil.uriListToAttachmentList(mediaBean.getMediaList()));videoAddAdapter.notifyDataSetChanged();binding.tvVideo.setText("视频选择(" + videoAddAdapter.getList().size() + "/" + mediaHelper.getMediaBuilder().getVideoMaxSelectedCount() + ")");} else if (mediaBean.getMediaType() == MediaTypeEnum.IMAGE_AND_VIDEO) {mediaAddAdapter.getList().addAll(AttachmentUtil.uriListToAttachmentList(mediaBean.getMediaList()));mediaAddAdapter.notifyDataSetChanged();binding.tvImageVideo.setText("图片、视频混合选择(" + mediaAddAdapter.getList().size() + "/" + mediaHelper.getMediaBuilder().getMediaMaxSelectedCount() + ")");} else if (mediaBean.getMediaType() == MediaTypeEnum.AUDIO) {audioList.clear();audioList.addAll(coverUriToString(mediaBean.getMediaList()));binding.chooseAudioResult.setText("音频选择结果:" + new Gson().toJson(audioList));} else if (mediaBean.getMediaType() == MediaTypeEnum.FILE) {fileList.clear();fileList.addAll(coverUriToString(mediaBean.getMediaList()));binding.chooseFileResult.setText("文件选择结果:" + new Gson().toJson(fileList));}});
封装思路
框架代码地址:https://github.com/fzkf9225/mvvm-componnent-master/blob/master/commonmedia/src/main/java/pers/fz/media/MediaHelper.java
- 回调结果 ,先定义回调结果,我想要什么样的回调数据;
- 定义可配置参数,比如我们想要哪些数据可以配置,先定义一个
MediaBuilder
; - 请求权限,我们调用相机等,肯定需要请求系统权限等,所以我们不可能在使用工具类前再写一次请求权限,因此需要写在工具类内部;
- 选图、选择拍照、录像等dialog样式,仿照微信的样式类似的结果,点击选图拍照前可以展示一个dialog然后选择
拍照
录像
相册选图
等等模式; - 压缩图片、视频等工具,拍照、选图后可能需要压缩,因为原图可能会很大很大,这个功能我们改为自定义的形式,可选择的方式;
- 拓展功能,我们可以加入一些水印等功能加入进去,或者我们自己可以封装一个九宫格按钮;
回调结果定义
我们顶一个是个实体类MediaBean
public class MediaBean {private List<Uri> mediaList;/*** 0-图片,1-视频等*/private String mediaType;public MediaBean() {}public MediaBean(List<Uri> mediaList, String mediaType) {this.mediaList = mediaList;this.mediaType = mediaType;}public List<Uri> getMediaList() {return mediaList;}public void setMediaList(List<Uri> mediaList) {this.mediaList = mediaList;}public String getMediaType() {return mediaType;}public void setMediaType(String mediaType) {this.mediaType = mediaType;}
}
mediaType
为文件类型:比如图片
、视频
、音频
、其他文档
等等,但是如果定义成这样的话,每次回调都要写int或者字符串,因此我们将他修改为枚举
,这样不会出现错别字和写错的情况
新建MediaTypeEnum
public enum MediaTypeEnum {/** 图片*/IMAGE(1),/** 视频*/VIDEO(2),/** 音频*/AUDIO(3),/** 文件、包含所有可选类型的文件,具体那些可选会根据系统api有一定的区别*/FILE(0),/** 未知类型*/OTHER(4),/** 图片和视频混合一起的*/IMAGE_AND_VIDEO(5);private final int mediaType;MediaTypeEnum(int mediaType) {this.mediaType = mediaType;}public int getMediaType() {return mediaType;}/*** 根据文件类型、文件地址获取文件类型** @param context 上下文* @param uri uri地址* @return 文件类型枚举*/public static MediaTypeEnum getMediaType(Context context, Uri uri) {if (uri == null) {return null;}if (context == null || context.getContentResolver() == null) {return null;}String type = context.getContentResolver().getType(uri);if (!TextUtils.isEmpty(type) && (type.startsWith("image") || type.startsWith("IMAGE"))) {return MediaTypeEnum.IMAGE;} else if ((!TextUtils.isEmpty(type)) && (type.startsWith("video") || type.startsWith("VIDEO"))) {return MediaTypeEnum.VIDEO;} else {return MediaTypeEnum.FILE;}}public static MediaTypeEnum getMediaType(Integer type) {if (type == null) {return OTHER;}for (MediaTypeEnum value : values()) {if (value.mediaType == type) {return value;}}return OTHER;}
}
修改下原来的MediaBean
public class MediaBean {private List<Uri> mediaList;/*** 0-图片,1-视频,具体值参考枚举MediaType*/private MediaTypeEnum mediaType;public MediaBean() {}public MediaBean(List<Uri> mediaList, MediaTypeEnum mediaType) {this.mediaList = mediaList;this.mediaType = mediaType;}public List<Uri> getMediaList() {return mediaList;}public void setMediaList(List<Uri> mediaList) {this.mediaList = mediaList;}public MediaTypeEnum getMediaType() {return mediaType;}public void setMediaType(MediaTypeEnum mediaType) {this.mediaType = mediaType;}}
定义可配置参数
我们使用Builder
模式,新建一个类MediaBuilder
里面添加一些你想配置的参数和方法,代码后面再补,因为我们现在还不知道需要什么
public class MediaBuilder {/*** 创建MediaHelper对象*/public MediaHelper builder() {return new MediaHelper(this);}
}
请求权限
我们使用相机、文件管理器、读写等必须要请求权限,我们不需要使用第三方库,自己写。
之前的startActivityForResult
已经被标记为过时,所以我们不用,我们使用Geogle推荐的registerForActivityResult
去请求权限,但是registerForActivityResult
必须写在oncreate中或者之前,因为我们需要监听到activity或者fragment的生命周期,我们使用Geogle
推荐的LifeCycle
的接口DefaultLifecycleObserver
,我们新建一个类去实现这个接口
public class MediaLifecycleObserver implements DefaultLifecycleObserver {private final MediaHelper mediaHelper;private ActivityResultLauncher<String[]> permissionLauncher = null;}
在重写onCreate
生命周期
@Overridepublic void onCreate(@NonNull LifecycleOwner owner) {DefaultLifecycleObserver.super.onCreate(owner);ActivityResultRegistry registry;if (owner instanceof ComponentActivity) {registry = ((ComponentActivity) owner).getActivityResultRegistry();} else if (owner instanceof Fragment) {registry = ((Fragment) owner).requireActivity().getActivityResultRegistry();} else {throw new RuntimeException("请使用Activity或Fragment的lifecycle对象");}//注册权限permissionLauncher = registry.register("permission" + UUID.randomUUID().toString(), owner, new ActivityResultContracts.RequestMultiplePermissions(), permissionCallback);}/*** 权限回调,这个dialog代码里有,很普通就不贴代码了*/ActivityResultCallback<Map<String, Boolean>> permissionCallback = new ActivityResultCallback<>() {@Overridepublic void onActivityResult(Map<String, Boolean> result) {for (Map.Entry<String, Boolean> entry : result.entrySet()) {LogUtil.show(MediaHelper.TAG, entry.getKey() + ":" + entry.getValue());if (Boolean.FALSE.equals(entry.getValue())) {new TipDialog(mediaHelper.getMediaBuilder().getContext()).setMessage("您拒绝了当前权限,可能导致无法使用该功能,可前往设置修改").setNegativeText("取消").setPositiveText("前往设置").setOnPositiveClickListener(dialog -> {dialog.dismiss();Intent intent = new Intent();intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);Uri uri = Uri.fromParts("package", mediaHelper.getMediaBuilder().getContext().getPackageName(), null);intent.setData(uri);mediaHelper.getMediaBuilder().getContext().startActivity(intent);}).builder().show();return;}}}};@Overridepublic void onDestroy(@NonNull LifecycleOwner owner) {DefaultLifecycleObserver.super.onDestroy(owner);if (permissionLauncher != null) {permissionLauncher.unregister();}mediaHelper.getMediaBuilder().getLifecycleOwner().getLifecycle().removeObserver(this);}
但是使用这个DefaultLifecycleObserver
前提是需要LifeCycle
对象,因此我们可以在MediaBuilder
中新增参数和方法
/*** lifecycle绑定*/private LifecycleOwner lifecycleOwner;public MediaBuilder bindLifeCycle(LifecycleOwner lifecycleOwner) {this.lifecycleOwner = lifecycleOwner;return this;}/*** 获取LifeCycle* @return LifecycleOwner*/public LifecycleOwner getLifecycleOwner() {return lifecycleOwner;}/*** 创建MediaHelper对象*/public MediaHelper builder() {if (lifecycleOwner == null) {throw new RuntimeException("please bind lifecycle");}return new MediaHelper(this);}
那么我们现在需要知道请求哪些权限:相机
、录音
、读写文件
、访问媒体库
等,但是Android版本区别很大,需要编写各个版本的权限,所以我们写个静态参数类
public class ConstantsHelper {public final static String[] PERMISSIONS_CAMERA = new String[]{Manifest.permission.CAMERA,Manifest.permission.RECORD_AUDIO,Manifest.permission.WRITE_EXTERNAL_STORAGE};public final static String[] PERMISSIONS_CAMERA_R = new String[]{Manifest.permission.CAMERA,Manifest.permission.RECORD_AUDIO,};public final static String[] PERMISSIONS_READ = new String[]{Manifest.permission.READ_EXTERNAL_STORAGE};public final static String[] PERMISSIONS_IMAGE_READ_TIRAMISU = new String[]{Manifest.permission.READ_MEDIA_IMAGES,};public final static String[] PERMISSIONS_VIDEO_READ_TIRAMISU = new String[]{Manifest.permission.READ_MEDIA_VIDEO};public final static String[] PERMISSIONS_AUDIT_READ_TIRAMISU = new String[]{Manifest.permission.READ_MEDIA_AUDIO,};public final static String[] PERMISSIONS_IMAGE_VIDEO_TEMP = new String[]{Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED};public final static String[] PERMISSIONS_IMAGE_READ_UPSIDE_DOWN_CAKE = new String[]{Manifest.permission.READ_MEDIA_IMAGES,Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED};public final static String[] PERMISSIONS_VIDEO_READ_UPSIDE_DOWN_CAKE = new String[]{Manifest.permission.READ_MEDIA_VIDEO,Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED};
}
所以我们现在完善一下MediaLifecycleObserver
,我们先以拍照
和选图
为例
private ActivityResultLauncher<MediaTypeEnum> cameraLauncher = null;//新版多选图片,因为多选有个参数初始化必须大于1,因此和单选必须分开写private ActivityResultLauncher<PickVisualMediaRequest> pickMuLtiImageSelectorLauncher = null;private ActivityResultLauncher<PickVisualMediaRequest> pickImageSelectorLauncher = null;//老版本的选图private ActivityResultLauncher<String[]> singleLauncher = null;private ActivityResultLauncher<String[]> multiLauncher = null;@Overridepublic void onCreate(@NonNull LifecycleOwner owner) {DefaultLifecycleObserver.super.onCreate(owner);pickMuLtiImageSelectorLauncher = registry.register("pickMuLtiImageSelector" + UUID.randomUUID().toString(), owner, new ActivityResultContracts.PickMultipleVisualMedia(9),回调方法);pickImageSelectorLauncher = registry.register("pickImageSelector" + UUID.randomUUID().toString(), owner, new ActivityResultContracts.PickVisualMedia(),回调方法);//传统选择器singleLauncher = registry.register("singleSelector" + UUID.randomUUID().toString(), owner, new ActivityResultContracts.OpenDocuments(),回调方法);//传统选择器,多选multiLauncher = registry.register("multiSelector" + UUID.randomUUID().toString(), owner, new ActivityResultContracts.OpenMultipleDocuments(),回调方法); }
回调方法比较简单咱写不写了,你们可以写
选图、选择拍照、录像等dialog样式
这些比较简单,就暂时提供一个代码,其他的可以看源码
public class OpenImageDialog extends Dialog implements View.OnClickListener {private OnOpenImageClickListener onOpenImageClickListener;/*** 只显示拍照*/public final static int CAMERA = 1;/*** 只显示相册*/public final static int ALBUM = 2;/*** 相册和拍照都显示*/public final static int CAMERA_ALBUM = 3;private int mediaType = CAMERA_ALBUM;public OpenImageDialog(@NonNull Context context) {super(context, R.style.ActionSheetDialogStyle);}public OpenImageDialog(@NonNull Context context, int themeResId) {super(context, themeResId);}public OpenImageDialog setMediaType(int mediaType) {this.mediaType = mediaType;return this;}public OpenImageDialog builder() {initView();return this;}private CameraAlbumDialogBinding binding;public CameraAlbumDialogBinding getBinding() {return binding;}private void initView() {binding = CameraAlbumDialogBinding.inflate(getLayoutInflater(), null, false);if (mediaType == CAMERA) {binding.choosePhoto.setVisibility(View.GONE);binding.vLine.setVisibility(View.GONE);} else if (mediaType == ALBUM) {binding.takePhoto.setVisibility(View.GONE);binding.vLine.setVisibility(View.GONE);}binding.buttonCancel.setOnClickListener(view -> dismiss());binding.choosePhoto.setOnClickListener(this);binding.takePhoto.setOnClickListener(this);setContentView(binding.getRoot());Window dialogWindow = getWindow();if (dialogWindow == null) {return;}dialogWindow.setLayout(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);dialogWindow.setGravity(Gravity.BOTTOM);WindowManager.LayoutParams lp = dialogWindow.getAttributes();lp.y = 20;dialogWindow.setAttributes(lp);}@Overridepublic void onClick(View v) {if (isShowing()) {dismiss();}int id = v.getId();if (id == R.id.choosePhoto) {if (mediaType == CAMERA) {Toast.makeText(getContext(), "暂不支持拍照!", Toast.LENGTH_SHORT).show();return;}if (onOpenImageClickListener != null) {onOpenImageClickListener.imageClick(ALBUM);}} else if (id == R.id.takePhoto) {if (mediaType == ALBUM) {Toast.makeText(getContext(), "暂不支持打开相册!", Toast.LENGTH_SHORT).show();return;}if (onOpenImageClickListener != null) {onOpenImageClickListener.imageClick(CAMERA);}}}public OpenImageDialog setOnOpenImageClickListener(OnOpenImageClickListener onOpenImageClickListener) {this.onOpenImageClickListener = onOpenImageClickListener;return this;}public interface OnOpenImageClickListener {void imageClick(int mediaType);}
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"><data></data><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:layout_gravity="center_horizontal"android:orientation="vertical"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:background="@drawable/rounded_white"android:orientation="vertical"><androidx.appcompat.widget.AppCompatButtonandroid:id="@+id/takePhoto"android:layout_width="match_parent"android:layout_height="@dimen/height_xxl"android:background="@null"android:gravity="center"android:text="@string/camera"android:textColor="@color/autoColor"android:textSize="@dimen/font_size_xxl" /><Viewandroid:id="@+id/v_line"style="@style/h_line" /><androidx.appcompat.widget.AppCompatButtonandroid:id="@+id/choosePhoto"android:layout_width="match_parent"android:layout_height="@dimen/height_xxl"android:background="@null"android:gravity="center"android:text="@string/album"android:textColor="@color/autoColor"android:textSize="@dimen/font_size_xxl" /></LinearLayout><androidx.appcompat.widget.AppCompatButtonandroid:id="@+id/button_cancel"android:layout_width="match_parent"android:layout_height="@dimen/height_xxl"android:layout_marginTop="@dimen/vertical_margin_xl"android:background="@drawable/rounded_white"android:gravity="center"android:text="@string/cancel"android:textColor="@color/autoColor"android:textSize="@dimen/font_size_xl" /></LinearLayout>
</layout>
正式开始封装
在某个按钮等条件触发下,然后展示dialog,选图
还是拍照
,然后调用上面的代码,组合起来
- 先请求权限
- 调用选图、拍照等
- 监听回调
在MediaHelper
中编写
public void openImageDialog(View v, int mediaType) {new OpenImageDialog(v.getContext()).setMediaType(mediaType).setOnOpenImageClickListener(this).builder().show();}@Overridepublic void imageClick(int mediaType) {if (OpenImageDialog.ALBUM == mediaType) {openImg();} else if (OpenImageDialog.CAMERA == mediaType) {camera();}}
选图
我们先看下如何选图
新版选图需要请求权限,可以直接调用,都是Geogle
的Api,不解释了
if (mediaBuilder.getImageMaxSelectedCount() == 1) {mediaLifecycleObserver.getPickImageSelectorLauncher().launch(new PickVisualMediaRequest.Builder().setMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly.INSTANCE).build());} else {mediaLifecycleObserver.getPickMuLtiImageSelectorLauncher().launch(new PickVisualMediaRequest.Builder().setMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly.INSTANCE).build());}
老板的选图
//先请求权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {if (lacksPermissions(mediaBuilder.getContext(), ConstantsHelper.PERMISSIONS_IMAGE_READ_UPSIDE_DOWN_CAKE)) {checkPermission(ConstantsHelper.PERMISSIONS_IMAGE_READ_UPSIDE_DOWN_CAKE);return;}} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.TIRAMISU) {if (lacksPermissions(mediaBuilder.getContext(), ConstantsHelper.PERMISSIONS_IMAGE_READ_TIRAMISU)) {checkPermission(ConstantsHelper.PERMISSIONS_IMAGE_READ_TIRAMISU);return;}} else {if (lacksPermissions(mediaBuilder.getContext(), ConstantsHelper.PERMISSIONS_READ)) {checkPermission(ConstantsHelper.PERMISSIONS_READ);return;}}
//开始选图if (mediaBuilder.getImageMaxSelectedCount() == 1) {mediaLifecycleObserver.getSingleLauncher().launch(new String[]{"*/*"});} else {mediaLifecycleObserver.getMultiLauncher().launch(new String[]{"*/*"});}
监听回调
回调我们需要实现接口ActivityResultCallback<Uri>
,以单选为例
@Overridepublic void onActivityResult(Uri result) {LogUtil.show(MediaHelper.TAG, "单选回调:" + result);}
回调我们需要可以自行选择,以MutableLiveData
为例
mutableLiveData.postValue(new MediaBean(List.of(result), MediaTypeEnum.IMAGE));
OK!!!这样我们选择的流程基本完成了,我们现在补充拍照的逻辑
拍照
先请求权限
/*** 打开摄像机*/public void camera() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {if (lacksPermissions(mediaBuilder.getContext(), ConstantsHelper.PERMISSIONS_CAMERA_R)) {checkPermission(ConstantsHelper.PERMISSIONS_CAMERA_R);return;}} else {if (lacksPermissions(mediaBuilder.getContext(), ConstantsHelper.PERMISSIONS_CAMERA)) {checkPermission(ConstantsHelper.PERMISSIONS_CAMERA);return;}}//调用相机方法待会再补}
我们需要补充调用相机的launcher
cameraLauncher = registry.register("camera" + UUID.randomUUID().toString(), owner, new ActivityResultContract<Object, Uri>,回调方法)
这样我们就可以简单的调用相机了
mediaLifecycleObserver.getCameraLauncher().launch(null);
但是这样我们太简单了,我们无法自定义保存的路径等等,需要我们自己写个ActivityResultContract<Object, Uri>
替换上面的
public class TakeCameraUri extends ActivityResultContract<Object , Uri> {/*** 拍照返回的uri*/private Uri uri;private final MediaBuilder mediaBuilder;public TakeCameraUri(MediaBuilder mediaBuilder) {this.mediaBuilder = mediaBuilder;}@NonNull@Overridepublic Intent createIntent(@NonNull Context context, Object input) {return new Intent(MediaStore.ACTION_IMAGE_CAPTURE).putExtra(MediaStore.EXTRA_OUTPUT, uri);}/*** 下面注释代码是正常的,但是由于视频录像的时候返回的resultCode有问题,这里虽然没有问题,但是担心兼容性不好,索性就不判断了*/@Overridepublic Uri parseResult(int resultCode, @Nullable Intent intent) {LogUtil.show(MediaHelper.TAG, "拍照回调resultCode:" + resultCode);return uri;}
}
我们现在修改下createIntent
,修改保存路径,那么我们在MediaBuilder
添加一些参数,定义路径
/*** 图片子目录*/private String imageSubPath;/*** 是否将拍摄的图片和视频保存到公共目录,默认false*/private boolean savePublicPath = true;/*** 是否保存到公共目录* true代表保存到公共目录*/public MediaBuilder setSavePublic(boolean isPublic) {this.savePublicPath = isPublic;return this;}/*** 设置图片子目录* @param subPath 子目录地址* @return 新的父路径*/public MediaBuilder setDefaultImageSubPath(String subPath) {imageSubPath = subPath;return this;}
由于Geogle限制,现在拍照选图等都基本上推荐使用Uri
,所以我们也不使用绝对路径,那么我们兼容下版本修改下createIntent
@NonNull@Overridepublic Intent createIntent(@NonNull Context context, MediaTypeEnum input) {this.mediaTypeEnum = input;String mimeType = "image/jpeg";String fileName = "IMAGE_" + System.currentTimeMillis() + ".jpg";if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {//如果保存到公共目录if (mediaBuilder.isSavePublicPath()) {ContentValues values = new ContentValues();values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);//如果没有设置子目录的话,则默认取包名if (TextUtils.isEmpty(mediaBuilder.getImageSubPath())) {values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + File.separator + MediaUtil.getDefaultBasePath(context) + File.separator + "image");} else {values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + File.separator +mediaBuilder.getImageSubPath());}uri = context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);} else {File file = new File(mediaBuilder.getImageOutPutPath()+File.separator + fileName);if(file.getParentFile() != null && !file.getParentFile().exists()){boolean isCreated = file.getParentFile().mkdirs();}uri = FileProvider.getUriForFile(context, context.getPackageName() + ".FileProvider",file);}} else {File file = new File(mediaBuilder.getImageOutPutPath()+File.separator + fileName);if(file.getParentFile() != null && !file.getParentFile().exists()){boolean isCreated = file.getParentFile().mkdirs();}uri = FileProvider.getUriForFile(context, context.getPackageName() + ".FileProvider",file);}return new Intent(MediaStore.ACTION_IMAGE_CAPTURE).putExtra(MediaStore.EXTRA_OUTPUT, uri);}
现在监听拍照回调即可,回调跟上面的基本一致,所以没啥好说的,拍照选图基本结束,那么现在问题是录像呢?
调用相机录像
public void openShootDialog(View v, int mediaType) {new OpenShootDialog(v.getContext()).setMediaType(mediaType).setOnOpenVideoClickListener(this).builder().show();}@Overridepublic void shootClick(int mediaType) {if (isMoreThanMaxVideo()) {return;}if (OpenShootDialog.ALBUM == mediaType) {openShoot();} else if (OpenShootDialog.CAMERA == mediaType) {shoot();}}/*** 打开视频资源选择库*/public void openShoot(String[] customVideoType) {if (mediaBuilder.getChooseType() == MediaPickerTypeEnum.PICK) {if (mediaBuilder.getVideoMaxSelectedCount() == 1) {mediaLifecycleObserver.getPickVideoSelectorLauncher().launch(new PickVisualMediaRequest.Builder().setMediaType(ActivityResultContracts.PickVisualMedia.VideoOnly.INSTANCE).build());} else {mediaLifecycleObserver.getPickMuLtiVideoSelectorLauncher().launch(new PickVisualMediaRequest.Builder().setMediaType(ActivityResultContracts.PickVisualMedia.VideoOnly.INSTANCE).build());}} else {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {if (lacksPermissions(mediaBuilder.getContext(), ConstantsHelper.PERMISSIONS_VIDEO_READ_UPSIDE_DOWN_CAKE)) {checkPermission(ConstantsHelper.PERMISSIONS_VIDEO_READ_UPSIDE_DOWN_CAKE);return;}} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.TIRAMISU) {if (lacksPermissions(mediaBuilder.getContext(), ConstantsHelper.PERMISSIONS_VIDEO_READ_TIRAMISU)) {checkPermission(ConstantsHelper.PERMISSIONS_VIDEO_READ_TIRAMISU);return;}} else {if (lacksPermissions(mediaBuilder.getContext(), ConstantsHelper.PERMISSIONS_READ)) {checkPermission(ConstantsHelper.PERMISSIONS_READ);return;}}if (mediaBuilder.getVideoMaxSelectedCount() == 1) {mediaLifecycleObserver.getSingleLauncher().launch(new String[]{"video/*"});} else {mediaLifecycleObserver.getMultiLauncher().launch(new String[]{"video/*"});}}}
最难的地方来了,那就是调用相机录像,先增加参数,在MediaBuilder
中
/*** 视频子目录*/private String videoSubPath;/*** 设置视频拍摄时长,单位:秒*/public int maxVideoTime = 30;/*** 视频最大时长* @param maxVideoTime 单位秒* @return this*/public MediaBuilder setMaxVideoTime(int maxVideoTime) {this.maxVideoTime = maxVideoTime;return this;}/*** 设置视频子目录地址* @param subPath 子目录地址* @return 新的父路径*/public MediaBuilder setDefaultVideoSubPath(String subPath) {if (TextUtils.isEmpty(subPath)) {return this;}videoSubPath = subPath;return this;}
跟调用相机同理,我们先自定义一个ActivityResultContract
其他没啥变化,主要区别在于createIntent
@NonNull@Overridepublic Intent createIntent(@NonNull Context context, MediaTypeEnum input) {this.mediaType = input;String mimeType = "video/mp4";String fileName = "VIDEO_" + System.currentTimeMillis() + ".mp4";if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {//如果保存到公共目录if (mediaBuilder.isSavePublicPath()) {ContentValues values = new ContentValues();values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);//如果没有设置子目录的话,则默认取包名if (TextUtils.isEmpty(mediaBuilder.getVideoSubPath())) {values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + File.separator + MediaUtil.getDefaultBasePath(context) + File.separator + "video");} else {values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + File.separator +mediaBuilder.getVideoSubPath());}uri = context.getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);} else {File file = new File(mediaBuilder.getVideoOutPutPath() + File.separator + fileName);if (file.getParentFile() != null && !file.getParentFile().exists()) {boolean isCreated = file.getParentFile().mkdirs();}uri = FileProvider.getUriForFile(context, context.getPackageName() + ".FileProvider", file);}} else {File file = new File(mediaBuilder.getVideoOutPutPath() + File.separator + fileName);if (file.getParentFile() != null && !file.getParentFile().exists()) {boolean isCreated = file.getParentFile().mkdirs();}uri = FileProvider.getUriForFile(context, context.getPackageName() + ".FileProvider", file);}return new Intent(MediaStore.ACTION_VIDEO_CAPTURE)// 视频质量。0 低质量;1 高质量.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1)// 时长限制,单位秒.putExtra(MediaStore.EXTRA_DURATION_LIMIT, durationLimit).putExtra(MediaStore.EXTRA_OUTPUT, uri);}
监听回到基本就不讲了,所以基本流程已经全部结束,我们可以再增加一些参数配置,比如:限制最大可选择的图片数量、限制选图的文件格式、是选择新版的选图还是旧版
MediaBuilder
代码
选择器类型,默认为DEFAULT,枚举MediaPickerTypeEnum*/private MediaPickerTypeEnum chooseType = MediaPickerTypeEnum.DEFAULT;public MediaBuilder(@NotNull Context context) {this.mContext = context;}public int getAudioMaxSelectedCount() {return audioMaxSelectedCount;}public MediaBuilder setAudioMaxSelectedCount(int audioMaxSelectedCount) {this.audioMaxSelectedCount = audioMaxSelectedCount;return this;}public Context getContext() {return mContext;}public void setContext(Context mContext) {this.mContext = mContext;}/*** 是否展示请求权限前的dialog* @return true展示,默认展示*/public boolean isShowPermissionDialog() {return isShowPermissionDialog;}/*** 设置是否展示请求权限前的dialog* @param showPermissionDialog true展示,默认展示* @return MediaBuilder*/public MediaBuilder setShowPermissionDialog(boolean showPermissionDialog) {isShowPermissionDialog = showPermissionDialog;return this;}/*** 获取请求权限前的dialog取消按钮文字内容* @return 确认按钮文字内容*/public String getPermissionNegativeText() {return permissionNegativeText;}/*** 设置请求权限前的dialog确认按钮文字内容* @param permissionNegativeText 确认按钮文字内容* @return MediaBuilder*/public MediaBuilder setPermissionNegativeText(String permissionNegativeText) {this.permissionNegativeText = permissionNegativeText;return this;}/*** 获取请求权限前的dialog确认按钮文字内容* @return 确认按钮文字内容*/public String getPermissionPositiveText() {return permissionPositiveText;}/*** 获取请求权限前的dialog确认按钮文字内容* @param permissionPositiveText 确认按钮文字内容* @return MediaBuilder*/public MediaBuilder setPermissionPositiveText(String permissionPositiveText) {this.permissionPositiveText = permissionPositiveText;return this;}/*** 获取请求权限前的dialog提示内容,优先级高于permissionMessage* @return 确认按钮文字颜色*/public SpannableString getPermissionSpannableContent() {return permissionSpannableContent;}/*** 设置请求权限前的dialog提示内容,优先级高于permissionMessage* @param permissionSpannableContent 确认按钮文字颜色* @return MediaBuilder*/public MediaBuilder setPermissionSpannableContent(SpannableString permissionSpannableContent) {this.permissionSpannableContent = permissionSpannableContent;return this;}/*** 获取请求权限前的dialog提示文字,优先级低于permissionSpannableContent* @return 确认按钮文字颜色*/public String getPermissionMessage() {return permissionMessage;}/*** 设置请求权限前的dialog提示文字,优先级低于permissionSpannableContent* @param permissionMessage 确认按钮文字颜色* @return MediaBuilder*/public MediaBuilder setPermissionMessage(String permissionMessage) {this.permissionMessage = permissionMessage;return this;}/*** 获取请求权限前的dialog确认按钮点击事件* @return 确认按钮点击事件*/public OnDialogInterfaceClickListener getOnPermissionPositiveClickListener() {return onPermissionPositiveClickListener;}/*** 设置请求权限前的dialog确认按钮点击事件* @param onPermissionPositiveClickListener 点击事件* @return this*/public MediaBuilder setOnPermissionPositiveClickListener(OnDialogInterfaceClickListener onPermissionPositiveClickListener) {this.onPermissionPositiveClickListener = onPermissionPositiveClickListener;return this;}/*** 获取请求权限前的dialog取消按钮点击事件* @return 取消按钮点击事件*/public OnDialogInterfaceClickListener getOnPermissionNegativeClickListener() {return onPermissionNegativeClickListener;}/*** 设置请求权限前的dialog取消按钮点击事件* @param onPermissionNegativeClickListener 点击事件* @return this*/public MediaBuilder setOnPermissionNegativeClickListener(OnDialogInterfaceClickListener onPermissionNegativeClickListener) {this.onPermissionNegativeClickListener = onPermissionNegativeClickListener;return this;}/*** 获取请求权限前的dialog确认按钮文字颜色* @return 确认按钮文字颜色*/public @ColorInt int getPermissionPositiveTextColor() {return permissionPositiveTextColor;}/*** 获取请求权限前的dialog确认按钮文字颜色*/public MediaBuilder setPermissionPositiveTextColor(@ColorInt int permissionPositiveTextColor) {this.permissionPositiveTextColor = permissionPositiveTextColor;return this;}/*** 获取请求权限前的dialog取消按钮文字颜色* @return 取消按钮文字颜色*/public @ColorInt int getPermissionNegativeTextColor() {return permissionNegativeTextColor;}/*** 设置请求权限前的dialog取消按钮文字颜色* @param permissionNegativeTextColor 确认按钮文字颜色* @return this*/public MediaBuilder setPermissionNegativeTextColor(@ColorInt int permissionNegativeTextColor) {this.permissionNegativeTextColor = permissionNegativeTextColor;return this;}/*** 最大可选文件数量* @return 最大可选文件数量*/public int getFileMaxSelectedCount() {return fileMaxSelectedCount;}/*** 设置最大可选文件数量* @param fileMaxSelectedCount 最大可选文件数量* @return this*/public MediaBuilder setFileMaxSelectedCount(int fileMaxSelectedCount) {this.fileMaxSelectedCount = fileMaxSelectedCount;return this;}/*** 最大可选图片、视频数量* @return 最大可选图片、视频数量*/public int getMediaMaxSelectedCount() {return mediaMaxSelectedCount;}/*** 设置最大可选图片、视频数量* @param mediaMaxSelectedCount 最大可选图片、视频数量* @return this*/public MediaBuilder setMediaMaxSelectedCount(int mediaMaxSelectedCount) {this.mediaMaxSelectedCount = mediaMaxSelectedCount;return this;}/*** 设置图片子目录* @param subPath 子目录地址* @return 新的父路径*/public MediaBuilder setDefaultImageSubPath(String subPath) {imageSubPath = subPath;return this;}/*** 文件选择器的文件类型* @return 数组*/public String[] getFileType() {return fileType;}/*** 设置文件类型,file选择时有效* @param fileType 默认为所有* @return this*/public MediaBuilder setFileType(String[] fileType) {this.fileType = fileType;return this;}/*** 音频选择器的文件类型* @return 数组*/public String[] getAudioType() {return audioType;}/*** 设置音频类型,audio选择时有效* @param audioType 默认为所有* @return this*/public MediaBuilder setAudioType(String[] audioType) {this.audioType = audioType;return this;}/*** 视频选择器的文件类型* @return 数组*/public String[] getVideoType() {return videoType;}/*** 设置视频类型,video选择时有效* @param videoType 默认为所有* @return this*/public MediaBuilder setVideoType(String[] videoType) {this.videoType = videoType;return this;}/*** 图片选择器的文件类型* @return 数组*/public String[] getImageType() {return imageType;}/*** 设置图片类型,image选择时有效* @param imageType 默认为所有* @return this*/public MediaBuilder setImageType(String[] imageType) {this.imageType = imageType;return this;}/*** 图片和视频选择器的文件类型* @return 数组*/public String[] getMediaType() {return mediaType;}/*** 媒体选择器的文件类型。图片和视频的类型* @param mediaType 默认为所有* @return this*/public MediaBuilder setMediaType(String[] mediaType) {this.mediaType = mediaType;return this;}/*** 绑定生命周期* @param lifecycleOwner LifeCycle对象* @return this*/public MediaBuilder bindLifeCycle(LifecycleOwner lifecycleOwner) {this.lifecycleOwner = lifecycleOwner;return this;}/*** 设置视频子目录地址* @param subPath 子目录地址* @return 新的父路径*/public MediaBuilder setDefaultVideoSubPath(String subPath) {if (TextUtils.isEmpty(subPath)) {return this;}videoSubPath = subPath;return this;}/*** 是否保存到公共目录* true代表保存到公共目录*/public MediaBuilder setSavePublic(boolean isPublic) {this.savePublicPath = isPublic;return this;}/*** 设置监听* @param mediaListener 监听* @return this*/public MediaBuilder setMediaListener(MediaListener mediaListener) {this.mediaListener = mediaListener;return this;}/*** 选择类型,枚举MediaPickerTypeEnum* @param chooseType 默认为0,PICK_TYPE-1 DEFAULT_TYPE-0(默认的选择传统选择器)* @return this*/public MediaBuilder setChooseType(MediaPickerTypeEnum chooseType) {this.chooseType = chooseType;return this;}/*** 设置水印文字* @param waterMark 水印文字* @return this*/public MediaBuilder setWaterMark(String waterMark) {this.waterMark = waterMark;return this;}/*** 水印文字* @return 水印文字*/public String getWaterMark() {return waterMark;}/*** 图片子目录,示例:/casic/image* @return 子目录*/public String getImageSubPath() {return imageSubPath;}/*** 视频子目录,示例:/casic/video* @return 子目录*/public String getVideoSubPath() {return videoSubPath;}/*** 是否保存到公共目录* @return true代表保存到公共目录*/public boolean isSavePublicPath() {return savePublicPath;}/*** 图片输出目录,根据设置的savePublicPath和imageSubPath等拼接输出付目录* @return 图片输出目录*/public String getImageOutPutPath() {if (savePublicPath) {if (TextUtils.isEmpty(imageSubPath)) {return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath() + File.separator + MediaUtil.getDefaultBasePath(mContext) + File.separator + "image";} else {return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath() + File.separator + imageSubPath;}} else {if (TextUtils.isEmpty(imageSubPath)) {return mContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES) + File.separator + "image";} else {return mContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES) + File.separator + imageSubPath;}}}/*** 视频输出目录,根据设置的savePublicPath和videoSubPath等拼接输出付目录* @return 视频输出目录*/public String getVideoOutPutPath() {if (savePublicPath) {if (TextUtils.isEmpty(videoSubPath)) {return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath() + File.separator + MediaUtil.getDefaultBasePath(mContext) + File.separator + "video";} else {return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath() + File.separator + imageSubPath;}} else {if (TextUtils.isEmpty(videoSubPath)) {return mContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES) + File.separator + "video";} else {return mContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES) + File.separator + videoSubPath;}}}/*** 图片压缩质量** @param imageQualityCompress 单位大小kb,默认200kb* @return this*/public MediaBuilder setImageQualityCompress(int imageQualityCompress) {this.imageQualityCompress = imageQualityCompress;return this;}/*** 相册最大选择数量** @param imageMaxSelectedCount 最大选择的图片数量,最多9张* @return this*/public MediaBuilder setImageMaxSelectedCount(int imageMaxSelectedCount) {this.imageMaxSelectedCount = imageMaxSelectedCount;return this;}/*** 相册最大选择数量** @param videoMaxSelectedCount 最大选择的图片数量,最多9张* @return this*/public MediaBuilder setVideoMaxSelectedCount(int videoMaxSelectedCount) {this.videoMaxSelectedCount = videoMaxSelectedCount;return this;}/*** 视频最大时长* @param maxVideoTime 单位秒* @return this*/public MediaBuilder setMaxVideoTime(int maxVideoTime) {this.maxVideoTime = maxVideoTime;return this;}/*** 视频压缩质量,枚举VideoQualityEnum* @param videoQuality 低质量-1,中质量-2,高质量-3* @return this*/public MediaBuilder setVideoQuality(VideoQualityEnum videoQuality) {this.videoQuality = videoQuality;return this;}/*** 是否显示加载框* @return this*/public boolean isShowLoading() {return showLoading;}/*** 是否显示加载框* @param showLoading true-显示* @return this*/public MediaBuilder setShowLoading(boolean showLoading) {this.showLoading = showLoading;return this;}/*** 获取LifeCycle* @return LifecycleOwner*/public LifecycleOwner getLifecycleOwner() {return lifecycleOwner;}/*** 获取视频最大时长* @return 单位描*/public int getMaxVideoTime() {return maxVideoTime;}/*** 获取视频压缩质量,低质量-1,中质量-2,高质量-3* @return 视频质量*/public VideoQualityEnum getVideoQuality() {return videoQuality;}/*** 获取图片最大选择数量* @return 图片最大选择数量*/public int getImageMaxSelectedCount() {return imageMaxSelectedCount;}/*** 获取图片压缩质量,单位kb* @return 图片压缩质量*/public int getImageQualityCompress() {return imageQualityCompress;}/*** 获取图片选择监听* @return 图片选择监听*/public MediaListener getMediaListener() {return mediaListener;}/*** 获取选择的类型* @return 选择类型*/public MediaPickerTypeEnum getChooseType() {return chooseType;}/*** 获取视频最大选择数量* @return 视频最大选择数量*/public int getVideoMaxSelectedCount() {return videoMaxSelectedCount;}/*** 创建MediaHelper对象*/public MediaHelper builder() {if (lifecycleOwner == null) {throw new RuntimeException("please bind lifecycle");}return new MediaHelper(this);}}
private final MediaHelper mediaHelper;private ActivityResultLauncher<String[]> permissionLauncher = null;private ActivityResultLauncher<MediaTypeEnum> cameraLauncher = null;private ActivityResultLauncher<MediaTypeEnum> shootLauncher = null;private ActivityResultLauncher<SelectorOptions> singleLauncher = null;private ActivityResultLauncher<SelectorOptions> multiLauncher = null;private ActivityResultLauncher<PickVisualMediaRequest> pickMuLtiImageSelectorLauncher = null;private ActivityResultLauncher<PickVisualMediaRequest> pickImageSelectorLauncher = null;private ActivityResultLauncher<PickVisualMediaRequest> pickMuLtiMediaSelectorLauncher = null;private ActivityResultLauncher<PickVisualMediaRequest> pickMediaSelectorLauncher = null;private ActivityResultLauncher<PickVisualMediaRequest> pickMuLtiVideoSelectorLauncher = null;private ActivityResultLauncher<PickVisualMediaRequest> pickVideoSelectorLauncher = null;private TakeVideoUri takeVideoUri;private TakeCameraUri takeCameraUri;private OpenPickMediaSelector pickMediaSelector;private OpenPickMultipleMediaSelector pickMuLtiVideoSelector;private OpenPickMultipleMediaSelector pickMuLtiImageSelector;private OpenPickMultipleMediaSelector pickMuLtiImageAndViewSelector;private OpenMultiSelector multiSelector;private OpenSingleSelector singleSelector;public MediaLifecycleObserver(MediaHelper mediaHelper) {this.mediaHelper = mediaHelper;}@Overridepublic void onCreate(@NonNull LifecycleOwner owner) {DefaultLifecycleObserver.super.onCreate(owner);ActivityResultRegistry registry;if (owner instanceof ComponentActivity) {registry = ((ComponentActivity) owner).getActivityResultRegistry();} else if (owner instanceof Fragment) {registry = ((Fragment) owner).requireActivity().getActivityResultRegistry();} else {throw new RuntimeException("请使用Activity或Fragment的lifecycle对象");}//新选择器,兼容性不是很好,register中的key不能重复,如果重复则默认为同一个因此当你一个页面有多个实例的时候就会有问题pickMediaSelector = new OpenPickMediaSelector();//图片if (mediaHelper.getMediaBuilder().getImageMaxSelectedCount() > 1) {pickMuLtiImageSelector = new OpenPickMultipleMediaSelector(mediaHelper.getMediaBuilder().getImageMaxSelectedCount());} else {pickMuLtiImageSelector = new OpenPickMultipleMediaSelector(MediaHelper.DEFAULT_ALBUM_MAX_COUNT);}pickMuLtiImageSelectorLauncher = registry.register("pickMuLtiImageSelector" + UUID.randomUUID().toString(), owner, pickMuLtiImageSelector,new MultiSelectorCallBack(mediaHelper, pickMuLtiImageSelector));pickImageSelectorLauncher = registry.register("pickImageSelector" + UUID.randomUUID().toString(), owner, pickMediaSelector,new SingleSelectorCallBack(pickMediaSelector, mediaHelper.getMutableLiveData()));//图片和视频if (mediaHelper.getMediaBuilder().getMediaMaxSelectedCount() > 1) {pickMuLtiImageAndViewSelector = new OpenPickMultipleMediaSelector(mediaHelper.getMediaBuilder().getMediaMaxSelectedCount());} else {pickMuLtiImageAndViewSelector = new OpenPickMultipleMediaSelector(MediaHelper.DEFAULT_MEDIA_MAX_COUNT);}pickMuLtiMediaSelectorLauncher = registry.register("pickMuLtiMediaSelector" + UUID.randomUUID().toString(), owner, pickMuLtiImageAndViewSelector,new MultiSelectorCallBack(mediaHelper, pickMuLtiImageAndViewSelector));pickMediaSelectorLauncher = registry.register("pickMediaSelector" + UUID.randomUUID().toString(), owner, pickMediaSelector,new SingleSelectorCallBack(pickMediaSelector, mediaHelper.getMutableLiveData()));//视频if (mediaHelper.getMediaBuilder().getVideoMaxSelectedCount() > 1) {pickMuLtiVideoSelector = new OpenPickMultipleMediaSelector(mediaHelper.getMediaBuilder().getVideoMaxSelectedCount());} else {pickMuLtiVideoSelector = new OpenPickMultipleMediaSelector(MediaHelper.DEFAULT_VIDEO_MAX_COUNT);}pickMuLtiVideoSelectorLauncher = registry.register("pickMuLtiVideoSelector" + UUID.randomUUID().toString(), owner, pickMuLtiVideoSelector,new MultiSelectorCallBack(mediaHelper, pickMuLtiVideoSelector));pickVideoSelectorLauncher = registry.register("pickVideoSelector" + UUID.randomUUID().toString(), owner, pickMediaSelector,new SingleSelectorCallBack(pickMediaSelector, mediaHelper.getMutableLiveData()));//传统选择器,单选singleSelector = new OpenSingleSelector();singleLauncher = registry.register("singleSelector" + UUID.randomUUID().toString(), owner, singleSelector,new SingleSelectorCallBack(singleSelector, mediaHelper.getMutableLiveData()));//传统选择器,多选multiSelector = new OpenMultiSelector();multiLauncher = registry.register("multiSelector" + UUID.randomUUID().toString(), owner, multiSelector,new MultiSelectorCallBack(mediaHelper, multiSelector));//权限permissionLauncher = registry.register("permission" + UUID.randomUUID().toString(), owner, new ActivityResultContracts.RequestMultiplePermissions(), permissionCallback);//拍照takeCameraUri = new TakeCameraUri(mediaHelper.getMediaBuilder());cameraLauncher = registry.register("camera" + UUID.randomUUID().toString(), owner, takeCameraUri,new CameraCallBack(mediaHelper.getMediaBuilder(), takeCameraUri, mediaHelper.getMutableLiveData()));//录像takeVideoUri = new TakeVideoUri(mediaHelper.getMediaBuilder(), mediaHelper.getMediaBuilder().getMaxVideoTime());shootLauncher = registry.register("shoot" + UUID.randomUUID().toString(), owner, takeVideoUri,new CameraCallBack(mediaHelper.getMediaBuilder(), takeVideoUri, mediaHelper.getMutableLiveData()));}/*** 权限回调*/ActivityResultCallback<Map<String, Boolean>> permissionCallback = new ActivityResultCallback<>() {@Overridepublic void onActivityResult(Map<String, Boolean> result) {for (Map.Entry<String, Boolean> entry : result.entrySet()) {LogUtil.show(MediaHelper.TAG, entry.getKey() + ":" + entry.getValue());if (Boolean.FALSE.equals(entry.getValue())) {new TipDialog(mediaHelper.getMediaBuilder().getContext()).setMessage("您拒绝了当前权限,可能导致无法使用该功能,可前往设置修改").setNegativeText("取消").setPositiveText("前往设置").setOnPositiveClickListener(dialog -> {dialog.dismiss();Intent intent = new Intent();intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);Uri uri = Uri.fromParts("package", mediaHelper.getMediaBuilder().getContext().getPackageName(), null);intent.setData(uri);mediaHelper.getMediaBuilder().getContext().startActivity(intent);}).builder().show();return;}}}};@Overridepublic void onDestroy(@NonNull LifecycleOwner owner) {DefaultLifecycleObserver.super.onDestroy(owner);//取消pick监听if (pickMuLtiImageSelectorLauncher != null) {pickMuLtiImageSelectorLauncher.unregister();}if (pickMuLtiMediaSelectorLauncher != null) {pickMuLtiMediaSelectorLauncher.unregister();}if (pickMediaSelectorLauncher != null) {pickMediaSelectorLauncher.unregister();}if (pickImageSelectorLauncher != null) {pickImageSelectorLauncher.unregister();}if (pickMuLtiVideoSelectorLauncher != null) {pickMuLtiVideoSelectorLauncher.unregister();}if (pickVideoSelectorLauncher != null) {pickVideoSelectorLauncher.unregister();}//拍照录像、权限if (cameraLauncher != null) {cameraLauncher.unregister();}if (shootLauncher != null) {shootLauncher.unregister();}if (permissionLauncher != null) {permissionLauncher.unregister();}if (singleLauncher != null) {singleLauncher.unregister();}if (multiLauncher != null) {multiLauncher.unregister();}mediaHelper.getMediaBuilder().getLifecycleOwner().getLifecycle().removeObserver(this);takeCameraUri = null;takeVideoUri = null;}public TakeCameraUri getTakeCameraUri() {return takeCameraUri;}public TakeVideoUri getTakeVideoUri() {return takeVideoUri;}public ActivityResultLauncher<SelectorOptions> getMultiLauncher() {return multiLauncher;}public ActivityResultLauncher<SelectorOptions> getSingleLauncher() {return singleLauncher;}public ActivityResultLauncher<String[]> getPermissionLauncher() {return permissionLauncher;}public ActivityResultLauncher<MediaTypeEnum> getCameraLauncher() {return cameraLauncher;}public ActivityResultLauncher<MediaTypeEnum> getShootLauncher() {return shootLauncher;}public ActivityResultLauncher<PickVisualMediaRequest> getPickMuLtiImageSelectorLauncher() {return pickMuLtiImageSelectorLauncher;}public ActivityResultLauncher<PickVisualMediaRequest> getPickImageSelectorLauncher() {return pickImageSelectorLauncher;}public ActivityResultLauncher<PickVisualMediaRequest> getPickMuLtiVideoSelectorLauncher() {return pickMuLtiVideoSelectorLauncher;}public ActivityResultLauncher<PickVisualMediaRequest> getPickVideoSelectorLauncher() {return pickVideoSelectorLauncher;}public ActivityResultLauncher<PickVisualMediaRequest> getPickMuLtiMediaSelectorLauncher() {return pickMuLtiMediaSelectorLauncher;}public ActivityResultLauncher<PickVisualMediaRequest> getPickMediaSelectorLauncher() {return pickMediaSelectorLauncher;}public OpenPickMultipleMediaSelector getPickMuLtiImageAndViewSelector() {return pickMuLtiImageAndViewSelector;}public OpenPickMultipleMediaSelector getPickMuLtiImageSelector() {return pickMuLtiImageSelector;}public OpenPickMultipleMediaSelector getPickMuLtiVideoSelector() {return pickMuLtiVideoSelector;}public OpenPickMediaSelector getPickMediaSelector() {return pickMediaSelector;}public OpenMultiSelector getMultiSelector() {return multiSelector;}public OpenSingleSelector getSingleSelector() {return singleSelector;}
}
public class TakeCameraUri extends ActivityResultContract<MediaTypeEnum, Uri> {/*** 拍照返回的uri*/private Uri uri;private final MediaBuilder mediaBuilder;private MediaTypeEnum mediaTypeEnum;public TakeCameraUri(MediaBuilder mediaBuilder) {this.mediaBuilder = mediaBuilder;}public MediaTypeEnum getMediaType() {return mediaTypeEnum;}@NonNull@Overridepublic Intent createIntent(@NonNull Context context, MediaTypeEnum input) {this.mediaTypeEnum = input;String mimeType = "image/jpeg";String fileName = "IMAGE_" + System.currentTimeMillis() + ".jpg";if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {//如果保存到公共目录if (mediaBuilder.isSavePublicPath()) {ContentValues values = new ContentValues();values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);//如果没有设置子目录的话,则默认取包名if (TextUtils.isEmpty(mediaBuilder.getImageSubPath())) {values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + File.separator + MediaUtil.getDefaultBasePath(context) + File.separator + "image");} else {values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + File.separator +mediaBuilder.getImageSubPath());}uri = context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);} else {File file = new File(mediaBuilder.getImageOutPutPath()+File.separator + fileName);if(file.getParentFile() != null && !file.getParentFile().exists()){boolean isCreated = file.getParentFile().mkdirs();}uri = FileProvider.getUriForFile(context, context.getPackageName() + ".FileProvider",file);}} else {File file = new File(mediaBuilder.getImageOutPutPath()+File.separator + fileName);if(file.getParentFile() != null && !file.getParentFile().exists()){boolean isCreated = file.getParentFile().mkdirs();}uri = FileProvider.getUriForFile(context, context.getPackageName() + ".FileProvider",file);}return new Intent(MediaStore.ACTION_IMAGE_CAPTURE).putExtra(MediaStore.EXTRA_OUTPUT, uri);}/*** 下面注释代码是正常的,但是由于视频录像的时候返回的resultCode有问题,这里虽然没有问题,但是担心兼容性不好,索性就不判断了*/@Overridepublic Uri parseResult(int resultCode, @Nullable Intent intent) {LogUtil.show(MediaHelper.TAG, "拍照回调resultCode:" + resultCode);
// if (resultCode == Activity.RESULT_OK) {
// return uri;
// }return uri;}}public class TakeVideoUri extends ActivityResultContract<MediaTypeEnum, Uri> {/*** 拍照返回的uri*/private Uri uri;private final MediaBuilder mediaBuilder;/*** 录制时长*/private int durationLimit = 30;private MediaTypeEnum mediaType;public TakeVideoUri(MediaBuilder mediaBuilder) {this.mediaBuilder = mediaBuilder;}public TakeVideoUri(MediaBuilder mediaBuilder, int durationLimit) {this.mediaBuilder = mediaBuilder;this.durationLimit = durationLimit;}public MediaTypeEnum getMediaType() {return mediaType;}@NonNull@Overridepublic Intent createIntent(@NonNull Context context, MediaTypeEnum input) {this.mediaType = input;String mimeType = "video/mp4";String fileName = "VIDEO_" + System.currentTimeMillis() + ".mp4";if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {//如果保存到公共目录if (mediaBuilder.isSavePublicPath()) {ContentValues values = new ContentValues();values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);//如果没有设置子目录的话,则默认取包名if (TextUtils.isEmpty(mediaBuilder.getVideoSubPath())) {values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + File.separator + MediaUtil.getDefaultBasePath(context) + File.separator + "video");} else {values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + File.separator +mediaBuilder.getVideoSubPath());}uri = context.getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);} else {File file = new File(mediaBuilder.getVideoOutPutPath() + File.separator + fileName);if (file.getParentFile() != null && !file.getParentFile().exists()) {boolean isCreated = file.getParentFile().mkdirs();}uri = FileProvider.getUriForFile(context, context.getPackageName() + ".FileProvider", file);}} else {File file = new File(mediaBuilder.getVideoOutPutPath() + File.separator + fileName);if (file.getParentFile() != null && !file.getParentFile().exists()) {boolean isCreated = file.getParentFile().mkdirs();}uri = FileProvider.getUriForFile(context, context.getPackageName() + ".FileProvider", file);}return new Intent(MediaStore.ACTION_VIDEO_CAPTURE)// 视频质量。0 低质量;1 高质量.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1)// 时长限制,单位秒.putExtra(MediaStore.EXTRA_DURATION_LIMIT, durationLimit).putExtra(MediaStore.EXTRA_OUTPUT, uri);}/*** 系统这里有bug。ACTION_VIDEO_CAPTURE返回的resultCode永远都为0,及Activity.RESULT_CANCELED,所以这个没办法判断resultCode这里属于系统bug*/@Overridepublic Uri parseResult(int resultCode, @Nullable Intent intent) {LogUtil.show(MediaHelper.TAG, "录像回调resultCode:" + resultCode);return uri;}
}
主角
public class MediaHelper implements OpenImageDialog.OnOpenImageClickListener, OpenShootDialog.OnOpenVideoClickListener,OpenFileDialog.OnOpenFileClickListener, OpenMediaDialog.OnOpenMediaClickListener {public final static String TAG = MediaHelper.class.getSimpleName();/*** 选择、拍照、拍摄、文件选择等结果*/private final MutableLiveData<MediaBean> mutableLiveData = new MutableLiveData<>();/*** 压缩结果*/private final MutableLiveData<MediaBean> mutableLiveDataCompress = new MutableLiveData<>();/*** 添加照片水印回调*/private final MutableLiveData<MediaBean> mutableLiveDataWaterMark = new MutableLiveData<>();/*** 最大可选的图片数量,默认9张*/public final static int DEFAULT_ALBUM_MAX_COUNT = 9;/*** 最大可选的视频数量,默认9张*/public final static int DEFAULT_VIDEO_MAX_COUNT = 9;/*** 最大可选的音频数量,默认9张*/public final static int DEFAULT_AUDIO_MAX_COUNT = 9;/*** 最大可选的文件数量*/public final static int DEFAULT_FILE_MAX_COUNT = 9;/*** 最大可选的图片、视频数量,同时选择图片视频时使用*/public final static int DEFAULT_MEDIA_MAX_COUNT = 9;private final MediaBuilder mediaBuilder;private final UIController uiController;private final MediaLifecycleObserver mediaLifecycleObserver = new MediaLifecycleObserver(this);private static final String THREAD_NAME = "mediaHelperThread";private final HandlerThread handlerThread = new HandlerThread(THREAD_NAME, 10);protected MediaHelper(MediaBuilder mediaBuilder) {this.mediaBuilder = mediaBuilder;uiController = new UIController(mediaBuilder.getContext(), this.mediaBuilder.getLifecycleOwner().getLifecycle());if (this.mediaBuilder.getLifecycleOwner() == null) {return;}this.mediaBuilder.getLifecycleOwner().getLifecycle().addObserver(mediaLifecycleObserver);}public UIController getUIController() {return uiController;}public MediaBuilder getMediaBuilder() {return mediaBuilder;}public MutableLiveData<MediaBean> getMutableLiveData() {return mutableLiveData;}public MutableLiveData<MediaBean> getMutableLiveDataCompress() {return mutableLiveDataCompress;}public void openImageDialog(View v, int mediaType) {new OpenImageDialog(v.getContext()).setMediaType(mediaType).setOnOpenImageClickListener(this).builder().show();}public void openFileDialog(View v, String buttonMessage, int chooseType) {new OpenFileDialog(v.getContext()).setChooseType(chooseType).setButtonMessage(buttonMessage).setOnOpenFileClickListener(this).builder().show();}public void openShootDialog(View v, int mediaType) {new OpenShootDialog(v.getContext()).setMediaType(mediaType).setOnOpenVideoClickListener(this).builder().show();}public void openMediaDialog(View v, int mediaType) {new OpenMediaDialog(v.getContext()).setMediaType(mediaType).setOnOpenMediaClickListener(this).builder().show();}/*** 判断权限集合*/public boolean lacksPermissions(Context mContext, String... permissions) {for (String permission : permissions) {if (lacksPermission(mContext, permission)) {return true;}}return false;}/*** 判断是否缺少权限*/private boolean lacksPermission(Context mContext, String permission) {return ContextCompat.checkSelfPermission(mContext, permission) ==PackageManager.PERMISSION_DENIED;}/*** 权限检测** @param permissions 权限*/private void checkPermission(String[] permissions) {if (!mediaBuilder.isShowPermissionDialog()) {mediaLifecycleObserver.getPermissionLauncher().launch(permissions);return;}new PermissionReminderDialog(mediaBuilder.getContext()).setMessage(mediaBuilder.getPermissionMessage()).setSpannableContent(mediaBuilder.getPermissionSpannableContent()).setNegativeText(mediaBuilder.getPermissionNegativeText()).setPositiveText(mediaBuilder.getPermissionPositiveText()).setNegativeTextColor(mediaBuilder.getPermissionNegativeTextColor()).setPositiveTextColor(mediaBuilder.getPermissionPositiveTextColor()).setOnPositiveClickListener(mediaBuilder.getOnPermissionPositiveClickListener() == null ? dialog -> {dialog.dismiss();mediaLifecycleObserver.getPermissionLauncher().launch(permissions);} : mediaBuilder.getOnPermissionPositiveClickListener()).setOnNegativeClickListener(mediaBuilder.getOnPermissionNegativeClickListener() == null ? Dialog::dismiss : mediaBuilder.getOnPermissionNegativeClickListener()).builder().show();}/*** 开始压缩*/public void startCompressImage(List<Uri> images) {Message message = new Message();message.what = 0;new ImageCompressHandler(this,handlerLooper(), images).sendMessage(message);}@Overridepublic void fileClick(int chooseType) {if (OpenFileDialog.AUDIO == chooseType) {openAudio();} else if (OpenFileDialog.FILE == chooseType) {openFile();}}public void openAudio() {openAudio(null);}/*** 打开音频选择页面*/public void openAudio(String[] customAudioType) {if (isMoreThanMaxAudio()) {return;}if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {if (lacksPermissions(mediaBuilder.getContext(), ConstantsHelper.PERMISSIONS_AUDIT_READ_TIRAMISU)) {checkPermission(ConstantsHelper.PERMISSIONS_AUDIT_READ_TIRAMISU);return;}} else {if (lacksPermissions(mediaBuilder.getContext(), ConstantsHelper.PERMISSIONS_READ)) {checkPermission(ConstantsHelper.PERMISSIONS_READ);return;}}String[] audioType;if (customAudioType == null || customAudioType.length == 0) {if (mediaBuilder.getAudioType() == null || mediaBuilder.getAudioType().length == 0) {audioType = new String[]{"audio/*"};} else {audioType = mediaBuilder.getAudioType();}} else {audioType = customAudioType;}if (mediaBuilder.getAudioMaxSelectedCount() == 1) {mediaLifecycleObserver.getSingleLauncher().launch(new SelectorOptions(audioType,MediaTypeEnum.AUDIO));} else {mediaLifecycleObserver.getMultiLauncher().launch(new SelectorOptions(audioType,MediaTypeEnum.AUDIO));}}public void openFile() {openFile(null);}/*** 打开文件管理器*/public void openFile(String[] customFileType) {if (isMoreThanMaxFile()) {return;}if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {if (lacksPermissions(mediaBuilder.getContext(), ConstantsHelper.PERMISSIONS_READ)) {checkPermission(ConstantsHelper.PERMISSIONS_READ);return;}}String[] fileType;if (customFileType == null || customFileType.length == 0) {if (mediaBuilder.getFileType() == null || mediaBuilder.getFileType().length == 0) {fileType = new String[]{"*/*"};} else {fileType = mediaBuilder.getFileType();}} else {fileType = customFileType;}if (mediaBuilder.getFileMaxSelectedCount() == 1) {mediaLifecycleObserver.getSingleLauncher().launch(new SelectorOptions(fileType,MediaTypeEnum.FILE));} else {mediaLifecycleObserver.getMultiLauncher().launch(new SelectorOptions(fileType,MediaTypeEnum.FILE));}}private Looper handlerLooper() {if (!handlerThread.isAlive()) {handlerThread.start();}return handlerThread.getLooper();}/*** 开始添加水印*/public void startWaterMark(Bitmap bitmap) {if (bitmap == null) {return;}Message message = new Message();message.obj = bitmap;message.arg1 = 100;if (mediaBuilder.isShowLoading()) {uiController.showLoading("正在为图片添加水印...");}new WaterMarkHandler(this, handlerLooper()).sendMessage(message);}/*** 开始添加水印*/public void startWaterMark(Bitmap bitmap, int alpha) {if (bitmap == null) {return;}Message message = new Message();message.obj = bitmap;message.arg1 = alpha;if (mediaBuilder.isShowLoading()) {uiController.showLoading("正在为图片添加水印...");}new WaterMarkHandler(this, handlerLooper()).sendMessage(message);}public MutableLiveData<MediaBean> getMutableLiveDataWaterMark() {return mutableLiveDataWaterMark;}private boolean isMoreThanMaxMedia() {if (mediaBuilder.getMediaListener() != null) {if (mediaBuilder.getMediaMaxSelectedCount() <= mediaBuilder.getMediaListener().onSelectedMediaCount()) {uiController.showToast("最多只可选" + mediaBuilder.getMediaMaxSelectedCount() + "个附件");return true;}}return false;}private boolean isMoreThanMaxImage() {if (mediaBuilder.getMediaListener() != null) {if (mediaBuilder.getImageMaxSelectedCount() <= mediaBuilder.getMediaListener().onSelectedImageCount()) {uiController.showToast("最多只可选" + mediaBuilder.getImageMaxSelectedCount() + "张图片");return true;}}return false;}private boolean isMoreThanMaxVideo() {if (mediaBuilder.getMediaListener() != null) {if (mediaBuilder.getVideoMaxSelectedCount() <= mediaBuilder.getMediaListener().onSelectedVideoCount()) {uiController.showToast("最多只可选" + mediaBuilder.getVideoMaxSelectedCount() + "条视频");return true;}}return false;}private boolean isMoreThanMaxAudio() {if (mediaBuilder.getMediaListener() != null) {if (mediaBuilder.getAudioMaxSelectedCount() <= mediaBuilder.getMediaListener().onSelectedAudioCount()) {uiController.showToast("最多只可选" + mediaBuilder.getAudioMaxSelectedCount() + "条音频");return true;}}return false;}private boolean isMoreThanMaxFile() {if (mediaBuilder.getMediaListener() != null) {if (mediaBuilder.getFileMaxSelectedCount() <= mediaBuilder.getMediaListener().onSelectedFileCount()) {uiController.showToast("最多只可选" + mediaBuilder.getFileMaxSelectedCount() + "个文件");return true;}}return false;}public void openImg() {openImg(null);}/*** 打开相册选择页面*/public void openImg(String[] customImageType) {if (mediaBuilder.getChooseType() == MediaPickerTypeEnum.PICK) {if (mediaBuilder.getImageMaxSelectedCount() == 1) {mediaLifecycleObserver.getPickImageSelectorLauncher().launch(new PickVisualMediaRequest.Builder().setMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly.INSTANCE).build());} else {mediaLifecycleObserver.getPickMuLtiImageSelectorLauncher().launch(new PickVisualMediaRequest.Builder().setMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly.INSTANCE).build());}} else {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {if (lacksPermissions(mediaBuilder.getContext(), ConstantsHelper.PERMISSIONS_IMAGE_READ_UPSIDE_DOWN_CAKE)) {checkPermission(ConstantsHelper.PERMISSIONS_IMAGE_READ_UPSIDE_DOWN_CAKE);return;}} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.TIRAMISU) {if (lacksPermissions(mediaBuilder.getContext(), ConstantsHelper.PERMISSIONS_IMAGE_READ_TIRAMISU)) {checkPermission(ConstantsHelper.PERMISSIONS_IMAGE_READ_TIRAMISU);return;}} else {if (lacksPermissions(mediaBuilder.getContext(), ConstantsHelper.PERMISSIONS_READ)) {checkPermission(ConstantsHelper.PERMISSIONS_READ);return;}}String[] imageType;if (customImageType == null || customImageType.length == 0) {if (mediaBuilder.getImageType() == null || mediaBuilder.getImageType().length == 0) {imageType = new String[]{"image/*"};} else {imageType = mediaBuilder.getImageType();}} else {imageType = customImageType;}if (mediaBuilder.getImageMaxSelectedCount() == 1) {mediaLifecycleObserver.getSingleLauncher().launch(new SelectorOptions(imageType,MediaTypeEnum.IMAGE));} else {mediaLifecycleObserver.getMultiLauncher().launch(new SelectorOptions(imageType,MediaTypeEnum.IMAGE));}}}public void openMedia() {openMedia(null);}/*** 打开相册选择页面,包含视频、图片一起*/public void openMedia(String[] customMediaType) {if (mediaBuilder.getChooseType() == MediaPickerTypeEnum.PICK) {if (mediaBuilder.getMediaMaxSelectedCount() == 1) {mediaLifecycleObserver.getPickMediaSelectorLauncher().launch(new PickVisualMediaRequest.Builder().setMediaType(ActivityResultContracts.PickVisualMedia.ImageAndVideo.INSTANCE).build());} else {mediaLifecycleObserver.getPickMuLtiMediaSelectorLauncher().launch(new PickVisualMediaRequest.Builder().setMediaType(ActivityResultContracts.PickVisualMedia.ImageAndVideo.INSTANCE).build());}} else {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {if (lacksPermissions(mediaBuilder.getContext(), ConstantsHelper.PERMISSIONS_IMAGE_READ_UPSIDE_DOWN_CAKE)) {checkPermission(ConstantsHelper.PERMISSIONS_IMAGE_READ_UPSIDE_DOWN_CAKE);return;}} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.TIRAMISU) {if (lacksPermissions(mediaBuilder.getContext(), ConstantsHelper.PERMISSIONS_IMAGE_READ_TIRAMISU)) {checkPermission(ConstantsHelper.PERMISSIONS_IMAGE_READ_TIRAMISU);return;}} else {if (lacksPermissions(mediaBuilder.getContext(), ConstantsHelper.PERMISSIONS_READ)) {checkPermission(ConstantsHelper.PERMISSIONS_READ);return;}}String[] mediaType;if (customMediaType == null || customMediaType.length == 0) {if (mediaBuilder.getMediaType() == null || mediaBuilder.getMediaType().length == 0) {mediaType = new String[]{"image/*", "video/*"};} else {mediaType = mediaBuilder.getMediaType();}} else {mediaType = customMediaType;}if (mediaBuilder.getImageMaxSelectedCount() == 1) {mediaLifecycleObserver.getSingleLauncher().launch(new SelectorOptions(mediaType,MediaTypeEnum.IMAGE_AND_VIDEO));} else {mediaLifecycleObserver.getMultiLauncher().launch(new SelectorOptions(mediaType,MediaTypeEnum.IMAGE_AND_VIDEO));}}}/*** 打开摄像机*/public void camera() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {if (lacksPermissions(mediaBuilder.getContext(), ConstantsHelper.PERMISSIONS_CAMERA_R)) {checkPermission(ConstantsHelper.PERMISSIONS_CAMERA_R);return;}} else {if (lacksPermissions(mediaBuilder.getContext(), ConstantsHelper.PERMISSIONS_CAMERA)) {checkPermission(ConstantsHelper.PERMISSIONS_CAMERA);return;}}mediaLifecycleObserver.getCameraLauncher().launch(MediaTypeEnum.IMAGE);}/*** 打开摄像机*/public void mediaCamera() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {if (lacksPermissions(mediaBuilder.getContext(), ConstantsHelper.PERMISSIONS_CAMERA_R)) {checkPermission(ConstantsHelper.PERMISSIONS_CAMERA_R);return;}} else {if (lacksPermissions(mediaBuilder.getContext(), ConstantsHelper.PERMISSIONS_CAMERA)) {checkPermission(ConstantsHelper.PERMISSIONS_CAMERA);return;}}mediaLifecycleObserver.getCameraLauncher().launch(MediaTypeEnum.IMAGE_AND_VIDEO);}/*** 开始压缩*/public void startCompressVideo(List<Uri> videos) {Message message = new Message();message.obj = videos;message.what = 0;new VideoCompressHandler(this,handlerLooper(), videos).sendMessage(message);}/*** 开始压缩*/public void startCompressMedia(List<Uri> mediaList) {Message message = new Message();message.obj = mediaList;message.what = 0;new MediaCompressHandler(this,handlerLooper(), mediaList).sendMessage(message);}@Overridepublic void shootClick(int mediaType) {if (isMoreThanMaxVideo()) {return;}if (OpenShootDialog.ALBUM == mediaType) {openShoot();} else if (OpenShootDialog.CAMERA == mediaType) {shoot();}}/*** 打开拍摄*/public void shoot() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {if (lacksPermissions(mediaBuilder.getContext(), ConstantsHelper.PERMISSIONS_CAMERA_R)) {checkPermission(ConstantsHelper.PERMISSIONS_CAMERA_R);return;}} else {if (lacksPermissions(mediaBuilder.getContext(), ConstantsHelper.PERMISSIONS_CAMERA)) {checkPermission(ConstantsHelper.PERMISSIONS_CAMERA);return;}}mediaLifecycleObserver.getShootLauncher().launch(MediaTypeEnum.VIDEO);}/*** 打开拍摄*/public void mediaShoot() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {if (lacksPermissions(mediaBuilder.getContext(), ConstantsHelper.PERMISSIONS_CAMERA_R)) {checkPermission(ConstantsHelper.PERMISSIONS_CAMERA_R);return;}} else {if (lacksPermissions(mediaBuilder.getContext(), ConstantsHelper.PERMISSIONS_CAMERA)) {checkPermission(ConstantsHelper.PERMISSIONS_CAMERA);return;}}mediaLifecycleObserver.getShootLauncher().launch(MediaTypeEnum.IMAGE_AND_VIDEO);}public void openShoot() {openShoot(null);}/*** 打开视频资源选择库*/public void openShoot(String[] customVideoType) {if (mediaBuilder.getChooseType() == MediaPickerTypeEnum.PICK) {if (mediaBuilder.getVideoMaxSelectedCount() == 1) {mediaLifecycleObserver.getPickVideoSelectorLauncher().launch(new PickVisualMediaRequest.Builder().setMediaType(ActivityResultContracts.PickVisualMedia.VideoOnly.INSTANCE).build());} else {mediaLifecycleObserver.getPickMuLtiVideoSelectorLauncher().launch(new PickVisualMediaRequest.Builder().setMediaType(ActivityResultContracts.PickVisualMedia.VideoOnly.INSTANCE).build());}} else {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {if (lacksPermissions(mediaBuilder.getContext(), ConstantsHelper.PERMISSIONS_VIDEO_READ_UPSIDE_DOWN_CAKE)) {checkPermission(ConstantsHelper.PERMISSIONS_VIDEO_READ_UPSIDE_DOWN_CAKE);return;}} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.TIRAMISU) {if (lacksPermissions(mediaBuilder.getContext(), ConstantsHelper.PERMISSIONS_VIDEO_READ_TIRAMISU)) {checkPermission(ConstantsHelper.PERMISSIONS_VIDEO_READ_TIRAMISU);return;}} else {if (lacksPermissions(mediaBuilder.getContext(), ConstantsHelper.PERMISSIONS_READ)) {checkPermission(ConstantsHelper.PERMISSIONS_READ);return;}}String[] videoType;if (customVideoType == null || customVideoType.length == 0) {if (mediaBuilder.getVideoType() == null || mediaBuilder.getVideoType().length == 0) {videoType = new String[]{"video/*"};} else {videoType = mediaBuilder.getVideoType();}} else {videoType = customVideoType;}if (mediaBuilder.getVideoMaxSelectedCount() == 1) {mediaLifecycleObserver.getSingleLauncher().launch(new SelectorOptions(videoType,MediaTypeEnum.VIDEO));} else {mediaLifecycleObserver.getMultiLauncher().launch(new SelectorOptions(videoType,MediaTypeEnum.VIDEO));}}}@Overridepublic void mediaClick(int mediaType) {if (isMoreThanMaxMedia()) {return;}if (OpenMediaDialog.ALBUM == mediaType) {openMedia();} else if (OpenMediaDialog.CAMERA == mediaType) {mediaCamera();} else if (OpenMediaDialog.SHOOT == mediaType) {mediaShoot();}}@Overridepublic void imageClick(int mediaType) {if (isMoreThanMaxImage()) {return;}if (OpenImageDialog.ALBUM == mediaType) {openImg();} else if (OpenImageDialog.CAMERA == mediaType) {camera();}}
}
完整代码示例
https://github.com/fzkf9225/mvvm-componnent-master/blob/master/commonmedia/src/main/java/pers/fz/media/MediaHelper.java