Android 设置头像 - 裁剪及圆形头像

书接上文 Android 设置头像 - 相册拍照,通过相册和照片的设置就可以获取到需要的头像信息,但是在通常情况下,我们还想要实现针对头像的裁剪功能和圆形头像功能。

先上截图:

图像裁剪

通常裁剪可以分为程序自动裁剪和用户选择裁剪两种方式,程序自动裁剪及程序中设置固定的裁剪比例然后系统自动进行裁剪,Glide即可实现该功能。但这种实现方式往往不能满足用户实际需求,及实际裁剪需要用户进行选择图片裁剪范围。下面将提供两种图片裁剪的实现逻辑,出了本文描述的裁剪实现逻辑外还有自定义裁剪view等其他实现逻辑,由于技术有限,再次仅仅阐述较为简单的两种实现逻辑。

第三方裁剪库

目前存在很对开源的第三方Android裁剪工具库,通过比较uCrop的裁剪工具库较为流行,本文将以该裁剪工具集成为例进行说明。uCrop的GitHub地址点这里。通过README文档可知,集成该工具需要以下操作:

  • gradle引入
  • AndroidManifest.xml中声明Activity
  • 业务代码中调用UCrop进行裁剪
  • 通过onActivityResult接收裁剪结果intent

上述四个过程在github中作者都进行了详细说明,下面进行我理解的简单说明:
采用该工具需要传入目标Uri

使用该工具进行裁剪需要输入源Uri和目标Uri,及被参见图片Uri和裁剪后图片存放的Uri,不同Android版本的Uri获取方式不同,Uri相关内容本人也一知半解,后续了解后将另文说明。
AndroidManifest.xml中声明Activity

相信使用module开发或者引入过arr资源包的都清楚需要在AndroidManifest.xml中引入第三方arr的Activity文件。根据这个说明不难推断出Ucrop为一个arr资源包并非jar包,并且结合网上了解到的自定义裁剪试图的实现逻辑不难推断出,Ucrop也实现了一个自定义Activity进行裁剪,当用户发起裁剪请求时,将跳转到Ucrop的Activity中,待裁剪完成后进行回跳并携带裁剪结果。

通过onActivityResult接收裁剪结果intent

UCrop.start()方法将进行裁剪,通过查看源码,该方法调用了activity.startActivityForResult(getIntent(activity), requestCode);方法,因此需要重写onActivityResult方法进行接收回跳携带参数。但是由于startActivityForResult即将弃用,所以这种方式我不建议使用。考虑是否可以通过重写UCrop.start方法通过ActivityResultLauncher.launch的方法进行裁剪调用。

系统默认裁剪intent调用

系统默认裁剪实现是裁剪过程中最简单的实现方式,按照百度结果只需要创建一个Intent intent = new Intent("com.android.camera.action.CROP");意图并进行相关参数配置即可。下面首先展示我的实现逻辑

  • 首先配置ActivityResultContract如下
java 复制代码
/**
 * 裁剪照片的contract
 *
 * @author baiyang
 * @since 2024-04-28
 */
public class CropImageResultContract extends ActivityResultContract<Intent, Intent> {
    public static final String AUTHORITY = "com.ldr.imosApp.provider";
    public static final String IMAGE_TYPE = "image/jpeg";
    public static final String JPG_TYPE = "jpg";
    private String type;
    private Uri uri;
    @NonNull
    @Override
    public Intent createIntent(@NonNull Context context, Intent input) {
        input.putExtra("crop", "true");
        input.putExtra("aspectX", 1);
        input.putExtra("aspectY", 1);
        input.putExtra("outputX", 256);
        input.putExtra("outputY", 256);
        input.putExtra("return-data", true);
        String mimeType = null;
        String fileName = null;
        mimeType = IMAGE_TYPE;
        fileName = System.currentTimeMillis() + "." + JPG_TYPE;
        Uri mediaUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            ContentValues values = new ContentValues();
            values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
            values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
            values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM);
            uri = context.getContentResolver()
                    .insert(mediaUri, values);
        } else {
            uri = FileProvider.getUriForFile(context, AUTHORITY,
                    new File(context.getExternalCacheDir().getAbsolutePath(), fileName));
        }
        input.putExtra(MediaStore.EXTRA_OUTPUT, uri);
        return input;
    }

    /**
     * 返回拍照结果,因为在调用相机的过程中设置了EXTRA_OUTPUT,因此返回时intent=null,需要重新设置一下
     *
     * @param resultCode
     * @param intent
     * @return
     */
    @Override
    public Intent parseResult(int resultCode, @Nullable Intent intent) {
        if (resultCode != Activity.RESULT_OK) {
            return null;
        }
        intent = new Intent();
        intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
        return intent;
    }
}

