Android 更换用户头像(拍照、相册选取) |
您所在的位置:网站首页 › 雅典娜换头头像 › Android 更换用户头像(拍照、相册选取) |
Android 更换头像
前言正文一、新建项目二、配置项目三、布局、样式改动四、权限请求五、底部弹窗显示六、工具类七、打开相机、相册八、页面返回显示图片九、本地缓存十、后台获取十一、源码
总结
运行效果图: 做Android应用开发,通常是有很多的功能组成,今天就来看一下这个用户头像更换的功能该怎么去写。相信很多的小伙伴都写过这个功能,因为作为一个APP来说这是很普遍的功能,基本都会有。只要你的APP有用户模块,就会有用户的个人信息的修改,比如常规的手机号码修改、地址修改、头像修改、昵称修改等。这里面技术含量高一点的就是头像修改了,进入正题吧。 正文这里我还是新建一个项目来做这个头像修改的功能,这样对于没有接触过这个功能的朋友更友好,这也是我一直以来的写作风格,不要嫌我啰嗦啊。 一、新建项目创建一个名为ChangeAvatarDemo的项目 基于这些考虑,首先打开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 }添加位置如下图所示: 然后打开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;然后赋值 如果我没有猜错的话,你的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类型的,于是你可以这么写。
可以看到,当我杀死程序之后再进入时,它显示的是我之前从相册中选取的图片。这就证明本地缓存成功了,并且可以使用。 十、后台获取这个由于我无法实际操作,因此我就说一下方式。 实际中大部分的图片都是不会放到缓存里面的,因为会很占空间,第二个是缓存是少量的存储。 首先拿到拍照或者打开相册后的图片路径之后,这个地址当然不是直接发送给后台的,根据我的经验,它们通常需要的是图片的base64,如下图所示: 还有一个就是这个base64Pic,就有是只要直接传就可以了,还有的需要你这样拼接一下: "data:image/jpeg;base64,"+base64Pic这与你的后台对应的图片转换地址的要求有关系。说道这个网络还有一个地方要配置一下: 首先在你的res下新建一个xml文件夹,在这个文件夹下新建一个network_security_config.xml,里面的代码如下: 然后在AndroidManifest.xml中配置: 源码地址:ChangeAvatarDemo 总结文章就结束了,能帮到你那就最好了,山高水长,后会有期~ |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |