【Android调用相册、拍照、录像】等功能的封装

关于调用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

  1. 回调结果 ,先定义回调结果,我想要什么样的回调数据;
  2. 定义可配置参数,比如我们想要哪些数据可以配置,先定义一个MediaBuilder
  3. 请求权限,我们调用相机等,肯定需要请求系统权限等,所以我们不可能在使用工具类前再写一次请求权限,因此需要写在工具类内部;
  4. 选图、选择拍照、录像等dialog样式,仿照微信的样式类似的结果,点击选图拍照前可以展示一个dialog然后选择拍照 录像 相册选图等等模式;
  5. 压缩图片、视频等工具,拍照、选图后可能需要压缩,因为原图可能会很大很大,这个功能我们改为自定义的形式,可选择的方式;
  6. 拓展功能,我们可以加入一些水印等功能加入进去,或者我们自己可以封装一个九宫格按钮;

回调结果定义

我们顶一个是个实体类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,选图还是拍照,然后调用上面的代码,组合起来

  1. 先请求权限
  2. 调用选图、拍照等
  3. 监听回调
    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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/95093.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/95093.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Git 分支管理:从新开发分支迁移为主分支的完整指南

问题背景 我在使用 Git 进行开发时&#xff0c;由于原有的主分支遭到了污染&#xff0c;不得已在多方尝试之后&#xff0c;决定替换原有的主分支。创建一个新分支并完成了重要修改&#xff1a; 基于提交 0fcb6df0f5e8caa3d853bb1f43f23cfe6d269b18 创建了 new-development 分支…

nginx常见问题(四):端口无权限

当 Nginx 日志报错 bind() to 80 failed (13: Permission denied) 时&#xff0c;这通常是由于权限不足导致 Nginx 无法绑定到 80 端口&#xff08;该端口为系统特权端口&#xff09;。以下是详细的问题分析与解决方案&#xff1a;一、问题原因分析80 端口属于 系统特权端口&am…

【线性代数】线性方程组与矩阵——(3)线性方程组解的结构

上一节&#xff1a;【线性代数】线性方程组与矩阵——&#xff08;2&#xff09;矩阵与线性方程组的解 总目录&#xff1a;【线性代数】目录 文章目录9. 向量组的线性相关性与线性方程组解的结构9.1. 向量组及其线性组合9.2. 向量组的线性相关性9.3. 向量组的秩9.4. 线性方程组…

机器学习-----K-means算法介绍

一、为什么需要 K-Means&#xff1f;在监督学习中&#xff0c;我们总把数据写成 (x, y)&#xff0c;让模型学习 x → y 的映射。 但现实中很多数据根本没有标签 y&#xff0c;例如&#xff1a;啤酒&#xff1a;热量、钠含量、酒精度、价格用户&#xff1a;访问时长、点击次数、…

Spring Security自动处理/login请求,后端控制层没有 @PostMapping(“/login“) 这样的 Controller 方法

一&#xff1a;前言 &#xff08;1&#xff09;Spring Security概念&#xff1a; Spring Security 是属于 Spring 生态下一个功能强大且高度可定制的认证和授权框架&#xff0c;它不仅限于 Web 应用程序的安全性&#xff0c;也可以用于保护任何类型的应用程序。 &#xff08…

idea开发工具中git如何忽略编译文件build、gradle的文件?

idea开发工具中&#xff1a; git显示下面这个文件有变更&#xff1a; ~/Documents/wwwroot-dev/wlxl-backend/java/hyh-apis/hyh-apis-springboot/build/resources/main/mapping/AccountRealnameMapper.xml 我git的根路径是&#xff1a; ~/Documents/wwwroot-dev/wlxl-backend/…

状态机浅析

状态机是处理状态依赖型行为的高效工具&#xff0c;通过结构化建模状态转换&#xff0c;解决了传统条件判断的冗余和混乱问题。它在设备控制、流程管理、协议解析等场景中表现优异&#xff0c;核心优势在于逻辑清晰、可扩展性强和易于调试。 一、介绍 1. 概念 状态机&#x…

Windows 手动病毒排查指南:不依赖杀毒软件的系统安全防护

Windows 手动病毒排查指南&#xff1a;不依赖杀毒软件的系统安全防护 在数字时代&#xff0c;电脑病毒就像潜伏的"网络幽灵"&#xff0c;从窃取隐私的木马到消耗资源的蠕虫&#xff0c;时刻威胁着系统安全。当杀毒软件失效或遭遇新型威胁时&#xff0c;手动排查病毒便…

GPT-5 is here

GPT-5 is here https://openai.com/index/introducing-gpt-5/ — and it’s #1 across the board! #1 in Text, WebDev, and Vision Arena #1 in Hard Prompts, Coding, Math, Creativity, Long Queries, and more Tested under the codename “summit”, GPT-5 now holds the …

