Android 设置头像 - 相册拍照

news2024/11/19 23:37:30

    Android开发在个人信息管理中,如果设置头像,一般都提供了从相册选择和拍照两种方式。下午将针对设置用户头像相册和拍照两种方式的具体实现进行详细说明。

    在实际实现过程中需要使用到权限管理,新版本的Android需要动态申请权限,权限的相关内容参考: Android 设置头像 - 权限申请一文。

界面实现

    根据分析,个人头像设置的界面中需要使用到ImageView、底部选择框两种组件进行实现。实现效果如下图所示,在该界面中底部选择框使用了XPopup组件库提供的底部选择框组件。
在这里插入图片描述

registerForActivityResult介绍

    registerForActivityResult()是startActivityForResult()的替代,简化了数据回调的写法。并且目前在新版本的Android开发中,官方建议弃用startActivityForResult()方法。因此本demo的实现过程中将采用registerForActivityResult进行实现。
    我们都知道startActivityForResult是实现activity切换回调的一个方法,在使用这个旧方法的过程中我们需要传入一个intent并且在activity中配置回调接收事件。与之不同的是,在使用registerForActivityResult方法时并不会设计到intent的调用,该方法将返回一个ActivityResultLauncher(activity结果启动器)。该方法如下代码所示:

    @NonNull
    @Override
    public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
            @NonNull ActivityResultContract<I, O> contract,
            @NonNull ActivityResultCallback<O> callback) {
        return registerForActivityResult(contract, mActivityResultRegistry, callback);
    }

    通过代码最终可知,最终该方法将调用register方法进行注册结果转化器和回调还是,register方法如下:

 /**
  * Register a new callback with this registry.
  *
  * This is normally called by a higher level convenience methods like
  * {@link ActivityResultCaller#registerForActivityResult}.
  *
  * @param key a unique string key identifying this call
  * @param lifecycleOwner a {@link LifecycleOwner} that makes this call.
  * @param contract the contract specifying input/output types of the call
  * @param callback the activity result callback
  *
  * @return a launcher that can be used to execute an ActivityResultContract.
  */
 @NonNull
 public final <I, O> ActivityResultLauncher<I> register(
         @NonNull final String key,
         @NonNull final LifecycleOwner lifecycleOwner,
         @NonNull final ActivityResultContract<I, O> contract,
         @NonNull final ActivityResultCallback<O> callback

    从上述源码中不难看出在调用registerForActivityResult方法时需要传入一个结果转化器(ActivityResultContract)和处理回调函数(ActivityResultCallback)。
    ActivityResultContract的源码如下:

/**
 * A contract specifying that an activity can be called with an input of type [I]
 * and produce an output of type [O].
 *
 * Makes calling an activity for result type-safe.
 *
 * @see androidx.activity.result.ActivityResultCaller
 */
abstract class ActivityResultContract<I, O> {
    /**
     * Create an intent that can be used for [android.app.Activity.startActivityForResult].
     * 将intent进行加工,可以通过这个方法加过传入的intent
     */
    abstract fun createIntent(context: Context, input: I): Intent

    /**
     * Convert result obtained from [android.app.Activity.onActivityResult] to [O].
     * 加工结果返回的intent
     */
    abstract fun parseResult(resultCode: Int, intent: Intent?): O

    /**
     * An optional method you can implement that can be used to potentially provide a result in
     * lieu of starting an activity.
     *
     * @return the result wrapped in a [SynchronousResult] or `null` if the call
     * should proceed to start an activity.
     */
    open fun getSynchronousResult(context: Context, input: I): SynchronousResult<O>? {
        return null
    }

    /**
     * The wrapper for a result provided in [getSynchronousResult]. This allows differentiating
     * between a null [T] synchronous result and no synchronous result at all.
     */
    class SynchronousResult<T>(val value: T)
}

    在该抽象类中createIntent方法和parseResult方法尤为重要,一个对acticity跳转时的intent进行加工,一个对跳回时携带的intent进行处理。在这里需要说明的时,当我们进行拍照时,如果自己设定了一个uri(当我们指定文件名的时候需要自己设定这个uri)时,跳回携带的intent中将不再携带uri,及intent.getData()方法将返回null。
    ActivityResultCallback的源码如下:


/**
 * A type-safe callback to be called when an {@link Activity#onActivityResult activity result}
 * is available.
 *
 * @param <O> result type
 */
public interface ActivityResultCallback<O> {

    /**
     * Called when result is available
     */
    void onActivityResult(@SuppressLint("UnknownNullness") O result);
}

    在开发的过程中我们需要实现onActivityResult进行接收处理跳回携带的intent数据。
通过以上简单说明可以梳理出以下逻辑:

  • 在oncreate方法中调用 ActivityResultLauncher resultLauncher = registerForActivityResult(new TakeImageAndVideoUri(), callback);进行获取ActivityResultLauncher对象,该方法的调用建议在oncreate方法中进行,引用该方法将使用到ActivityResultRegistry对象。
  • 在点击拍照或者图库的过程中调用resultLauncher.launch(intent);进行界面跳转
  • 跳转的过程中android会自动调用你实现的TakeImageAndVideoUri对象中的createIntent方法进行intent加工。
  • 用户进行拍照或者选择图片并进行跳回
  • 跳回的过程中android将自动调用你实现的TakeImageAndVideoUri对象中的parseResult方法进行结果判断,以及携带intent加工
  • 最后将调用callback对象中的onActivityResult方法进行intent数据获取和处理。

    以上内容为registerForActivityResult()方法的简单说明,在学习了解该方法的过程中并未进行深入源码或官网学习,如描述存在问题欢迎斧正。

图片选择实现

intent跳转

    无论图库中选择图片还是拍照,其实都是通过intent配置进行实现的,该部分代码如下

        binding.headSculptureLayout.setOnClickListener(e->{
            BottomListPopupView popupView = new XPopup.Builder(PersonalInformationActivity.this)
                    .asBottomList("", ImageSelectSourceEnums.getLabels().toArray(new String[0]),
                            (position, text) -> {
                                if (text.equals(ImageSelectSourceEnums.PHOTO.getLabel())) {
                                    resultLauncher.launch(new Intent(MediaStore.ACTION_IMAGE_CAPTURE));
                                } else {
                                    Intent intent = new Intent(Intent.ACTION_PICK);
                                    intent.setType("image/*");
                                    resultLauncher.launch(intent);
                                }
                            });
            TextView cancel = popupView.findViewById(com.lxj.xpopup.R.id.tv_cancel);
            cancel.setText("取消");
            popupView.show();
        });

    代码中定义了一个点击事件,当点击headSculptureLayout布局时将进行BottomListPopupView底部选择视图的弹出,在该视图中存在两个选择【拍照】和【图库】,并配置了不同item的点击事件。及点击不同的选项将进行不同的intent配置,然后通过resultLauncher(ActivityResultLauncher的对象,在oncreate方法中通过registerForActivityResult获取的)进行跳转。
    在此额外补充一点,如果是选择进行录像则对intent进行如下配置:

Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
// 设置图像质量
intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
resultLauncher.launch(intent);

TakeImageAndVideoUri的实现

    ActivityResultContract的实现类Android已经默认提供了很多种,针对图片处理的也提供了一种。

    但为了需求的定制化,在这里我自己实现了一个ActivityResultContract的实现类TakeImageAndVideoUri。
    该实现类的源码如下:

/**
 * 拍照、录像、选择图库的ActivityResultContract
 *
 * @author baiyang
 * @since 2024-04-27
 */
public class TakeImageAndVideoUri extends ActivityResultContract<Intent, Intent> {
    private Uri uri;
    private Bundle bundle;
    private String type;
    private String action;
    public static final String IMAGE_TYPE = "image/jpeg";
    public static final String VIDEO_TYPE = "video/*";
    public static final String JPG_TYPE = "jpg";
    public static final String MP4_TYPE = "mp4";
    public static final String TYPE = "type";
    /**
    * 设置为你自己的AUTHORITY 
    */
    public static final String AUTHORITY = "com.**.***.provider";

    @NonNull
    @Override
    public Intent createIntent(@NonNull Context context, Intent input) {
        action = input.getAction();
        String mimeType = null;
        String fileName = null;
        Uri mediaUri = null;
        if (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)) {
            mimeType = IMAGE_TYPE;
            fileName = System.currentTimeMillis() + "." + JPG_TYPE;
            mediaUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            type = JPG_TYPE;
        } else if (MediaStore.ACTION_VIDEO_CAPTURE.equals(action)) {
            mimeType = VIDEO_TYPE;
            fileName = System.currentTimeMillis() + "." + MP4_TYPE;
            mediaUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
            type = MP4_TYPE;
        } else if(Intent.ACTION_PICK.equals(action)){
            type = JPG_TYPE;
            bundle = input.getBundleExtra("bundle");
            return input;
        }
        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);
        bundle = input.getBundleExtra("bundle");
        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;
        }
        if(Intent.ACTION_PICK.equals(action)){
            intent.putExtra(MediaStore.EXTRA_OUTPUT, intent.getData());
            intent.putExtra(TYPE, type);
            return intent;
        }
        intent = new Intent();
        intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
        intent.putExtra(TYPE, type);
        intent.putExtra("bundle", bundle);
        return intent;
    }
}

在这里需要说明的有以下几点

  • 一旦在createIntent方法中给intent设置了MediaStore.EXTRA_OUTPUT,则在parseResult方法中返回的intent则无法通过getData获取uri。
  • createIntent方法和parseResult方法参数intent并不是同一个对象
  • 在该实现类中使用了Bundle bundle进行intent携带额外参数的实现
  • 在进行拍照、录像、选择相册的过程中需要权限认证,实现类中的参数AUTHORITY 需要和你在AndroidManifest.xml中配置的提供者provider中的android:authorities一直,及如下代码
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.***.***.provider"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>

    其中file_paths为配置的资源访问范围xml配置文件。该部分内容可自行百度了解。

  • 代码中拍照和录像都配置了uri,指定了文件名称,但是从图库中选择为进行uri的配置,因此当intent的action为ACTION_PICK时,parseResult方法中intent已经携带了uri,因此无需再进行设置

ActivityResultCallback的实现

    通过上述方法,已经实现了拍照、图库的界面跳转和回挑过程,并且在回跳的时已经携带了我们需要的参数数据。因此我们需要实现ActivityResultCallback进行数据的业务逻辑处理。例如图片回显、裁剪、上传等操作。目前我仅仅实现了简单的回显功能,并且是通过Glide工具进行回显的。代码如下:

    /**
     * 回调
     */
    private final ActivityResultCallback<Intent> callback = result -> {
        if (Objects.nonNull(result)) {
            Uri uri = result.getParcelableExtra(MediaStore.EXTRA_OUTPUT);
            String type = result.getStringExtra(TakeImageAndVideoUri.TYPE);
            Glide.with(getApplicationContext()).load(uri).into(binding.headSculpture);
        } else {
            LogUtils.e("拍照、录像数据回调失败,未回传相关数据");
        }
    };

    后续我将实现图片的裁剪、上传等业务逻辑,可见其他文章。

