news 2026/10/15 16:41:22

AndroidPicker 图片选择器(ImagePicker)实战指南:相机拍照 + 相册选取 + 图片裁剪全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AndroidPicker 图片选择器(ImagePicker)实战指南:相机拍照 + 相册选取 + 图片裁剪全流程
  • 移动开发
  • UI组件

【免费下载链接】AndroidPicker

安卓选择器类库,包括日期及时间选择器(可用于出生日期、营业时间等)、单项选择器(可用于性别、民族、职业、学历、星座等)、二三级联动选择器(可用于车牌号、基金定投日期等)、城市地址选择器(分省级、地市级及区县级)、数字选择器(可用于年龄、身高、体重、温度等)、日历选日期择器(可用于酒店及机票预定日期)、颜色选择器、文件及目录选择器、图片选择器等……WheelPicker/DatePicker/TimePicker/OptionPicker/NumberPicker/LinkagePicker/AddressPicker/CarPlatePicker/CalendarPicker/ColorPicker/FilePicker/ImagePicker etc.

项目地址:https://gitcode.com/gh_mirrors/an/AndroidPicker
点击查看免费下载

本篇技术指南围绕 AndroidPicker 项目中的 ImagePicker 模块 展开,讲解如何通过ImagePicker单例快速完成「相机拍照」「相册选取」与「图片裁剪」三合一流程,包括权限处理、结果回调、裁剪参数定制,以及底层FileProvider、content://URI 转换等实现原理。读完本文,你将能够在一个FragmentActivity或Fragment中独立集成完整的图片选取与裁剪能力,并掌握每个裁剪配置项的默认值与取值范围。

模块定位:一个方法搞定「选图 + 裁剪」

AndroidPicker 的 ImagePicker 模块是一套基于startActivityForResult的图片选择组件,整合了两种经典开源方案的能力:图片选择(相机与相册)改造自 ImagePicker,裁剪 UI 与手势交互改造自 Android-Image-Cropper。模块对外暴露的核心 API 只有ImagePicker一个类,通过ImagePicker.getInstance()获取单例后,即可分别调用startCamera(...)、startGallery(...)、startChooser(...)启动三类来源,再配合一个抽象的 PickCallback 完成回调闭环。

在仓库中,ImagePicker 已被注册为独立模块(见 settings.gradle 中的include ':ImagePicker'),可通过 Gradle 依赖方式引入;示例工程中的 ImagePickerActivity 是它的完整可运行范例。

第一步:声明权限与合规注意

模块自身的 AndroidManifest.xml 已声明三项权限:

  • android.permission.CAMERA:调用相机拍照时需要;
  • android.permission.READ_EXTERNAL_STORAGE:读取相册/外置存储中的图片;
  • android.permission.WRITE_EXTERNAL_STORAGE:写入裁剪结果等文件。

集成时需注意两点:

  1. 这三个权限属于 Android 6.0(API 23)及以上的运行时权限,除在 manifest 中声明外,还必须在运行时向用户动态申请;
  2. 调用startCamera/startGallery/startChooser之前,要确保用户已授予对应权限,否则系统可能直接抛出安全异常或返回无数据。

好消息是:ImagePicker 内部会自动处理一部分运行时权限请求(详见下文「底层实现」小节),你只需要在Activity/Fragment的回调方法里把结果转发给ImagePicker即可。

第二步:最小可用示例(源自模块 README)

ImagePicker/README.md 给出了一个可直接照搬的完整示例,使用FragmentActivity承载:

public class ImagePickerActivity extends FragmentActivity { private final PickCallback cropCallback = new PickCallback() { @Override public void onPermissionDenied(String[] permissions, String message) { Toast.makeText(ImagePickerActivity.this, message, Toast.LENGTH_SHORT).show(); } @Override public void cropConfig(ActivityBuilder builder) { builder.setMultiTouchEnabled(true) .setGuidelines(CropImageView.Guidelines.ON_TOUCH) .setCropShape(CropImageView.CropShape.OVAL) .setRequestedSize(400, 400) .setFixAspectRatio(true) .setAspectRatio(1, 1); } @Override public void onCropImage(@Nullable Uri imageUri) { Toast.makeText(ImagePickerActivity.this, String.valueOf(imageUri), Toast.LENGTH_SHORT).show(); } }; public void onCamera(View view) { ImagePicker.getInstance().startCamera(this, true, cropCallback); } public void onGallery(View view) { ImagePicker.getInstance().startGallery(this, true, cropCallback); } @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); ImagePicker.getInstance().onActivityResult(this, requestCode, resultCode, data); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); ImagePicker.getInstance().onRequestPermissionsResult(this, requestCode, permissions, grantResults); } }

示例工程中的 ImagePickerActivity 与上述写法一致:两个按钮分别触发相机与相册(见布局文件 activity_picker_image.xml),并在onActivityResult、onRequestPermissionsResult两个生命周期回调中把结果原样转发给ImagePicker.getInstance()。

