Android图像选择之 PictureSelector

一款针对Android平台下的图片选择器,支持从相册获取图片、视频、音频&拍照,支持裁剪(单图or多图裁剪)、压缩、主题自定义配置等功能,支持动态获取权限&适配Android 5.0+系统的开源图片选择框架。

废话不多。开干!!

添加依赖:

gradle 复制代码
    //图片选择器
    api 'io.github.lucksiege:pictureselector:v3.11.1'
    //图片压缩
    api 'io.github.lucksiege:compress:v3.11.1'
    //图片裁剪
    api 'io.github.lucksiege:ucrop:v3.11.1'
    //自定义相机
    api 'io.github.lucksiege:camerax:v3.11.1'

实现工具类如下 PictureUtils :

java 复制代码
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.MediaStore;
import android.widget.ImageView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
import com.luck.lib.camerax.SimpleCameraX;
import com.luck.picture.lib.basic.PictureSelector;
import com.luck.picture.lib.config.PictureMimeType;
import com.luck.picture.lib.config.SelectMimeType;
import com.luck.picture.lib.engine.CompressFileEngine;
import com.luck.picture.lib.engine.CropFileEngine;
import com.luck.picture.lib.engine.VideoPlayerEngine;
import com.luck.picture.lib.entity.LocalMedia;
import com.luck.picture.lib.entity.MediaExtraInfo;
import com.luck.picture.lib.interfaces.OnCameraInterceptListener;
import com.luck.picture.lib.interfaces.OnExternalPreviewEventListener;
import com.luck.picture.lib.interfaces.OnRecordAudioInterceptListener;
import com.luck.picture.lib.interfaces.OnResultCallbackListener;
import com.luck.picture.lib.permissions.PermissionChecker;
import com.luck.picture.lib.permissions.PermissionResultCallback;
import com.luck.picture.lib.utils.MediaUtils;
import com.luck.picture.lib.utils.ToastUtils;
import com.yalantis.ucrop.UCrop;
import com.yalantis.ucrop.UCropImageEngine;

import java.io.File;
import java.util.ArrayList;

import top.zibin.luban.Luban;
import top.zibin.luban.OnNewCompressListener;

/**
 * @author: zzw
 * @Description:
 * @Date: 2022/9/21 21:37
 */
public class PictureUtils {

    /**
     * 打开摄像头 拍照
     *
     * @param context
     * @param isRotateImage                   true 前置 false 后置
     * @param onPictureSelectorResultListener 回调
     */
    public static void openCamera(Context context, boolean isRotateImage, OnPictureSelectorResultListener onPictureSelectorResultListener) {
        openCamera(context, SelectMimeType.ofImage(), isRotateImage, onPictureSelectorResultListener);
    }

    /**
     * 打开摄像头 录制视频
     *
     * @param context
     * @param isRotateImage                   true 前置 false 后置
     * @param onPictureSelectorResultListener 回调
     */
    public static void openVideo(Context context, boolean isRotateImage, OnPictureSelectorResultListener onPictureSelectorResultListener) {
        openCamera(context, SelectMimeType.ofVideo(), isRotateImage, onPictureSelectorResultListener);
    }

    /**
     * 打开摄像头
     *
     * @param context                         上下文
     * @param onPictureSelectorResultListener 回调
     */
    public static void openCamera(Context context, int openCamera, boolean isRotateImage, OnPictureSelectorResultListener onPictureSelectorResultListener) {
        PictureSelector.create(context)
                .openCamera(openCamera)
                .isCameraAroundState(isRotateImage)
                .setVideoThumbnailListener(new VideoThumbListener(context))
                .setCompressEngine((CompressFileEngine) (context1, source, call) -> Luban.with(context1).load(source).ignoreBy(100)
                        .setCompressListener(new OnNewCompressListener() {
                            @Override
                            public void onStart() {

                            }

                            @Override
                            public void onSuccess(String source, File compressFile) {
                                if (call != null) {
                                    call.onCallback(source, compressFile.getAbsolutePath());
                                }
                            }

                            @Override
                            public void onError(String source, Throwable e) {
                                if (call != null) {
                                    call.onCallback(source, null);
                                }
                            }
                        }).launch())
                .forResult(new OnResultCallbackListener<LocalMedia>() {
                    @Override
                    public void onResult(ArrayList<LocalMedia> result) {
                        onPictureSelectorResultListener.onResult(result);
                    }

                    @Override
                    public void onCancel() {
                    }
                });
    }