总结

    本文主要阐述了android通过图库和拍照两种方法设置头像的功能实现。在具体实现过程中主要使用了以下技术:

  • XPopup的BottomListPopupView实现底部选择试图
  • ActivityResultLauncher对象、 registerForActivityResult()方法、TakeImageAndVideoUri(ActivityResultContract实现类型)和ActivityResultCallback实现类;分别进行intent跳转,intent输入输出配置以及回跳回调。
  • Glide 本地图片回显,后续还将通过该工具进行网络图片回显
  • 在布局方面主要使用了ConstraintLayout、LinearLayoutCompat、RelativeLayout三种组合布局。

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

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

相关文章

【JAVA】一文掌握Java并发编程

Java 开发中&#xff0c;并发编程属于相当重要的一个知识点&#xff0c;可以说&#xff0c;Java 的并发能力&#xff0c;是成就今日 Java 地位的因素之一。Java 的并发编程由浅入深实质上是包含 Java&#xff08;API&#xff09;层、JVM&#xff08;虚拟机&#xff09;层、内核…

Mac下使用homebrew管理多版本mysql同时启动

Mac下使用homebrew管理多版本mysql同时启动 思路 给每个版本分配不同的数据目录和配置文件即可 本文尝试了使用 brew 安装管理多个MySQL版本&#xff0c;同时运行、直接切换 安装 如果已有数据文件请自行备份以及使用 安装 mysql 5.7 brew install mysql5.7在 /opt/home…

