Android 更换用户头像(拍照、相册选取)

您所在的位置:网站首页 雅典娜换头头像 Android 更换用户头像(拍照、相册选取)

Android 更换用户头像(拍照、相册选取)

2024-07-16 17:29| 来源: 网络整理| 查看: 265

Android 更换头像 前言正文一、新建项目二、配置项目三、布局、样式改动四、权限请求五、底部弹窗显示六、工具类七、打开相机、相册八、页面返回显示图片九、本地缓存十、后台获取十一、源码 总结

运行效果图: 在这里插入图片描述

前言

  做Android应用开发,通常是有很多的功能组成,今天就来看一下这个用户头像更换的功能该怎么去写。相信很多的小伙伴都写过这个功能,因为作为一个APP来说这是很普遍的功能,基本都会有。只要你的APP有用户模块,就会有用户的个人信息的修改,比如常规的手机号码修改、地址修改、头像修改、昵称修改等。这里面技术含量高一点的就是头像修改了,进入正题吧。

正文

  这里我还是新建一个项目来做这个头像修改的功能,这样对于没有接触过这个功能的朋友更友好,这也是我一直以来的写作风格,不要嫌我啰嗦啊。

一、新建项目

创建一个名为ChangeAvatarDemo的项目 在这里插入图片描述 项目创建好之后,先想清楚你的这个功能需要什么,换头像常规肯定是上传到后台去,那么你肯定是要有网络权限的,其次如果你的网络请求地址是http开头的话,而在Android9.0及以上版本则要配置http访问许可才行,之后你是否会用到一些第三方框架,比如圆形头像,圆角头像、图片加载、动态权限请求。

二、配置项目

基于这些考虑,首先打开app模块下的build.gradle,在dependencies闭包下添加如下依赖:

//权限请求框架 implementation 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.4@aar' implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' implementation "io.reactivex.rxjava2:rxjava:2.0.0" //热门强大的图片加载器 implementation 'com.github.bumptech.glide:glide:4.11.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0' //Google Material控件,以及迁移到AndroidX下一些控件的依赖 implementation 'com.google.android.material:material:1.2.0'

然后在android闭包下指定JDK版本为1.8

compileOptions { sourceCompatibility = 1.8 targetCompatibility = 1.8 }

添加位置如下图所示: 在这里插入图片描述 然后点击右上角Sync进行同步,到这里gradle就配置完成了。

然后打开AndroidManifest.xml,在里面配置如下权限:

添加位置如下图所示: 在这里插入图片描述

这里还有一个要适配,那就是在Android10.0时增加了作用域存储,因此我这个不用这个作用域存储,所以在你的application标签下增加这样一句话

android:requestLegacyExternalStorage="true"

在这里插入图片描述

三、布局、样式改动

首先打开项目中的styles.xml,在里面增加一个样式:

rounded 50%

然后在layout包下新建一个dialog_bottom.xml,里面的代码如下:

这是一个弹窗的布局文件,里面提供你选择拍照、打开相册、取消。而且从命名来看,这是一个底部弹窗。所以需要一个地方去触发这个弹窗从屏幕底部出现。下面打开activity_main.xml,修改代码后如下所示:

这里我用了一个ShapeableImageView,这是material库里面的一个控件,你只要知道它比普通的ImageView要🐮🍺就可以了,想要详细了解的看看Android Material UI控件之ShapeableImageView。

这里我指定了app:shapeAppearanceOverlay="@style/circleImageStyle",也就是说它变成了一个圆形图片控件。

布局就写完了。

四、权限请求

进入到MainActivity,先声明变量

//权限请求 private RxPermissions rxPermissions;

先写一个Toast提示方法。

/** * Toast提示 * * @param msg */ private void showMsg(String msg) { Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); }

然后可以写一个checkVersion()方法,用于检查当前的Android版本,并且给你提示。