    /**
     * 设置头像
     *
     * @param mContext
     * @param selectResult                    结果
     * @param onPictureSelectorResultListener 结果回调
     */
    public static void createAvatar(Context mContext, ArrayList<LocalMedia> selectResult, OnPictureSelectorResultListener onPictureSelectorResultListener) {
        create(mContext, SelectMimeType.ofImage(), selectResult, 1, 1, true, onPictureSelectorResultListener);
    }

    /**
     * 选择单张图片
     *
     * @param mContext
     * @param selectResult                    结果
     * @param onPictureSelectorResultListener 结果回调
     */
    public static void createImageMin(Context mContext, ArrayList<LocalMedia> selectResult, OnPictureSelectorResultListener onPictureSelectorResultListener) {
        create(mContext, SelectMimeType.ofImage(), selectResult, 1, 1, false, onPictureSelectorResultListener);
    }


    /**
     * 选择多张图片
     *
     * @param mContext
     * @param selectResult                    结果
     * @param selectMax                       最多选择
     * @param onPictureSelectorResultListener 结果回调
     */
    public static void createImageMax(Context mContext, int selectMax, ArrayList<LocalMedia> selectResult, OnPictureSelectorResultListener onPictureSelectorResultListener) {
        create(mContext, SelectMimeType.ofImage(), selectResult, 1, selectMax, false, onPictureSelectorResultListener);
    }

    /**
     * 选择单个视频
     *
     * @param mContext
     * @param selectResult                    结果
     * @param onPictureSelectorResultListener 结果回调
     */
    public static void createVideo(Context mContext, ArrayList<LocalMedia> selectResult, OnPictureSelectorResultListener onPictureSelectorResultListener) {
        create(mContext, SelectMimeType.ofVideo(), selectResult, 1, 1, false, onPictureSelectorResultListener);
    }

    /**
     * 选择单个音频
     *
     * @param mContext
     * @param selectResult                    结果
     * @param onPictureSelectorResultListener 结果回调
     */
    public static void createAudio(Context mContext, ArrayList<LocalMedia> selectResult, OnPictureSelectorResultListener onPictureSelectorResultListener) {
        create(mContext, SelectMimeType.ofAudio(), selectResult, 1, 1, false, onPictureSelectorResultListener);
    }

    /**
     * 选择 媒体 图片 视频 音频
     *
     * @param mContext
     * @param selectResult                    结果
     * @param onPictureSelectorResultListener 结果回调
     */
    public static void createPicture(Context mContext, ArrayList<LocalMedia> selectResult, OnPictureSelectorResultListener onPictureSelectorResultListener) {
        create(mContext, SelectMimeType.ofAll(), selectResult, 1, 1, false, onPictureSelectorResultListener);
    }