运维笔记:基于阿里云跨地域服务器通信(上)

运维笔记 阿里云&#xff1a;跨地域服务器通信&#xff08;上&#xff09; - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this a…

C语言编译的优化等级应该选哪个?O0、O1、O2还是O3

在使用IDE开发STM32程序时&#xff0c;IDE一般都会提供优化等级设置的选项&#xff0c;例如下图中KEIL软件优化等级的设置。 从上图中也可以看出&#xff0c;设置不同的优化等级&#xff0c;实际上是修改了编译器的编译参数。这个编译器是由ARM公司提供的C/C编译器armclang或者…

opencv4.8 系列一环境搭搭建

open 运行环境&#xff1a; vs2017 下载地址&#xff1a;https://www.123pan.com/s/cVyRVv-ydPWh.html 一&#xff1a;新建项目 二&#xff1a;核心代码&#xff1a; 在这里插入代码片 #include<opencv2/opencv.hpp>int main(int argc,char** argv) {cv::Mat src cv…

【软考高项】二十六、范围管理基础内容

一、管理基础 产品范围和项目范围 产品范围强调结果&#xff0c;项目范围强调结果 管理的新实践 &#xff1a;需求一直是项目管理的关注重点&#xff0c;需求管理过程结束于需求关闭&#xff0c;即把产品、服务或成果移交给接收方&#xff0c;以便长期测量、监控、实现并维持收…

ptyhon画图显示中文

import matplotlib.pyplot as plt import matplotlib# 设置中文字体 matplotlib.rcParams[font.sans-serif] [SimHei] matplotlib.rcParams[font.family]sans-serifplt.plot([1, 2, 3, 4]) plt.xlabel(这是x轴) plt.ylabel(这是y轴) plt.title(这是标题) plt.show()用这个代码…

anaconda安装python 3.8环境

打开anaconda命令行窗口 在命令行窗口中&#xff0c;输入命令&#xff1a;conda create -n py38 python3.8 执行命令后&#xff0c;显示conda版本、安装路径和安装的包 然后提醒是否安装&#xff0c;输入y 等待安装完成。然后进入python3.8&#xff0c;执行命令&#xff1a;con…

收藏:什么是协程的通俗解析

不错的视频&#xff1a;到底该怎么理解协程&#xff1f;_哔哩哔哩_bilibili 重点的要点&#xff1a; 比如这个函数&#xff1a; python中&#xff0c;使用yield关键字来做协程&#xff0c;就是暂停可以去执行其他东西&#xff0c;然后其他东西执行完后&#xff0c;继续执行yiel…

抓包理解协议

用的Wireshark 抓包 1.抓包网卡选择 - WLAN 无线网卡&#xff0c;其他是本地虚拟机的网卡 这里分别是开始捕获、停止捕获、重新捕获、网卡选择&#xff0c;下面是可以过滤选择 过滤tcp包 3次握手&#xff1a; source是源地址&#xff0c; destination是目标地址&#xff0c;in…

