Android 更换用户头像(拍照、相册选取)(1),目前最稳定和高效的UI适配方案

//热门强大的图片加载器

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,在里面增加一个样式:

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

<?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” />

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

<?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” />

这里我用了一个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 < 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;

}

}

工具类里面都有注释,我就不多说了。

七、打开相机、相册


声明变量

//存储拍完照后的图片

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

下图是我进阶学习所积累的历年腾讯、头条、阿里、美团、字节跳动等公司2019-2021年的高频面试题,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节

整理不易,望各位看官老爷点个关注转发,谢谢!祝大家都能得到自己心仪工作。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

七、打开相机、相册


声明变量

//存储拍完照后的图片

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-qHqtaIEB-1712356263735)]

[外链图片转存中…(img-jUBR5aaR-1712356263736)]

[外链图片转存中…(img-hhVL6lw7-1712356263736)]

[外链图片转存中…(img-V5BfH2nJ-1712356263737)]

[外链图片转存中…(img-OARvOCbG-1712356263737)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

下图是我进阶学习所积累的历年腾讯、头条、阿里、美团、字节跳动等公司2019-2021年的高频面试题,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节

[外链图片转存中…(img-JbkRh0lN-1712356263737)]

整理不易,望各位看官老爷点个关注转发,谢谢!祝大家都能得到自己心仪工作。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 9
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
为了在Android Studio中实现拍照和从相册选取图片的功能,您需要执行以下操作: 1.添加以下权限到AndroidManifest.xml文件中: <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-feature android:name="android.hardware.camera"/> 2.在主要活动(Main Activity)中添加以下代码来调用相机并拍照: private static final int CAMERA_REQUEST = 1888; private Button button; private ImageView imageView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = (Button) findViewById(R.id.button); imageView = (ImageView)findViewById(R.id.imageView); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent cameraIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE); startActivityForResult(cameraIntent, CAMERA_REQUEST); } }); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == CAMERA_REQUEST && resultCode == Activity.RESULT_OK) { Bitmap photo = (Bitmap) data.getExtras().get("data"); imageView.setImageBitmap(photo); } } 3.在主要活动中添加以下代码来调用相册选取图片: private static final int PICK_IMAGE_REQUEST = 1; private Button button1; private ImageView imageView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button1 = (Button) findViewById(R.id.button1); imageView = (ImageView)findViewById(R.id.imageView); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent galleryIntent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI); startActivityForResult(galleryIntent, PICK_IMAGE_REQUEST); } }); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == PICK_IMAGE_REQUEST && resultCode == RESULT_OK && null != data) { Uri selectedImage = data.getData(); String[] filePathColumn = { MediaStore.Images.Media.DATA }; Cursor cursor = getContentResolver().query(selectedImage,filePathColumn, null, null, null); cursor.moveToFirst(); int columnIndex = cursor.getColumnIndex(filePathColumn[0]); String picturePath = cursor.getString(columnIndex); cursor.close(); imageView.setImageBitmap(BitmapFactory.decodeFile(picturePath)); } } 确保您在AndroidManifest.xml文件中添加了必要的权限,并按照上述代码操作,就可以实现Android Studio中拍照和从相册选取图片的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值