    /**
     * 默认设置
     *
     * @param mContext
     * @param selectMimeType                  结果
     * @param selectResult                    结果
     * @param selectMin                       最少选择
     * @param selectMax                       最多选择
     * @param isCrop                          是否剪裁
     * @param onPictureSelectorResultListener 结果回到
     */
    public static void create(Context mContext, int selectMimeType, ArrayList<LocalMedia> selectResult, int selectMin, int selectMax, boolean isCrop,
                              OnPictureSelectorResultListener onPictureSelectorResultListener) {
        PictureSelector.create(mContext)
                .openGallery(selectMimeType)
                .setMaxSelectNum(selectMax)
                .setCropEngine(getCropFileEngine(isCrop))
                .setMinSelectNum(selectMin)
                .setFilterVideoMaxSecond(selectMimeType == SelectMimeType.ofVideo() ? 60 : 60 * 10)
                .setFilterVideoMinSecond(5)
                .setRecordVideoMaxSecond(selectMimeType == SelectMimeType.ofVideo() ? 60 : 60 * 10)
                .setRecordVideoMinSecond(5)
                .setFilterMaxFileSize(100 * 1024 * 1024)
                .setCameraInterceptListener(new MeOnCameraInterceptListener())
                .isFilterSizeDuration(true)
                .setSelectedData(selectResult)
                .setRecordAudioInterceptListener(new MeOnRecordAudioInterceptListener())
                .setCompressEngine((CompressFileEngine) (context, source, call) -> Luban.with(context).load(source).ignoreBy(100)
                        .setCompressListener(new OnNewCompressListener() {
                            @Override
                            public void onStart() {

                            }

                            @Override
                            public void onSuccess(String source, File compressFile) {
                                if (call != null) {
                                    call.onCallback(source, compressFile.getAbsolutePath());
                                }
                            }

                            @Override
                            public void onError(String source, Throwable e) {
                                if (call != null) {
                                    call.onCallback(source, null);
                                }
                            }
                        }).launch()).setImageEngine(GlideUtils.createGlideEngine())
                .forResult(new OnResultCallbackListener<LocalMedia>() {
                    @Override
                    public void onResult(ArrayList<LocalMedia> result) {
                        for (LocalMedia media : result) {
                            if (media.getWidth() == 0 || media.getHeight() == 0) {
                                if (PictureMimeType.isHasImage(media.getMimeType())) {
                                    MediaExtraInfo imageExtraInfo = MediaUtils.getImageSize(mContext, media.getPath());
                                    media.setWidth(imageExtraInfo.getWidth());
                                    media.setHeight(imageExtraInfo.getHeight());
                                } else if (PictureMimeType.isHasVideo(media.getMimeType())) {
                                    MediaExtraInfo videoExtraInfo = MediaUtils.getVideoSize(mContext, media.getPath());
                                    media.setWidth(videoExtraInfo.getWidth());
                                    media.setHeight(videoExtraInfo.getHeight());
                                }
                            }
//                            LogUtils.e("文件名: " + media.getFileName() + "\n" +
//                                    "是否压缩:" + media.isCompressed() + "\n" +
//                                    "压缩路径:" + media.getCompressPath() + "\n" +
//                                    "初始路径:" + media.getPath() + "\n" +
//                                    "绝对路径:" + media.getRealPath() + "\n" +
//                                    "是否裁剪:" + media.isCut() + "\n" +
//                                    "裁剪路径:" + media.getCutPath() + "\n" +
//                                    "是否开启原图:" + media.isOriginal() + "\n" +
//                                    "原图路径:" + media.getOriginalPath() + "\n" +
//                                    "沙盒路径:" + media.getSandboxPath() + "\n" +
//                                    "水印路径:" + media.getWatermarkPath() + "\n" +
//                                    "视频缩略图:" + media.getVideoThumbnailPath() + "\n" +
//                                    "原始宽高: " + media.getWidth() + "x" + media.getHeight() + "\n" +
//                                    "裁剪宽高: " + media.getCropImageWidth() + "x" + media.getCropImageHeight() + "\n" +
//                                    "文件大小: " + PictureFileUtils.formatAccurateUnitFileSize(media.getSize()) + "\n" +
//                                    "文件大小: " + media.getSize() + "\n" +
//                                    "文件时长: " + media.getDuration()
//                            );
                        }
                        onPictureSelectorResultListener.onResult(result);
                    }

                    @Override
                    public void onCancel() {

                    }
                });
    }

    /**
     * 查看图片大图
     *
     * @param mContext
     * @param position
     * @param localMedia
     */
    public static void openImage(Context mContext, int position, ArrayList<LocalMedia> localMedia) {
        PictureSelector.create(mContext)
                .openPreview()
                .isHidePreviewDownload(true)
                .setImageEngine(GlideUtils.createGlideEngine())
                .setExternalPreviewEventListener(new OnExternalPreviewEventListener() {
                    @Override
                    public void onPreviewDelete(int position) {

                    }

                    @Override
                    public boolean onLongPressDownload(Context context, LocalMedia media) {
                        return false;
                    }

                }).startActivityPreview(position, false, localMedia);
    }

    public static void openImage(Context mContext, int position, String imageUrl) {
        ArrayList<LocalMedia> localMedia = new ArrayList<>();
        for (String url : imageUrl.split(",")) {
            localMedia.add(LocalMedia.generateHttpAsLocalMedia(url));
        }
        PictureSelector.create(mContext)
                .openPreview()
                .setImageEngine(GlideUtils.createGlideEngine())
                .setExternalPreviewEventListener(new OnExternalPreviewEventListener() {
                    @Override
                    public void onPreviewDelete(int position) {

                    }

                    @Override
                    public boolean onLongPressDownload(Context context, LocalMedia media) {
                        return false;
                    }

                }).startActivityPreview(position, false, localMedia);
    }

    /**
     * 预览视频
     *
     * @param mContext
     * @param position
     * @param localMedia
     */
    public static void openVideo(Context mContext, int position, ArrayList<LocalMedia> localMedia) {
        openVideo(mContext, position, localMedia, null);
    }