Mysql用语句创建表/插入列【示例】

一、 创建表 COMMENT表示字段或列的注释 -- 新建student表 CREATE TABLE student (id BIGINT NOT NULL COMMENT 学生id, enroll_date DATE NOT NULL COMMENT 注册时间, NAME VARCHAR(18) DEFAULT NOT NULL COMMENT 学生姓名, deal_flag TINYINT(1) DEFAULT 0 NOT NULL COMM…

创新入门|从点击到转化:AI个性化登陆页助力潜在客户转化

在数字营销的竞争格局中&#xff0c;采用先进技术对于旨在区分自己并吸引受众的企业至关重要。人工智能 &#xff08;AI&#xff09; 成为一项关键技术&#xff0c;尤其是在制作个性化登录页面的艺术方面。这些页面不仅仅是品牌与其潜在客户之间的第一个接触点;它们是吸引兴趣、…

vue-admin-template项目实现中英文切换

实现效果&#xff1a; 1.安装 *注意版本号 npm install vue-i18n8.24.5 -S2.新建文件夹 在src目录下新建lang文件夹&#xff0c;里面有3个文件 // index.js import Vue from vue import VueI18n from vue-i18n import Cookies from js-cookie import elementEnLocale fr…

Redis 服务等过期策略和内存淘汰策略解析

redis服务是基于内存运行的&#xff0c;所以很多数据都存放在内存中&#xff0c;但是内存又不是无限的&#xff0c;所以redis就引出了key的过期和淘汰策略。 一、Redis的过期策略&#xff1a; 我们在set key的时候&#xff0c;可以给它设置一个过期时间&#xff0c;比如expire …

【UnityRPG游戏制作】RPG项目的背包系统商城系统和BOSS大界面

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…

【06】JAVASE-数组讲解【从零开始学JAVA】

Java零基础系列课程-JavaSE基础篇 Lecture&#xff1a;波哥 Java 是第一大编程语言和开发平台。它有助于企业降低成本、缩短开发周期、推动创新以及改善应用服务。如今全球有数百万开发人员运行着超过 51 亿个 Java 虚拟机&#xff0c;Java 仍是企业和开发人员的首选开发平台。…

华为MRS服务使用记录

背景&#xff1a;公司的业务需求是使用华为的这一套成品来进行开发&#xff0c;使用中发现&#xff0c;这个产品跟原生的Hadoop的那一套的使用&#xff0c;还是有很大的区别的&#xff0c;现记录一下&#xff0c;避免以后忘了 一、原始代码的下载 下载地址&#xff1a;MRS样例…

使用Umbrello学习工厂模式

工厂方法模式之所以有一个别名叫多态性工厂模式是因为具体工厂类都有共同的接口&#xff0c; 或者有共同的抽象父类。 当系统扩展需要添加新的产品对象时&#xff0c;仅仅需要添加一个具体对象以及一个具体工厂对 象&#xff0c;原有工厂对象不需要进行任何修改&#xff0c;也不…

Go语言中有哪些常见的编码规范和最佳实践?

文章目录 一、命名规范1. 包名2. 变量名3. 函数名原因 二、代码格式1. 花括号位置2. 缩进3. 空格示例代码原因 三、错误处理1. 检查错误2. 错误值3. 多重错误处理示例代码原因 四、注释1. 注释内容2. 注释位置3. 避免冗余注释示例代码原因 五、总结 在Go语言编程中&#xff0c;…

数据结构––复杂度

目录 一.时间复杂度 1.1定义 1.2时间复杂度的分类 1.3时间复杂度基本计算规则 1.4例子 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.4.10 1.4.11 1.4.12 1.4.13 二.空间复杂度 2.1定义 2.2推导大O阶方法 一.时间复杂度 1.1定义 算法的时间…