上述代码在拍照图库文档中已经进行详细说明,在此需要补充一点疏漏是:我们在createIntent方法中创建了一个Uri,那么intent返回的时候通过getData就不能获取裁剪结果的Uri了,而且我们创建的这个Uri会导致裁剪后的图片将作为一个资源存放到了我们的通过中,这也是为什么在Android 设置头像 - 相册拍照一文从图库中选择照片的createIntent方法中我没有创建Uri的原因,如果创建了会导致图库中存在两张重复的图片。在上述代码实现初期,我并没有创建Uri,直接通过getData进行获取Uri,在模拟器上进行测试是可以通过的,但是使用真机测试的过程中依旧会出现裁剪正常进行,但是裁剪结果getData=null的情况。目前原因不详,怀疑可能是通过intent调用默认的裁剪实现后,裁剪的返回结果在不同机型上进行了不同方式的返回,因此通过创建Uri进行接收裁剪结果是最稳妥的实现方式

  • 进行裁剪结果回调函数实现
java 复制代码
    /**
     * 裁剪照片的回调函数
     */
    private final ActivityResultCallback<Intent> cropCallback = result -> {
        if (Objects.nonNull(result)) {
            Uri uri = result.getParcelableExtra(MediaStore.EXTRA_OUTPUT);
            Glide.with(PersonalInformationActivity.this).load(uri).into(binding.headSculpture);
        } else {
            LogUtils.e("裁剪照片回调失败,未回传相关数据");
        }
    };

即获取结果后直接通过Glide进行图片回显。后续的圆形图像可以通过Glide进行实现。

  • intent调用
        在进行intent调用之前首先需要获取ActivityResultLauncher,如下:
java 复制代码
        cropResultLauncher = registerForActivityResult(new CropImageResultContract(),cropCallback);

intent调用是需要进行裁剪,而裁剪过程应该是在选择图片或者拍照后进行,因此需要修改原拍照图库的回调函数进行调用裁剪的intent,代码如下:

java 复制代码
    /**
     * 拍照或者选择图库照片后回调
     */
    private final ActivityResultCallback<Intent> callback = result -> {
        if (Objects.nonNull(result)) {
            Uri uri = result.getParcelableExtra(MediaStore.EXTRA_OUTPUT);
            // 进行裁剪
            Intent intent = new Intent("com.android.camera.action.CROP");
            intent.setDataAndType(uri, "image/*");
            cropResultLauncher.launch(intent);
//            Glide.with(getApplicationContext()).load(uri).into(binding.headSculpture);
        } else {
            LogUtils.e("拍照、录像数据回调失败,未回传相关数据");
        }
    };

上述及使用系统默认的裁剪工具进行裁剪的实现方式,不同手机,不同android版本的系统默认裁剪工具并不相同,因此最终实现效果也不相同,由于手头真机数量有限,并不能针对更多机型进行测试,所以并不确定上述逻辑实现可以适配所以手机,而且对于没有系统默认裁剪工具的机型,上述代码无法进行裁剪,而Ucrop工具可以实现裁剪。

intent到底是什么?

Intent(意图)主要是解决Android应用的各项组件之间的通讯。