    /**
     * 预览视频
     *
     * @param mContext
     * @param position
     * @param localMedia
     */
    public static void openVideo(Context mContext, int position, ArrayList<LocalMedia> localMedia, VideoPlayerEngine videoPlayerEngine) {
        PictureSelector.create(mContext)
                .openPreview()
                .setImageEngine(GlideUtils.createGlideEngine())
                .setVideoPlayerEngine(videoPlayerEngine)
                .isAutoVideoPlay(true)
                .setExternalPreviewEventListener(new OnExternalPreviewEventListener() {
                    @Override
                    public void onPreviewDelete(int position) {

                    }

                    @Override
                    public boolean onLongPressDownload(Context context, LocalMedia media) {
                        return false;
                    }

                }).startActivityPreview(position, false, localMedia);
    }


    /**
     * 裁剪引擎
     *
     * @return
     */
    private static ImageFileCropEngine getCropFileEngine(boolean isCrop) {
        return isCrop ? new ImageFileCropEngine() : null;
    }

    /**
     * 自定义裁剪
     */
    private static class ImageFileCropEngine implements CropFileEngine {

        @Override
        public void onStartCrop(Fragment fragment, Uri srcUri, Uri destinationUri, ArrayList<String> dataSource, int requestCode) {
            UCrop.Options options = buildOptions();
            UCrop uCrop = UCrop.of(srcUri, destinationUri, dataSource);
            uCrop.withOptions(options);
            uCrop.setImageEngine(new UCropImageEngine() {
                @Override
                public void loadImage(Context context, String url, ImageView imageView) {
                    Glide.with(context).load(url).override(180, 180).into(imageView);
                }

                @Override
                public void loadImage(Context context, Uri url, int maxWidth, int maxHeight, OnCallbackListener<Bitmap> call) {
                    Glide.with(context)
                            .asBitmap()
                            .load(url)
                            .override(maxWidth, maxHeight)
                            .into(new CustomTarget<Bitmap>() {
                                @Override
                                public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
                                    if (call != null) {
                                        call.onCall(resource);
                                    }
                                }

                                @Override
                                public void onLoadCleared(@Nullable Drawable placeholder) {
                                    if (call != null) {
                                        call.onCall(null);
                                    }
                                }
                            });
                }
            });
            uCrop.start(fragment.requireActivity(), fragment, requestCode);
        }
    }


    /**
     * 配制UCrop,可根据需求自我扩展
     *
     * @return
     */
    private static UCrop.Options buildOptions() {
        UCrop.Options options = new UCrop.Options();
        options.setHideBottomControls(true);
        options.setFreeStyleCropEnabled(true);
        options.setShowCropFrame(true);
        options.setShowCropGrid(false);
        options.setCircleDimmedLayer(false);
        options.withAspectRatio(1, 1);
        options.isCropDragSmoothToCenter(false);
        options.setMaxScaleMultiplier(100);
        return options;
    }


    public interface OnPictureSelectorResultListener {
        void onResult(ArrayList<LocalMedia> result);
    }

    /**
     * 自定义拍照
     */
    private static class MeOnCameraInterceptListener implements OnCameraInterceptListener {

        @Override
        public void openCamera(Fragment fragment, int cameraMode, int requestCode) {
            SimpleCameraX camera = SimpleCameraX.of();
            camera.isAutoRotation(true);
            camera.setCameraMode(cameraMode);
            camera.setVideoFrameRate(50);
            camera.setVideoBitRate(5 * 1024 * 1024);
            camera.isDisplayRecordChangeTime(true);
            camera.isManualFocusCameraPreview(true);
            camera.isZoomCameraPreview(true);
            camera.setImageEngine((context, url, imageView) -> Glide.with(context).load(url).into(imageView));
            camera.start(fragment.requireActivity(), fragment, requestCode);
        }
    }

    /**
     * 录音回调事件
     */
    private static class MeOnRecordAudioInterceptListener implements OnRecordAudioInterceptListener {

        @Override
        public void onRecordAudio(Fragment fragment, int requestCode) {
            String[] recordAudio = {Manifest.permission.RECORD_AUDIO};
            if (PermissionChecker.isCheckSelfPermission(fragment.getContext(), recordAudio)) {
                startRecordSoundAction(fragment, requestCode);
            } else {
                PermissionChecker.getInstance().requestPermissions(fragment,
                        new String[]{Manifest.permission.RECORD_AUDIO}, new PermissionResultCallback() {
                            @Override
                            public void onGranted() {
                                startRecordSoundAction(fragment, requestCode);
                            }

                            @Override
                            public void onDenied() {
                            }
                        });
            }
        }
    }

    /**
     * 启动录音意图
     *
     * @param fragment
     * @param requestCode
     */
    private static void startRecordSoundAction(Fragment fragment, int requestCode) {
        Intent recordAudioIntent = new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
        if (recordAudioIntent.resolveActivity(fragment.requireActivity().getPackageManager()) != null) {
            fragment.startActivityForResult(recordAudioIntent, requestCode);
        } else {
            ToastUtils.showToast(fragment.getContext(), "The system is missing a recording component");
        }
    }
}

