@TOC
</font>
<hr style=" border:solid; width:100px; height:1px;" color=#000000 size=1">
运行效果图:
前言
做Android应用开发,通常是有很多的功能组成,今天就来看一下这个用户头像更换的功能该怎么去写。相信很多的小伙伴都写过这个功能,因为作为一个APP来说这是很普遍的功能,基本都会有。只要你的APP有用户模块,就会有用户的个人信息的修改,比如常规的手机号码修改、地址修改、头像修改、昵称修改等。这里面技术含量高一点的就是头像修改了,进入正题吧。
<hr style=" border:solid; width:100px; height:1px;" color=#000000 size=1">
正文
这里我还是新建一个项目来做这个头像修改的功能,这样对于没有接触过这个功能的朋友更友好,这也是我一直以来的写作风格,不要嫌我啰嗦啊。
一、新建项目
创建一个名为ChangeAvatarDemo的项目
项目创建好之后,先想清楚你的这个功能需要什么,换头像常规肯定是上传到后台去,那么你肯定是要有网络权限的,其次如果你的网络请求地址是http开头的话,而在Android9.0及以上版本则要配置http访问许可才行,之后你是否会用到一些第三方框架,比如圆形头像,圆角头像、图片加载、动态权限请求。
二、配置项目
基于这些考虑,首先打开app模块下的build.gradle,在dependencies闭包下添加如下依赖:
代码语言:txt复制 //权限请求框架
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
代码语言:txt复制 compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
添加位置如下图所示:
然后点击右上角Sync进行同步,到这里gradle就配置完成了。
然后打开AndroidManifest.xml,在里面配置如下权限:
代码语言:txt复制 <!--网络权限-->
<uses-permission android:name="android.permission.INTERNET"/>
<!--相机权限-->
<uses-permission android:name="android.permission.CAMERA"/>
<!-- 读写文件权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
添加位置如下图所示:
这里还有一个要适配,那就是在Android10.0时增加了作用域存储,因此我这个不用这个作用域存储,所以在你的application标签下增加这样一句话
代码语言:txt复制 android:requestLegacyExternalStorage="true"
如下图所示
三、布局、样式改动
首先打开项目中的styles.xml,在里面增加一个样式:
代码语言:txt复制 <!-- 圆形图片 -->
<style name="circleImageStyle">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">50%</item>
</style>
然后在layout包下新建一个dialog_bottom.xml,里面的代码如下:
代码语言:txt复制<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FFF"
android:orientation="vertical">
<TextView
android:id="@ id/tv_take_pictures"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:foreground="?android:attr/selectableItemBackground"
android:gravity="center"
android:padding="16dp"
android:text="拍照"
android:textColor="#000" />
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="#EEEEEE" />
<TextView
android:id="@ id/tv_open_album"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:foreground="?android:attr/selectableItemBackground"
android:gravity="center"
android:padding="16dp"
android:text="打开相册"
android:textColor="#000" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#EEEEEE" />
<TextView
android:id="@ id/tv_cancel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FFF"
android:foreground="?android:attr/selectableItemBackground"
android:gravity="center"
android:padding="16dp"
android:text="取消"
android:textColor="#000" />
</LinearLayout>
这是一个弹窗的布局文件,里面提供你选择拍照、打开相册、取消。而且从命名来看,这是一个底部弹窗。所以需要一个地方去触发这个弹窗从屏幕底部出现。下面打开activity_main.xml,修改代码后如下所示:
代码语言:txt复制<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".MainActivity">
<!--圆形图片-->
<com.google.android.material.imageview.ShapeableImageView
android:id="@ id/iv_head"
android:layout_width="200dp"
android:layout_height="200dp"
android:onClick="changeAvatar"
android:src="@mipmap/ic_launcher"
app:shapeAppearanceOverlay="@style/circleImageStyle" />
</LinearLayout>
这里我用了一个ShapeableImageView,这是material库里面的一个控件,你只要知道它比普通的ImageView要??就可以了,想要详细了解的看看Android Material UI控件之ShapeableImageView。
这里我指定了app:shapeAppearanceOverlay="@style/circleImageStyle",也就是说它变成了一个圆形图片控件。
布局就写完了。
四、权限请求
进入到MainActivity,先声明变量
代码语言:txt复制 //权限请求
private RxPermissions rxPermissions;
先写一个Toast提示方法。
代码语言:txt复制 /**
* Toast提示
*
* @param msg
*/
private void showMsg(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
然后可以写一个checkVersion()方法,用于检查当前的Android版本,并且给你提示。
代码语言:txt复制 /**
* 检查版本
*/
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()。使用户一进入这个页面就进行检查版本和授权。
不过这里还要防范一个问题,那就是假如用户没有通过权限。再创建一个变量
代码语言:txt复制 //是否拥有权限
private boolean hasPermissions = false;
然后赋值
只有权限全部通过授权之后才会是true。
五、底部弹窗显示
如果我没有猜错的话,你的activity_main.xml中还有一个地方报错。
不过不要担心,先增加两个变量
代码语言:txt复制 //底部弹窗
private BottomSheetDialog bottomSheetDialog;
//弹窗视图
private View bottomView;
然后新增一个changeAvatar()方法,里面的代码如下:
代码语言:txt复制 /**
* 更换头像
*
* @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类,里面的代码如下:
代码语言:txt复制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类,代码如下;
代码语言:txt复制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 < Build.VERSION_CODES.N) {
// 从文件中创建uri
Uri uri = Uri.fromFile(outputImagePath);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
} else {
//兼容android7.0 使用共享文件的形式
ContentValues contentValues = new ContentValues(1);
contentValues.put(MediaStore.Images.Media.DATA, outputImagePath.getAbsolutePath());
Uri uri = context.getApplicationContext().getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
}
}
return intent;
}
/**
* 相册Intent
* @return
*/
public static Intent getSelectPhotoIntent() {
Intent intent = new Intent("android.intent.action.GET_CONTENT");
intent.setType("image/*");
return intent;
}
/**
* 判断sdcard是否被挂载
*/
public static boolean hasSdcard() {
return Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED);
}
/**
* 4.4及以上系统处理图片的方法
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
public static String getImageOnKitKatPath(Intent data, Context context) {
String imagePath = null;
Uri uri = data.getData();
Log.d("uri=intent.getData :", "" uri);
if (DocumentsContract.isDocumentUri(context, uri)) {
//数据表里指定的行
String docId = DocumentsContract.getDocumentId(uri);
Log.d("getDocumentId(uri) :", "" docId);
Log.d("uri.getAuthority() :", "" uri.getAuthority());
if ("com.android.providers.media.documents".equals(uri.getAuthority())) {
String id = docId.split(":")[1];
String selection = MediaStore.Images.Media._ID "=" id;
imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection, context);
} else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())) {
Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(docId));
imagePath = getImagePath(contentUri, null, context);
}
} else if ("content".equalsIgnoreCase(uri.getScheme())) {
imagePath = getImagePath(uri, null, context);
}
return imagePath;
}
/**
* 通过uri和selection来获取真实的图片路径,从相册获取图片时要用
*/
public static String getImagePath(Uri uri, String selection, Context context) {
String path = null;
Cursor cursor = context.getContentResolver().query(uri, null, selection, null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
}
cursor.close();
}
return path;
}
/**
* 更改图片显示角度
* @param filepath
* @param orc_bitmap
* @param iv
*/
public static void ImgUpdateDirection(String filepath, Bitmap orc_bitmap, ImageView iv) {
//图片旋转的角度
int digree = 0;
//根据图片的filepath获取到一个ExifInterface的对象
ExifInterface exif = null;
try {
exif = new ExifInterface(filepath);
if (exif != null) {
// 读取图片中相机方向信息
int ori = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
// 计算旋转角度
switch (ori) {
case ExifInterface.ORIENTATION_ROTATE_90:
digree = 90;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
digree = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
digree = 270;
break;
default:
digree = 0;
break;
}
}
//如果图片不为0
if (digree != 0) {
// 旋转图片
Matrix m = new Matrix();
m.postRotate(digree);
orc_bitmap = Bitmap.createBitmap(orc_bitmap, 0, 0, orc_bitmap.getWidth(),
orc_bitmap.getHeight(), m, true);
}
if (orc_bitmap != null) {
iv.setImageBitmap(orc_bitmap);
}
} catch (IOException e) {
e.printStackTrace();
exif = null;
}
}
/**
* 4.4以下系统处理图片的方法
*/
public static String getImageBeforeKitKatPath(Intent data, Context context) {
Uri uri = data.getData();
String imagePath = getImagePath(uri, null, context);
return imagePath;
}
/**
* 比例压缩
* @param image
* @return
*/
public static Bitmap compression(Bitmap image) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
//判断如果图片大于1M,进行压缩避免在生成图片(BitmapFactory.decodeStream)时溢出
if (outputStream.toByteArray().length / 1024 > 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 < outHeight && outHeight > height) {//如果高度高的话根据宽度固定大小缩放
zoomRatio = (int) (options.outHeight / height);
}
if (zoomRatio <= 0) {
zoomRatio = 1;
}
options.inSampleSize = zoomRatio;//设置缩放比例
options.inPreferredConfig = Bitmap.Config.RGB_565;//降低图片从ARGB888到RGB565
//重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了
inputStream = new ByteArrayInputStream(outputStream.toByteArray());
//压缩好比例大小后再进行质量压缩
bitmap = BitmapFactory.decodeStream(inputStream, null, options);
return bitmap;
}
}
工具类里面都有注释,我就不多说了。
七、打开相机、相册
声明变量
代码语言:txt复制 //存储拍完照后的图片
private File outputImagePath;
//启动相机标识
public static final int TAKE_PHOTO = 1;
//启动相册标识
public static final int SELECT_PHOTO = 2;
拍照方法:
代码语言:txt复制 /**
* 拍照
*/
private void takePhoto() {
if (!hasPermissions) {
showMsg("未获取到权限");
checkVersion();
return;
}
SimpleDateFormat timeStampFormat = new SimpleDateFormat(
"yyyy_MM_dd_HH_mm_ss");
String filename = timeStampFormat.format(new Date());
outputImagePath = new File(getExternalCacheDir(),
filename ".jpg");
Intent takePhotoIntent = CameraUtils.getTakePhotoIntent(this, outputImagePath);
// 开启一个带有返回值的Activity,请求码为TAKE_PHOTO
startActivityForResult(takePhotoIntent, TAKE_PHOTO);
}
打开相册方法:
代码语言:txt复制 /**
* 打开相册
*/
private void openAlbum() {
if (!hasPermissions) {
showMsg("未获取到权限");
checkVersion();
return;
}
startActivityForResult(CameraUtils.getSelectPhotoIntent(), SELECT_PHOTO);
}
方法写完了记得要调用才行,如下图在changeAvatar中调用。
现在你运行,你就会发现会跳转到相机和打开相册。但是你还是要回来的。
八、页面返回显示图片
先声明如下变量
代码语言:txt复制 //图片控件
private ShapeableImageView ivHead;
//Base64
private String base64Pic;
//拍照和相册获取图片的Bitmap
private Bitmap orc_bitmap;
//Glide请求图片选项配置
private RequestOptions requestOptions = RequestOptions.circleCropTransform()
.diskCacheStrategy(DiskCacheStrategy.NONE)//不做磁盘缓存
.skipMemoryCache(true);//不做内存缓存
然后别忘了绑定图片控件
下面重写onActivityResult方法,获取返回的路径。
代码语言:txt复制 /**
* 返回到Activity
* @param requestCode
* @param resultCode
* @param data
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
//拍照后返回
case TAKE_PHOTO:
if (resultCode == RESULT_OK) {
//显示图片
displayImage(outputImagePath.getAbsolutePath());
}
break;
//打开相册后返回
case SELECT_PHOTO:
if (resultCode == RESULT_OK) {
String imagePath = null;
//判断手机系统版本号
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
//4.4及以上系统使用这个方法处理图片
imagePath = CameraUtils.getImageOnKitKatPath(data, this);
} else {
imagePath = CameraUtils.getImageBeforeKitKatPath(data, this);
}
//显示图片
displayImage(imagePath);
}
break;
default:
break;
}
}
这里调用了displayImage方法,代码如下:
代码语言:txt复制 /**
* 通过图片路径显示图片
*/
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类,代码如下:
代码语言:txt复制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方法中。
代码语言:txt复制 //取出缓存
String imageUrl = SPUtils.getString("imageUrl",null,this);
if(imageUrl != null){
Glide.with(this).load(imageUrl).apply(requestOptions).into(ivHead);
}
这样就实现了本地图片缓存了,运行效果如下图
可以看到,当我杀死程序之后再进入时,它显示的是我之前从相册中选取的图片。这就证明本地缓存成功了,并且可以使用。
十、后台获取
这个由于我无法实际操作,因此我就说一下方式。
实际中大部分的图片都是不会放到缓存里面的,因为会很占空间,第二个是缓存是少量的存储。
这里的base64Pic是String类型的,它的数据会比较长,如果你的后台要求使用这种方式的话,那么你记得让他把这个字段的上限放到最大,否则会存储不完成,造成丢失。后台拿到这个base64Pic之后,会上传到一个服务器地址,然后在那里转换成图片,返回一个图片的url地址,通常是网址,这个网址你是后台的本地环境还是测试、正式开发环境,后台的本地环境,则只能在你当前的网络与后台处于同一局域网的情况下才能访问,比如你们连接同一个wifi,而测试、正式环境则不需要在同一局域网也可以访问,这一点你需要注意。
还有一个就是这个base64Pic,就有是只要直接传就可以了,还有的需要你这样拼接一下:
代码语言:txt复制"data:image/jpeg;base64," base64Pic
这与你的后台对应的图片转换地址的要求有关系。说道这个网络还有一个地方要配置一下:
首先在你的res下新建一个xml文件夹,在这个文件夹下新建一个network_security_config.xml,里面的代码如下:
代码语言:txt复制<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
然后在AndroidManifest.xml中配置:
这个配置就是为了防一手http请求,因为Android9.0及以后的版本默认是https请求,上面的配置就是允许http请求。
十一、源码
源码地址:ChangeAvatarDemo
<hr style=" border:solid; width:100px; height:1px;" color=#000000 size=1">
总结
文章就结束了,能帮到你那就最好了,山高水长,后会有期~