Intent负责对应用中一次操作的动作、动作涉及数据、附加数据进行描述,Android则根据此Intent的描述,负责找到对应的组件,将 Intent传递给被调用的组件,并完成组件的调用。

因此,Intent在这里起着一个媒体中介的作用,专门提供组件互相调用的相关信息,实现调用者与被调用者之间的解耦。

上面为百度词条对其的解释,intent可以实现各个组件的通信。回忆我们实现的Activity跳转逻辑,通过startActivity(intent);方法进行跳转,intent我们一般进行下面定义:

java 复制代码
Intent intent = new Intent();
intent.setClass(getApplicationContext(), MainActivity.class);

上述代码表示从当前Activity跳转到MainActivity,可以看到我们并没有进行MainActivity初始化跳转等特殊设置,我没有深入研究Android底层源码,但是不难推测出当startActivity(intent);方法被调用后,intent意图交由Android底层进行处理,Android系统根据intent传参判断该意图需求,如果是Activity跳转,则从Activity注册表中获取目的Activity并进行生命周期的调用(个人揣测,不准欢迎斧正)。

上面例子为Activity跳转,一般都是同一APP内部跳转,但是当我们进行拍照、调用通讯录等系统级别的操作时也是借助Intent进行实现的,那么可以大胆推测,intent的处理时交由Android底层进行处理,他并不是某个app内部进行处理的,当底层接收到一个访问相机的intent时,将判断是否具备访问权限,如果具备则调用相机。

通过上述分析,我们可以继续推测调用系统裁剪及Intent intent = new Intent("com.android.camera.action.CROP");这个意图在交由Android底层进行处理的时候,Android底层将从它的Activity注册表中找到相关Activity进行跳转。在此引申下属代码:

java 复制代码
        <activity
            android:name=".ui.GuideActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

这段代码写Android的都清楚,Android首界面的设置,设置Android受界面需要声明intent-filter标签,并且需要设置android:exported="true",表明该Activity允许外部访问(及允许Android底层进行调用启动)。

圆形头像

圆形头像的实现可记住Glide工具,按照如下代码:

java 复制代码
            RequestOptions options = new RequestOptions()
                    .circleCropTransform();
            Glide.with(PersonalInformationActivity.this).load(uri)
                    .apply(options)
                    .into(binding.headSculpture);

在进行into到目标view的过程中Glide使用了bitmapTransform进行转化,使用的是CropCircleTransformation实现,该实现类将图片转化为圆形图片。通过借助Glide的实现圆形头像较为简单,在此不再深入Gilde的具体细节。

总结

通过裁剪和圆形头像的实现更加深入的对ActivityResultLauncher进行了学习,并在实现的过程中对Intent有了更加深入的思考,如果本文内容存在问题欢迎各位大佬进行斧正。总结本文内容。

  • Ucrop工具实现图片裁剪
  • Intent系统默认裁剪实现类
  • intent深入思考
  • Gilde圆形图片的实现
相关推荐
烬奇小云3 小时前
认识一下Unicorn
android·python·安全·系统安全
顾北川_野15 小时前
Android 进入浏览器下载应用,下载的是bin文件无法安装,应为apk文件
android
CYRUS STUDIO15 小时前
Android 下内联汇编,Android Studio 汇编开发
android·汇编·arm开发·android studio·arm
右手吉他15 小时前
Android ANR分析总结
android
PenguinLetsGo17 小时前
关于 Android15 GKI2407R40 导致梆梆加固软件崩溃
android·linux
杨武博19 小时前
音频格式转换
android·音视频
音视频牛哥21 小时前
Android音视频直播低延迟探究之:WLAN低延迟模式
android·音视频·实时音视频·大牛直播sdk·rtsp播放器·rtmp播放器·android rtmp
ChangYan.21 小时前
CondaError: Run ‘conda init‘ before ‘conda activate‘解决办法
android·conda
二流小码农1 天前
鸿蒙开发:ForEach中为什么键值生成函数很重要
android·ios·harmonyos
夏非夏1 天前
Android 生成并加载PDF文件
android