其中第二、三个参数的含义需要区分:

  • startCamera(this, true, callback)的true表示拍照后进入裁剪;
  • startGallery(this, true, callback)的true表示选图后进入裁剪;
  • 如果只想取原图不裁剪,传false即可(示例工程中的相册按钮正是如此,见 ImagePickerActivity.java#L80)。

第三步:理解 PickCallback 的五个回调时机

PickCallback 是一个抽象类(非接口,空方法全部可选覆写),按调用顺序包含五个回调:

回调方法触发时机说明
onCanceled()用户取消选择/拍照内部在resultCode != RESULT_OK时触发
onPermissionDenied(String[] permissions, String message)用户拒绝相机或存储权限内部会传入中文提示,如「没有相机权限」「没有外部存储权限」
onPickImage(@Nullable Uri imageUri)图片已选定在裁剪之前回调;若cropEnabled=false,此回调即最终结果
cropConfig(ActivityBuilder builder)即将进入裁剪页在此统一配置裁剪参数,默认实现为 OVAL、640×640、5:5 宽高比
onCropImage(@Nullable Uri imageUri)裁剪完成最终结果以Uri形式返回,可直接用于显示或上传
onCropError(@NonNull Exception error)裁剪出错例如图片 URI 为空时抛出的IllegalArgumentException("Uri is null or empty")

值得注意的是回调的顺序设计:onPickImage会先于onCropImage触发(源码见 ImagePicker.java#L236-L248),因此即使cropEnabled=true,你也可以在onPickImage中拿到裁剪前的原始图片 URI;若cropEnabled=false,onPickImage收到的就是最终结果。

第四步:启动入口 API 全览

ImagePicker.java 是单例类(内部使用静态内部类Holder实现懒加载单例),对外提供三类启动方法,且每个方法都同时支持Activity与Fragment两个重载:

// 相机拍照,cropEnabled 控制是否继续裁剪 ImagePicker.getInstance().startCamera(activity, true, callback); ImagePicker.getInstance().startCamera(fragment, true, callback); // 相册/图库选取,内部即 startChooser(..., "image/*", ...) ImagePicker.getInstance().startGallery(activity, true, callback); ImagePicker.getInstance().startGallery(fragment, true, callback); // 自定义 MIME 的文件选择(例如 "image/*"、"video/*"),默认不裁剪 ImagePicker.getInstance().startChooser(activity, "image/*", callback); ImagePicker.getInstance().startChooser(fragment, "image/*", callback);

从 HybridityUtils.java#L33-L42 的源码可见:

  • 相册选取底层发送的是Intent.ACTION_GET_CONTENT并附带Intent.CATEGORY_OPENABLE,类型为image/*;
  • startChooser允许传入任意 MIME 类型,且实现中强制cropEnabled = false(见 ImagePicker.java#L93);
  • 相机拍照发送的是MediaStore.ACTION_IMAGE_CAPTURE,并通过MediaStore.EXTRA_OUTPUT指定输出文件(见 HybridityUtils.java#L26-L31)。

第五步:ActivityBuilder 裁剪参数完全参考

当进入裁剪流程后,cropConfig(ActivityBuilder builder)中拿到的 ActivityBuilder 承载了所有裁剪外观与输出参数。它内部持有一个 CropImageOptions,所有 setter 均为链式调用并最终写入mOptions,由 CropImageOptions.validate() 在启动前统一校验取值范围。

裁剪窗口与手势

方法默认值说明
setCropShape(CropShape)RECTANGLE裁剪窗形状;枚举见 CropImageView.java#L1466-L1469,支持RECTANGLE(矩形)、OVAL(圆形/椭圆,通常配合 1:1 宽高比实现圆形头像)
setGuidelines(Guidelines)ON_TOUCH辅助参考线显示策略:OFF永不显示、ON_TOUCH拖动时显示、ON始终显示(见 CropImageView.java#L1518-L1533)
setScaleType(ScaleType)FIT_CENTER图片初始缩放适配方式
setMultiTouchEnabled(boolean)false是否允许双指多点触控缩放/旋转
setMaxZoom(int)4最大缩放倍数
setTouchRadius(float)24dp手柄可触摸区域半径(依据 48dp Rhythm 设计规范)
setSnapRadius(float)3dp裁剪框边缘吸附到边界的触发距离
setAutoZoomEnabled(boolean)true拖动/缩放时是否自动缩放适配
setShowCropOverlay(boolean)true是否显示裁剪窗外部的半透明遮罩

宽高比与结果尺寸

方法默认值说明
setFixAspectRatio(boolean)false是否锁定宽高比;注意setAspectRatio(...)内部会将其强制置为true
setAspectRatio(int x, int y)1 / 1裁剪窗宽高比,例如setAspectRatio(1, 1)配合OVAL即圆形裁剪;x、y都必须大于 0,否则validate()抛出异常
setRequestedSize(int w, int h)0, 0(不缩放)裁剪输出的目标尺寸,默认使用RESIZE_INSIDE策略(等比缩放至不超过该尺寸,图片小于目标尺寸时不变)
setRequestedSize(int w, int h, RequestSizeOptions)—可指定缩放策略:NONE不缩放、SAMPLING仅采样、RESIZE_INSIDE等比缩小、RESIZE_FIT等比适配、RESIZE_EXACT拉伸填满(见 CropImageView.java#L1541-L1578)
setMinCropWindowSize(w, h)42dp, 42dp裁剪窗允许的最小尺寸
setMinCropResultSize(w, h)40, 40裁剪结果允许的最小宽高
setMaxCropResultSize(w, h)99999, 99999裁剪结果允许的最大宽高,必须不小于最小值

边框、辅助线与遮罩外观

方法默认值说明
setBorderLineThickness(float)3dp裁剪框边框线宽
setBorderLineColor(int)Color.argb(170, 255, 255, 255)裁剪框边框颜色
setBorderCornerThickness(float)2dp四角边线粗细
setBorderCornerOffset(float)5dp角线与裁剪框边缘的偏移
setBorderCornerLength(float)14dp角线的长度
setBorderCornerColor(int)WHITE角线颜色
setGuidelinesThickness(float)1dp辅助参考线线宽
setGuidelinesColor(int)Color.argb(170, 255, 255, 255)辅助参考线颜色
setBackgroundColor(int)Color.argb(119, 0, 0, 0)裁剪窗外遮罩背景色

输出与旋转

方法默认值说明
setOutputUri(Uri)Uri.EMPTY裁剪结果的保存位置;不设置时自动创建临时文件
setOutputCompressFormat(Bitmap.CompressFormat)JPEG输出压缩格式
setOutputCompressQuality(int)90压缩质量(0–100)
setNoOutputImage(boolean)false设为true时不保存裁剪位图,仅返回裁剪矩形与旋转数据(高级用法)
setInitialCropWindowRectangle(Rect)null裁剪窗初始位置,不设置则由 padding 比例推算
setInitialRotation(int)-1初始旋转角度(0–360),默认读取图片 EXIF
setAllowRotation(boolean)true是否允许在裁剪过程中旋转
setRotationDegrees(int)90单次旋转步长(0–360,必须在此范围内)
setInitialCropWindowPaddingRatio(float)0.1初始裁剪窗相对图片边缘的内边距比例,取值必须小于 0.5

第六步:底层实现与关键调用链

6.1 运行时权限的自动申请逻辑

startCamera启动前会调用 HybridityUtils.isExplicitCameraPermissionRequired:当系统为 Android 6.0+、manifest 中声明了CAMERA权限、且当前未授予时,自动通过ActivityCompat.requestPermissions请求相机权限;授权成功后才真正拉起相机 Intent。

选图结果回来后,onActivityResultInner 会先尝试用ContentResolver.openInputStream探测 URI 是否可读(见 HybridityUtils.isUriRequiresPermissions),若不可读则自动请求READ_EXTERNAL_STORAGE。整个过程对调用方透明,你只需要在两个权限回调中把结果转发给ImagePicker.getInstance()。

6.2 content:// 到 file:// 的自动转换

handleUri 会将回调中的content://协议 URI 通过 getRealPathFromUri(查询MediaStore.Images.Media.DATA列)转换为file://路径,避免后续读写时抛出FileNotFoundException;若转换失败则保留原始 URI 原样返回。

6.3 FileProvider 与 Android 7.0 兼容

相机拍照输出文件默认位于context.getExternalCacheDir()下的pickImageResult.jpeg(见 HybridityUtils.java#L53-L60)。由于 Android 7.0(API 24)起禁止在 Intent 中传递file://URI,getIntentUri 会自动改用FileProvider生成的content://URI,其 authority 固定为${applicationId}.imagepicker.provider(见 AndroidManifest.xml),且grantUriPermissions="true"允许临时授权给相机应用。路径映射配置在 crop_image_paths.xml 中,覆盖了external-path、external-cache-path、external-files-path三类根目录。

6.4 裁剪页与结果回传

裁剪由 CropImageActivity 承担(manifest 中已声明竖屏)。ActivityBuilder通过 getCropIntent 把源图片 URI 与CropImageOptions(Parcelable)打包进 Intent(请求码CROP_IMAGE_ACTIVITY_REQUEST_CODE = 203,见 CropImageConsts.java)。结果通过CROP_IMAGE_EXTRA_RESULT键以 ActivityResult 返回,ImagePicker 解析出其中的错误或图片 URI 后分发给onCropError/onCropImage。

第七步:集成到自己的项目

ImagePicker 以独立 Gradle 模块存在,主仓库 README 中给出的引入方式为:

dependencies { implementation 'com.github.gzu-liyujiang.AndroidPicker:ImagePicker:<version>' }

若以源码方式集成,则需在 settings.gradle 中include ':ImagePicker'并在依赖中加入project(':ImagePicker')。模块自带的 consumer-rules.pro 是专用于该库的混淆规则文件(内容为空,说明无需额外 keep 规则),随 AAR 打包发布。

集成清单总结如下:

  1. 在AndroidManifest.xml中声明CAMERA、READ_EXTERNAL_STORAGE、WRITE_EXTERNAL_STORAGE权限(库模块已自带,宿主工程声明后合并生效);
  2. 在Activity/Fragment中调用startCamera/startGallery/startChooser;
  3. 重写onActivityResult与onRequestPermissionsResult并转发给ImagePicker.getInstance();
  4. 在PickCallback中按需覆写cropConfig配置裁剪参数,在onCropImage中消费最终结果;
  5. 处理得到Uri后,即可配合任意图片加载库进行显示或上传。

常见问题与注意事项

  • 只想要原图,不想裁剪:将startCamera/startGallery的第二个参数传false,此时最终结果在onPickImage中返回(见 ImagePicker.java#L246-L248);
  • 圆形头像:setCropShape(CropImageView.CropShape.OVAL)配合setAspectRatio(1, 1)、setRequestedSize(400, 400),即 README 示例与 demo 中相机入口的配置;
  • 参数非法会直接抛异常:宽高比不能小于等于 0、initialCropWindowPaddingRatio必须在[0, 0.5)、rotationDegrees必须在[0, 360]、最大结果尺寸不得小于最小结果尺寸,这些约束在CropImageOptions.validate()中强制执行,配置时需注意取值范围;
  • 相机权限兼容:Android 6.0 以下系统无需动态申请相机权限,isExplicitCameraPermissionRequired会直接返回false并跳过申请步骤;
  • 临时输出文件:相机拍照的原始输出固定为外置缓存目录下的pickImageResult.jpeg,属于应用缓存,系统可能随时清理,如需要持久化请及时处理裁剪结果。

至此,从权限声明、入口调用、回调闭环到裁剪参数定制与底层原理,ImagePicker 模块的完整使用路径已全部打通;你也可以直接运行示例工程中的 ImagePickerActivity 体验「相机拍照」与「相册选取」两个入口的实际效果。

  • 移动开发
  • UI组件

【免费下载链接】AndroidPicker

安卓选择器类库,包括日期及时间选择器(可用于出生日期、营业时间等)、单项选择器(可用于性别、民族、职业、学历、星座等)、二三级联动选择器(可用于车牌号、基金定投日期等)、城市地址选择器(分省级、地市级及区县级)、数字选择器(可用于年龄、身高、体重、温度等)、日历选日期择器(可用于酒店及机票预定日期)、颜色选择器、文件及目录选择器、图片选择器等……WheelPicker/DatePicker/TimePicker/OptionPicker/NumberPicker/LinkagePicker/AddressPicker/CarPlatePicker/CalendarPicker/ColorPicker/FilePicker/ImagePicker etc.

项目地址:https://gitcode.com/gh_mirrors/an/AndroidPicker
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 16:39:40

Windows下VLC-Qt编译与Qt多媒体开发集成实战指南

简介&#xff1a;Windows平台编译VLC-Qt的最新资源包&#xff0c;面向需要在C/Qt应用中集成VLC播放能力的开发者&#xff0c;尤其适合对源码编译流程不熟悉、希望直接获得可用库文件的中高级程序员。包内提供VLC 3.0.0 Win64运行库、VLC-Qt 1.1.1源码&#xff0c;以及作者编译完…

作者头像 李华
网站建设 2026/10/15 16:37:40

工业级多源融合定位系统:IMU主干+GNSS校准+激光约束

1. 项目概述&#xff1a;这不是登月任务&#xff0c;而是一套高精度工业级实时定位与建图系统“Apollo-11”这个名称一出现&#xff0c;很多人第一反应是1969年的载人登月——但今天我们要聊的&#xff0c;是某自动驾驶实验室内部代号为“Apollo-11”的一套嵌入式多源融合定位系…

作者头像 李华
网站建设 2026/10/15 16:37:29

Delphi AlphaControls 14.24 懒人包:VCL 皮肤机制与 WebBrowser 皮肤激活实战

简介&#xff1a;AlphaControls 14.24 懒人包面向使用 Delphi 与 CBuilder 的界面开发者&#xff0c;尤其适合需要在项目中快速集成皮肤控件、又不想被商业皮肤提示打扰的中高级程序员。该版本已包含全部组件&#xff0c;并激活 WebBrowser 皮肤&#xff0c;同时去除商业皮肤提…

作者头像 李华