Android 图片裁剪
运行效果图
前言
图片裁剪是对图片进行区域选定,然后裁剪选定的区域,形成一个图片,然后再对这个图片进行压缩,最终返回结果图片。
正文
从上面的描述来看貌似是挺简单的是吧,不过实际操作起来就没有那么简单了,下面先来看看简单的实现方式,就是Android自带的裁剪。
一、创建并配置项目
我们依然从创建项目开始讲起,这虽然有一些繁琐,但无疑可以让每一个Android开发者看懂。创建一个名为PictureCroppingDemo的项目。
创建好之后,在app的build.gradle添加如下代码,有两处
//JDK版本
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
// google权限管理框架
implementation 'pub.devrel:easypermissions:3.0.0'
//热门强大的图片加载器
implementation 'com.github.bumptech.glide:glide:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
添加位置如下图所示:
然后打开AndroidManifest.xml,在里面添加两个权限
<!--读写外部存储-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
这两个权限在Android6.0及以上版本属于危险权限,需要动态申请,下面来写权限申请的代码吧。
二、权限申请
首先在MainActivity中重写这个onRequestPermissionsResult方法。这个方法属于Android原生的权限请求返回,下面来看它的具体内容:
/**
* 权限请求结果
* @param requestCode 请求码
* @param permissions 请求权限
* @param grantResults 授权结果
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
// 将结果转发给 EasyPermissions
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
}
EasyPermissions就是刚才在build.gradle中添加的依赖库,然后写一个权限请求的方法。
@AfterPermissionGranted(9527)
private void requestPermission(){
String[] param = {Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE};
if(EasyPermissions.hasPermissions(this,param)){
//已有权限
showMsg("已获得权限");
}else {
//无权限 则进行权限请求
EasyPermissions.requestPermissions(this,"请求权限",9527,param);
}
}
这个requestPermission()方法上面有一个注解,这个注解是什么意思嗯呢,就是权限通过后再调用一次这个方法。然后看方法里面做了什么,定义了一个字符串数组,里面有两个权限,都是在AndroidManifest.xml中配置过的,实际上这两个权限在一个权限组里面,一个权限组只有有一个权限通过则表示整组权限通过,因此你只需要放置一个权限就好了,我这么写是为了让你更清楚一些。然后是一个判断,通过这框架去判断当前的权限是否以获取,是则进行后续操作,我这里是弹一个Toast,方法也很简单。
/**
* Toast提示
* @param msg 内容
*/
private void showMsg(String msg){
Toast.makeText(this,msg,Toast.LENGTH_SHORT).show();
}
如果没有权限则通过下面这行代码去请求权限
EasyPermissions.requestPermissions(this,"请求权限",9527,param);
这里的9527其实是一个请求码,它需要与注解中的对应,只有这样它在权限授予之后才会再次调用这个方法做检测。更规范的写法是定于一个全局变量,然后替换这个9527,比如这样
/**
* 外部存储权限请求码
*/
public static final int REQUEST_EXTERNAL_STORAGE_CODE = 9527;
然后修改对应的地方即可,如下图所示:
最终记得在onCreate中调用这个requestPermission()方法。下面运行一下:
三、获取图片Uri
在上面我们已经获取到了权限,下面就来获取这个图片的Uri,然后通过图片Uri显示这个图片。
首先修改布局activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
tools:context=".MainActivity">
<ImageView
android:id="@+id/iv_picture"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="24dp"
android:onClick="openAlbum"
android:text="打开相册" />
</RelativeLayout>
很简单的布局,这里唯一要说的就是这个onClick=“openAlbum”,如果你的按钮不需要进行设置的话,单个按钮的点击事件这样写更简洁一些,你会看到这个地方有一条红线,这需要到Activity中去写这个方法,你可以通过快捷键去生成这个方法。鼠标点击这个划红线的地方,然后Alt + Enter,下面会弹出一个窗口,第二项就是说在MainActivity中创建openAlbum方法。这种方式在Fragment中并不是适用,请注意。
然后你就会在MainActivity中看到这样的方法,请注意一点,这个方法名与你onClick中的值必须要一致。
/**
* 打开相册
*/
public void openAlbum(View view) {
}
下面来写打开相册的方法。这里同样的需要一个请求码,去打开相册,然后通过返回的结果去读取图片的uri,定义一个请求码
/**
* 打开相册请求码
*/
private static final int OPEN_ALBUM_CODE = 100;
然后在修改openAlbum方法,代码如下:
/**
* 打开相册
*/
public void openAlbum(View view) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_PICK);
intent.setType("image/*");
startActivityForResult(intent, OPEN_ALBUM_CODE);
}
注意这里使用了startActivityForResult,则需要获取返回值。重写onActivityResult方法。
/**
* 返回Activity结果
*
* @param requestCode 请求码
* @param resultCode 结果码
* @param data 数据
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
}
这里先获取相册中的图片显示到Activity中,刚才在activity_main.xml中的ImageView控件就派上用场了。
//图片
private ImageView ivPicture;
然后在onCreate中绑定xml的id。下面你再使用这个ivPicture就不会报空对象了。
ivPicture = findViewById(R.id.iv_picture);
然后回到onActivityResult方法,修改代码如下:
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == OPEN_ALBUM_CODE && resultCode == RESULT_OK) {
final Uri imageUri = Objects.requireNonNull(data).getData();
//显示图片
Glide.with(this).load(imageUri).into(ivPicture);
}
}
这里加了一个判断用于检测是否为打开相册之后的返回与返回是否成功。RESULT_OK是Activity中自带的。
然后在获取数据时判空处理一下再赋值给一个Uri变量,然后通过Glide框架加载这个Url显示在刚才的ivPicture上。代码写好了,下面运行一下:
嗯,图片显示出来了,图片的url也拿到了,下面该做这个图片的剪裁了。
四、图片裁剪
既然是调用Android系统的图片裁剪,那么自然也和打开系统相册差不多,依然是先创建一个请求码:
/**
* 图片剪裁请求码
*/
public static final int PICTURE_CROPPING_CODE = 200;
然后写一个裁剪的方法。
/**
* 图片剪裁
*
* @param uri 图片uri
*/
private void pictureCropping(Uri uri) {
// 调用系统中自带的图片剪裁
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(uri, "image/*");
// 下面这个crop=true是设置在开启的Intent中设置显示的VIEW可裁剪
intent.putExtra("crop", "true");
// aspectX aspectY 是宽高的比例
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
// outputX outputY 是裁剪图片宽高
intent.putExtra("outputX", 150);
intent.putExtra("outputY", 150);
// 返回裁剪后的数据
intent.putExtra("return-data", true);
startActivityForResult(intent, PICTURE_CROPPING_CODE);
}
图片裁剪需要用到uri,再上面打开相册返回时就已经拿到了uri,那么下面修改onActivityResult方法。
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == OPEN_ALBUM_CODE && resultCode == RESULT_OK) {
//打开相册返回
final Uri imageUri = Objects.requireNonNull(data).getData();
//图片剪裁
pictureCropping(imageUri);
} else if (requestCode == PICTURE_CROPPING_CODE && resultCode == RESULT_OK) {
//图片剪裁返回
Bundle bundle = data.getExtras();
if (bundle != null) {
//在这里获得了剪裁后的Bitmap对象,可以用于上传
Bitmap image = bundle.getParcelable("data");
//设置到ImageView上
ivPicture.setImageBitmap(image);
}
}
}
在打开相册返回之后调用pictureCropping方法,传入图片url,然后会启动系统剪裁,剪裁后通过返回数据数据设置到ImageVIew控件上。注意剪裁后就不再是uri了,而是Bitmap。运行一下:
可以看到系统的剪裁并不是很彻底,gif中虽然演示的剪裁时是一个圆形,但实际上剪裁的是一个正方形的,这其实和Android系统版本及设置的参数有关系。我在荣耀8和荣耀20i上运行都是这样的,对应的版本是8.0和10.0,效果基本一致。那么下面修改一下参数试试看,如下图我修改了宽高比例和剪裁后的宽高。
再运行一下:
可以看到通过该参数真的就不一样了不是吗?
但是有一些朋友想要圆形的剪裁,那么这里有一个问题你要弄清楚,你要真的还是假的,真的圆形,那么肯定是需要剪裁后重新生成的,而假的圆形就很好办了,首先我们改回刚才的参数,那么在我的是手机上就还是这样的圆形剪裁框,而我只要让他显示出来是一个圆形,你就会以为你是剪裁成功了,当然这都是忽悠用户的好办法,下面来实践一下。这个可以通过外力来解决,圆形图片很多方式能做到,比如第三方框架、自定义View等。
还记得刚才用过的Glide吗?创建requestOptions对象
/**
* Glide请求图片选项配置
*/
private RequestOptions requestOptions = RequestOptions
.circleCropTransform()//圆形剪裁
.diskCacheStrategy(DiskCacheStrategy.NONE)//不做磁盘缓存
.skipMemoryCache(true);//不做内存缓存
然后在剪裁图片的返回中设置图片
Glide.with(this).load(image).apply(requestOptions).into(ivPicture);
运行一下:
尾声
OK,就到这里了。我是初学者-Study,山高水长,后会有期。
此项目并不一定适配所有机型和Android版本,要根据实际情况就改动才行。