/** * 检查版本 */ private void checkVersion() { //Android6.0及以上版本 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { //如果你是在Fragment中,则把this换成getActivity() rxPermissions = new RxPermissions(this); //权限请求 rxPermissions.request(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE) .subscribe(granted -> { if (granted) {//申请成功 showMsg("已获取权限"); } else {//申请失败 showMsg("权限未开启"); } }); } else { //Android6.0以下 showMsg("无需请求动态权限"); } }

然后你要在onCreate()中调用checkVersion()。使用户一进入这个页面就进行检查版本和授权。

不过这里还要防范一个问题,那就是假如用户没有通过权限。再创建一个变量

//是否拥有权限 private boolean hasPermissions = false;

然后赋值 在这里插入图片描述 只有权限全部通过授权之后才会是true。

五、底部弹窗显示

如果我没有猜错的话,你的activity_main.xml中还有一个地方报错。 不过不要担心,先增加两个变量

//底部弹窗 private BottomSheetDialog bottomSheetDialog; //弹窗视图 private View bottomView;

然后新增一个changeAvatar()方法,里面的代码如下:

/** * 更换头像 * * @param view */ public void changeAvatar(View view) { bottomSheetDialog = new BottomSheetDialog(this); bottomView = getLayoutInflater().inflate(R.layout.dialog_bottom, null); bottomSheetDialog.setContentView(bottomView); bottomSheetDialog.getWindow().findViewById(R.id.design_bottom_sheet).setBackgroundColor(Color.TRANSPARENT); TextView tvTakePictures = bottomView.findViewById(R.id.tv_take_pictures); TextView tvOpenAlbum = bottomView.findViewById(R.id.tv_open_album); TextView tvCancel = bottomView.findViewById(R.id.tv_cancel); //拍照 tvTakePictures.setOnClickListener(v -> { showMsg("拍照"); bottomSheetDialog.cancel(); }); //打开相册 tvOpenAlbum.setOnClickListener(v -> { showMsg("打开相册"); bottomSheetDialog.cancel(); }); //取消 tvCancel.setOnClickListener(v -> { bottomSheetDialog.cancel(); }); bottomSheetDialog.show(); }

这个方法就是配置弹窗的视图,并且绑定视图中的控件,设置点击事件。现在你再去看你的activity_main.xml布局,就不会报错了。并且如果你现在运行的话,当你点击图片是底部会出现弹窗。然后点击弹窗中的三个控件,或者弹窗外阴影区域都会关闭弹窗。

六、工具类

这里我会添加两个工具类,用来协助我们开发。

在com.llw.changeavatar下新建一个utils包,在这个包下新建一个BitmapUtils类,里面的代码如下:

package com.llw.changeavatar.utils; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.RadialGradient; import android.graphics.RectF; import android.graphics.Shader; import android.util.Base64; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; /** * Bitmap工具类 */ public class BitmapUtils { /** * bitmap转为base64 * * @param bitmap * @return */ public static String bitmapToBase64(Bitmap bitmap) { String result = null; ByteArrayOutputStream baos = null; try { if (bitmap != null) { baos = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos); baos.flush(); baos.close(); byte[] bitmapBytes = baos.toByteArray(); result = Base64.encodeToString(bitmapBytes, Base64.DEFAULT); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (baos != null) { baos.flush(); baos.close(); } } catch (IOException e) { e.printStackTrace(); } } return result; } /** * base64转为bitmap * * @param base64Data * @return */ public static Bitmap base64ToBitmap(String base64Data) { byte[] bytes = Base64.decode(base64Data, Base64.DEFAULT); return BitmapFactory.decodeByteArray(bytes, 0, bytes.length); } /** * url转bitmap * @param url * @return */ public static Bitmap urlToBitmap(final String url){ final Bitmap[] bitmap = {null}; new Thread(() -> { URL imageurl = null; try { imageurl = new URL(url); } catch (MalformedURLException e) { e.printStackTrace(); } try { HttpURLConnection conn = (HttpURLConnection)imageurl.openConnection(); conn.setDoInput(true); conn.connect(); InputStream is = conn.getInputStream(); bitmap[0] = BitmapFactory.decodeStream(is); is.close(); } catch (IOException e) { e.printStackTrace(); } }).start(); return bitmap[0]; } }

然后再新建一个CameraUtils类,代码如下;

package com.llw.changeavatar.utils; import android.annotation.TargetApi; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.media.ExifInterface; import android.net.Uri; import android.os.Build; import android.os.Environment; import android.provider.DocumentsContract; import android.provider.MediaStore; import android.util.Log; import android.widget.ImageView; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; /** * 相机、相册工具类 */ public class CameraUtils { /** * 相机Intent * @param context * @param outputImagePath * @return */ public static Intent getTakePhotoIntent(Context context, File outputImagePath) { // 激活相机 Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); // 判断存储卡是否可以用,可用进行存储 if (hasSdcard()) { if (Build.VERSION.SDK_INT 1024) { //重置outputStream即清空outputStream outputStream.reset(); //这里压缩50%,把压缩后的数据存放到baos中 image.compress(Bitmap.CompressFormat.JPEG, 50, outputStream); } ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); BitmapFactory.Options options = new BitmapFactory.Options(); //开始读入图片,此时把options.inJustDecodeBounds 设回true了 options.inJustDecodeBounds = true; Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, options); options.inJustDecodeBounds = false; int outWidth = options.outWidth; int outHeight = options.outHeight; //现在主流手机比较多是800*480分辨率,所以高和宽我们设置为 float height = 800f;//这里设置高度为800f float width = 480f;//这里设置宽度为480f //缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可 int zoomRatio = 1;//be=1表示不缩放 if (outWidth > outHeight && outWidth > width) {//如果宽度大的话根据宽度固定大小缩放 zoomRatio = (int) (options.outWidth / width); } else if (outWidth height) {//如果高度高的话根据宽度固定大小缩放 zoomRatio = (int) (options.outHeight / height); } if (zoomRatio Build.VERSION_CODES.KITKAT) { //4.4及以上系统使用这个方法处理图片 imagePath = CameraUtils.getImageOnKitKatPath(data, this); } else { imagePath = CameraUtils.getImageBeforeKitKatPath(data, this); } //显示图片 displayImage(imagePath); } break; default: break; } }

这里调用了displayImage方法,代码如下:

/** * 通过图片路径显示图片 */ private void displayImage(String imagePath) { if (!TextUtils.isEmpty(imagePath)) { //显示图片 Glide.with(this).load(imagePath).apply(requestOptions).into(ivHead); //压缩图片 orc_bitmap = CameraUtils.compression(BitmapFactory.decodeFile(imagePath)); //转Base64 base64Pic = BitmapUtils.bitmapToBase64(orc_bitmap); } else { showMsg("图片获取失败"); } }

那么到这里代码就写完了,下面运行一下吧。

在这里插入图片描述

九、本地缓存

如果你目前还没有与后台进行交互的话,那要让你的图片持久显示,那么你可以用到缓存。在utils包下新建一个SPUtils类,代码如下:

package com.llw.changeavatar.util; import android.content.Context; import android.content.SharedPreferences; /** * sharepref工具类 */ public class SPUtils { private static final String NAME="config"; public static void putBoolean(String key, boolean value, Context context) { SharedPreferences sp = context.getSharedPreferences(NAME, Context.MODE_PRIVATE); sp.edit().putBoolean(key, value).commit(); } public static boolean getBoolean(String key, boolean defValue, Context context) { SharedPreferences sp = context.getSharedPreferences(NAME, Context.MODE_PRIVATE); return sp.getBoolean(key, defValue); } public static void putString(String key, String value, Context context) { SharedPreferences sp = context.getSharedPreferences(NAME, Context.MODE_PRIVATE); sp.edit().putString(key, value).commit(); } public static String getString(String key, String defValue, Context context) { if(context!=null){ SharedPreferences sp = context.getSharedPreferences(NAME, Context.MODE_PRIVATE); return sp.getString(key, defValue); } return ""; } public static void putInt(String key, int value, Context context) { SharedPreferences sp = context.getSharedPreferences(NAME, Context.MODE_PRIVATE); sp.edit().putInt(key, value).commit(); } public static int getInt(String key, int defValue, Context context) { SharedPreferences sp = context.getSharedPreferences(NAME, Context.MODE_PRIVATE); return sp.getInt(key, defValue); } public static void remove(String key, Context context) { SharedPreferences sp = context.getSharedPreferences(NAME, Context.MODE_PRIVATE); sp.edit().remove(key).commit(); } }

刚才的地址这个类提供了缓存的存取方法,支持三种数据类型,String、Int、boolean。 而刚才的图片路径是String类型的,于是你可以这么写。 在这里插入图片描述 在拿到路径之后放入缓本地存中,注意我用的imageUrl作为Key,那么取出缓存也同样需要使用这个key。在什么地方取缓存呢?当然是一进入这个页面就取。就写在onCreate方法中。

//取出缓存 String imageUrl = SPUtils.getString("imageUrl",null,this); if(imageUrl != null){ Glide.with(this).load(imageUrl).apply(requestOptions).into(ivHead); }

在这里插入图片描述 这样就实现了本地图片缓存了,运行效果如下图 在这里插入图片描述

可以看到,当我杀死程序之后再进入时,它显示的是我之前从相册中选取的图片。这就证明本地缓存成功了,并且可以使用。

十、后台获取

这个由于我无法实际操作,因此我就说一下方式。

实际中大部分的图片都是不会放到缓存里面的,因为会很占空间,第二个是缓存是少量的存储。

首先拿到拍照或者打开相册后的图片路径之后,这个地址当然不是直接发送给后台的,根据我的经验,它们通常需要的是图片的base64,如下图所示:在这里插入图片描述 这里的base64Pic是String类型的,它的数据会比较长,如果你的后台要求使用这种方式的话,那么你记得让他把这个字段的上限放到最大,否则会存储不完成,造成丢失。后台拿到这个base64Pic之后,会上传到一个服务器地址,然后在那里转换成图片,返回一个图片的url地址,通常是网址,这个网址你是后台的本地环境还是测试、正式开发环境,后台的本地环境,则只能在你当前的网络与后台处于同一局域网的情况下才能访问,比如你们连接同一个wifi,而测试、正式环境则不需要在同一局域网也可以访问,这一点你需要注意。

还有一个就是这个base64Pic,就有是只要直接传就可以了,还有的需要你这样拼接一下:

"data:image/jpeg;base64,"+base64Pic

这与你的后台对应的图片转换地址的要求有关系。说道这个网络还有一个地方要配置一下: 首先在你的res下新建一个xml文件夹,在这个文件夹下新建一个network_security_config.xml,里面的代码如下:

然后在AndroidManifest.xml中配置: 在这里插入图片描述 这个配置就是为了防一手http请求,因为Android9.0及以后的版本默认是https请求,上面的配置就是允许http请求。

十一、源码

源码地址:ChangeAvatarDemo

总结

  文章就结束了,能帮到你那就最好了,山高水长,后会有期~



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3