【华为机试】55. 跳跃游戏

文章目录55. 跳跃游戏题目描述示例 1&#xff1a;示例 2&#xff1a;提示&#xff1a;解题思路一、问题本质与建模二、方法总览与选择三、贪心算法的正确性&#xff08;直观解释 循环不变式&#xff09;四、反向贪心&#xff1a;等价但有启发的视角五、与动态规划的对比与误区…

RabbitMQ面试精讲 Day 18:内存与磁盘优化配置

【RabbitMQ面试精讲 Day 18】内存与磁盘优化配置 开篇&#xff1a;内存与磁盘优化的重要性 欢迎来到"RabbitMQ面试精讲"系列的第18天&#xff01;今天我们将深入探讨RabbitMQ的内存与磁盘优化配置&#xff0c;这是面试中经常被问及的高频主题&#xff0c;也是生产环…

【C++】string 的特性和使用

Ciallo&#xff5e; (∠・ω< )⌒★ string&#xff08;1&#xff09;1. 构造函数1.1 string();1.2 string(const char* s);1.3 string(const string& str);1.4 string(size_t n, char c);1.5 string(const string& str, size_t pos, size_t len npos);1.6 string(…

创始人IP的精神修炼:于成长中积蓄力量

IP 经济席卷之下&#xff0c;众多企业家常被 “是否入局 IP”“能否做好 IP” 的焦虑裹挟。这种潜藏的精神内耗&#xff0c;对企业根基的侵蚀往往胜过业绩的起伏。著名文化学者于丹在全球创始人 IP 领袖高峰论坛上的洞见&#xff0c;为创始人 IP 的精神成长照亮了前路&#xff…

gbase8s数据库中对象元数据查询

最近整理了gbase8s数据库中常见的元数据的查询&#xff0c;包括表、视图、序列、包、类型、触发器、plsql等等&#xff0c;仅供参考。set environment sqlmode oracle; drop package DBMS_METADATA; create or replace package DBMS_METADATA is function GET_DDL(objtype varc…

常用hook钩子函数

爬虫Hook技术常用字段和勾子函数 目录 Hook技术概述网络请求相关Hook浏览器环境HookJavaScript引擎Hook加密算法Hook反爬虫检测Hook实际应用示例Hook工具和框架 Hook技术概述 Hook&#xff08;钩子&#xff09;技术是一种在程序运行时拦截和修改函数调用的技术。在爬虫中&a…

【解决方法】华为电脑的亮度调节失灵

华为电脑的亮度调节失灵 参考文章&#xff1a; 华为电脑屏幕亮度怎么调不了&#xff1f;华为电脑调节亮度没反应解决教程 亲测&#xff0c;在控制面板中卸载HWOSD&#xff0c;再重装有用。

【软考中级网络工程师】知识点之 DCC 深度剖析

目录一、DCC 是什么1.1 定义阐述1.2 作用讲解二、DCC 工作原理2.1 拨号触发机制2.1.1 感兴趣流量定义2.1.2 触发拨号过程2.2 链路建立流程2.2.1 物理链路连接2.2.2 数据链路层协议协商三、DCC 配置要点3.1 基础配置步骤3.1.1 接口配置3.1.2 拨号映射配置3.2 高级配置参数3.2.1 …

W5500之Socket寄存器区介绍

W5500之Socket寄存器区介绍1)、Socket n模式寄存器(Socket n Mode Register&#xff0c;简写Sn_MR)偏移地址为0x0000&#xff0c;可读写&#xff0c;复位值为0x00&#xff1b;Bit7Bit6Bit5Bit4Bit3Bit2Bit1Bit0MULTI/MFENBCASTBND/MC/MMBUCASTB/MIP6BP3P2P1P0MULTI/MFEN占用“S…

酉矩阵(Unitary Matrix)和随机矩阵

先讨论酉矩阵&#xff08;Unitary Matrix&#xff09;的性质。1. 酉矩阵定义酉矩阵&#xff08;Unitary Matrix&#xff09;是复数域上的方阵&#xff0c;满足以下条件&#xff1a;其中&#xff1a;是 的共轭转置&#xff08;即 Hermitian 转置&#xff0c; &#xff09;。是单…

「iOS」————单例与代理

iOS学习单例代理代理模式的原理代理的循环引用设计模式单例 优点&#xff1a; 全局访问&#xff1a;单例模式确保一个类只有一个实例&#xff0c;并提供全局访问点&#xff0c;方便在整个应用中共享数据或功能。节省资源&#xff1a;由于只创建一个实例&#xff0c;可以减少内…