上文监听 VideoThumbListener:

java 复制代码
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
import com.luck.picture.lib.interfaces.OnKeyValueResultCallbackListener;
import com.luck.picture.lib.interfaces.OnVideoThumbnailEventListener;
import com.luck.picture.lib.utils.PictureFileUtils;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 *
 * @Description: 视频缩略图
 * 
 */
public class VideoThumbListener implements OnVideoThumbnailEventListener {

    private Context context;

    public VideoThumbListener(Context context) {
        this.context = context;
    }

    @Override
    public void onVideoThumbnail(Context context, String videoPath, OnKeyValueResultCallbackListener call) {
        Glide.with(context).asBitmap().sizeMultiplier(0.6F).load(videoPath).into(new CustomTarget<Bitmap>() {
            @Override
            public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
                ByteArrayOutputStream stream = new ByteArrayOutputStream();
                resource.compress(Bitmap.CompressFormat.JPEG, 60, stream);
                FileOutputStream fos = null;
                String result = null;
                try {
                    File targetFile = new File(getVideoThumbnailDir(), "thumbnails_" + System.currentTimeMillis() + ".jpg");
                    fos = new FileOutputStream(targetFile);
                    fos.write(stream.toByteArray());
                    fos.flush();
                    result = targetFile.getAbsolutePath();
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    PictureFileUtils.close(fos);
                    PictureFileUtils.close(stream);
                }
                if (call != null) {
                    call.onCallback(videoPath, result);
                }
            }

            @Override
            public void onLoadCleared(@Nullable Drawable placeholder) {
                if (call != null) {
                    call.onCallback(videoPath, "");
                }
            }
        });
    }

    private String getVideoThumbnailDir() {
        File externalFilesDir = context.getExternalFilesDir("");
        File customFile = new File(externalFilesDir.getAbsolutePath(), "Thumbnail");
        if (!customFile.exists()) {
            customFile.mkdirs();
        }
        return customFile.getAbsolutePath() + File.separator;
    }
}

使用:

使用摄像头拍照:

kotlin 复制代码
PictureUtils.openCamera(requireActivity(), false) {
	val localMedia = it[0]
               
}

选择单张图片

kotlin 复制代码
  PictureUtils.createImageMin(this, ArrayList()) {
		val localMedia = it[0]
              
}

选择多张图片

kotlin 复制代码
PictureUtils.createImageMax(this, 9, arrayListOf()) { result: ArrayList<LocalMedia> ->
   ...                         
}

简单使用如上。

END

相关推荐
StackNoOverflow2 小时前
MySQL Explain 返回列详解:从入门到实战,附 SQL 与避坑大全
android
CYRUS_STUDIO10 小时前
Frida 检测与对抗实战:进程、maps、线程、符号全特征清除
android·逆向
csj5012 小时前
安卓基础之《(28)—Service组件》
android
lhbian14 小时前
PHP、C++和C语言对比:哪个更适合你?
android·数据库·spring boot·mysql·kafka
catoop15 小时前
Android 最佳实践、分层架构与全流程解析(2025)
android
ZHANG13HAO15 小时前
Android 13 特权应用(Android Studio 开发)调用 AOSP 隐藏 API 完整教程
android·ide·android studio
田梓燊15 小时前
leetcode 142
android·java·leetcode
angerdream16 小时前
Android手把手编写儿童手机远程监控App之JAVA基础
android
菠萝地亚狂想曲16 小时前
Zephyr_01, environment
android·java·javascript
sTone8737516 小时前
跨端框架通信机制全解析:从 URL Schema 到 JSI 到 Platform Channel
android·前端