声明: 学习这个之前, 建议先学习下内容提供器.
使用的是Android系统自带的裁剪功能.
本人真机(华为荣耀9 Android8.0)测试, 先上效果图(拍照和从相册选, 一样的效果):
.1. build.grandle 代码如下:
引用了circleimageview和butterknife.
使用butterknife的话, 需要添加javaCompileOptions选项, 如下所示:
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
defaultConfig {
applicationId "cwx.camerastudy"
minSdkVersion 19
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
includeCompileClasspath = true
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'de.hdodenhof:circleimageview:3.0.0'
implementation 'com.jakewharton:butterknife:7.0.1'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
2. AndroidManifest.xml代码如下:
注册了两个Activity和一个内容提供器.
内容提供器中, "android: authorities", 自己定义. 我定义的是"包名."+ fileprovider. 这个属性在MainActivity中会用到.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cwx.camerastudy">
<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" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".Main2Activity">
</activity>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="cwx.camerastudy.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>
3. 在res目录下新建Directory, 命名xml. 新建XML Resource File, 命名file_paths.内容代码如下:
path值为空, 表示可以访问整个sdcard目录.
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="my_images"
path="" />
</paths>
4. 辅助类HelpUtils, 代码如下:
public class HelpUtils {
public static final int TAKE_PHOTO= 1;
public static final int CHOSE_PHOTO= 2;
public static final int CLIP_PHOTO= 3;
/**
* 返回文件的在本地的真实路径
* @param data
* @return
*/
public static String handleImageonKitKat(Activity activity, Intent data) {
String imagePath= null;
Uri myuri= data.getData();
if (DocumentsContract.isDocumentUri(activity, myuri)) {
String docId= DocumentsContract.getDocumentId(myuri);
if ("com.android.providers.media.documents".equals(myuri.getAuthority())) {
String id= docId.split(":")[1];
String selection= MediaStore.Images.Media._ID+ "="+ id;
imagePath= getImagePath(activity, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection);
} else if ("com.android.providers.downloads.documents".equals(myuri.getAuthority())) {
Uri contentUri= ContentUris.withAppendedId(Uri.parse("content://downloads/public_download"), Long.valueOf(docId));
imagePath= getImagePath(activity, contentUri, null);
}
} else if ("content".equalsIgnoreCase(myuri.getScheme())) {
imagePath= getImagePath(activity, myuri, null);
} else if ("file".equalsIgnoreCase(myuri.getScheme())) {
imagePath= myuri.getPath();
}
return imagePath;
}
/**
* 返回文件的在本地的真实路径
* @param data
* @return
*/
public static String handleImageBeforeKitKat(Activity activity, Intent data) {
Uri uri= data.getData();
String imagePath= getImagePath(activity, uri, null);
return imagePath;
}
public static String getImagePath(Activity activity, Uri uri, String selection) {
String path= null;
Cursor cursor= activity.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;
}
/**
* 打开相册
*/
public static void openAlbum(Activity activity) {
Intent intent= new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("image/*");
if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.KITKAT) {
activity.startActivityForResult(intent, CHOSE_PHOTO);
} else {
activity.startActivityForResult(intent, CHOSE_PHOTO);
}
}
/**
* 打开摄像头
*/
public static void openCamera(Activity activity, Uri uri) {
Intent intent= new Intent("android.media.action.IMAGE_CAPTURE");
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
activity.startActivityForResult(intent, TAKE_PHOTO);
}
/**
* 剪裁绘制
* @param uri
*/
public static void photoClip(Activity activity, Uri uri) {
// 调用系统中自带的图片剪裁
Intent intent = new Intent("com.android.camera.action.CROP");
if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.N) {
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
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);
activity.startActivityForResult(intent, CLIP_PHOTO);
}
/**
* 保存图片
*/
public static String saveImage(Activity activity, String name, Bitmap bitmap) {
File appDir= new File(String.valueOf(activity.getExternalCacheDir()));
if (!appDir.exists()) {
appDir.mkdir();
}
String fileName= name+ ".jpg";
File file= new File(appDir, fileName);
try {
FileOutputStream fos= new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.flush();
fos.close();
return file.getAbsolutePath();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
5. MainActivity代码如下:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Uri uri;
@Bind(R.id.take_photo)
Button btn_take;
@Bind(R.id.chose_photo)
Button btn_chose;
@Bind(R.id.picture)
CircleImageView picture;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
btn_take.setOnClickListener(this);
btn_chose.setOnClickListener(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
ButterKnife.unbind(this);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
switch (requestCode) {
case HelpUtils.TAKE_PHOTO:
if (resultCode== RESULT_OK) {
try {
//因为uri是在缓存数据目录 不需要运行时权限处理
HelpUtils.photoClip(this, uri);
} catch (Exception e) {
e.printStackTrace();
}
}
break;
case HelpUtils.CHOSE_PHOTO:
String path;
if (resultCode== RESULT_OK) {
Uri newUri= null;
//获取真实路径
if (Build.VERSION.SDK_INT>= 19) {
path= HelpUtils.handleImageonKitKat(this, data);
} else {
path= HelpUtils.handleImageBeforeKitKat(this, data);
}
//申城新的uri实例
if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.N) {
newUri = FileProvider.getUriForFile(this, "cwx.camerastudy.fileprovider", new File(path));
} else {
newUri= Uri.fromFile(new File(path));
}
//剪裁绘制
HelpUtils.photoClip(this, newUri);
}
break;
case HelpUtils.CLIP_PHOTO:
Bundle bundle= data.getExtras();
if (bundle!= null) {
Bitmap bitmap= bundle.getParcelable("data");
picture.setImageBitmap(bitmap);
//为操作简单 保存图片到当前app缓存目录下
String strPath= HelpUtils.saveImage(this,"temp", bitmap);
Log.d("剪裁后图片路径: ", strPath);
}
break;
default:
break;
}
}
/**
* 权限请求回调
* @param requestCode
* @param permissions
* @param grantResults
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length> 0 && grantResults[0]== PackageManager.PERMISSION_GRANTED) {
HelpUtils.openCamera(this, uri);
} else {
Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();
}
break;
case 2:
if (grantResults.length> 0 && grantResults[0]== PackageManager.PERMISSION_GRANTED) {
HelpUtils.openAlbum(this);
} else {
Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();
}
break;
default:
break;
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.take_photo:
//使用getExternalCacheDir() 获取当前应用缓存数据的位置
File outoutImage= new File(getExternalCacheDir(), "output_image.jpg");
try {
if (outoutImage.exists()) {
outoutImage.delete();
}
outoutImage.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
//从Android 7.0开始 直接使用本地真实路径的Uri被认为不安全 此处作特殊处理
if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.N) {
uri= FileProvider.getUriForFile(MainActivity.this, "cwx.camerastudy.fileprovider", outoutImage);
} else {
uri= Uri.fromFile(outoutImage);
}
//验证摄像头权限 并打开摄像头
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA)!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[] {Manifest.permission.CAMERA}, 1);
} else {
HelpUtils.openCamera(this, uri);
}
break;
case R.id.chose_photo:
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, 2);
} else {
HelpUtils.openAlbum(this);
}
break;
default:
break;
}
}
}
6. 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">
<Button android:id="@+id/take_photo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_margin="10dp"
android:text="Take Photo" />
<Button android:id="@+id/chose_photo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_margin="10dp"
android:text="Chose Photo" />
<Button android:id="@+id/chose_"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_margin="10dp"
android:text="Test" />
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/picture"
android:layout_width="96dp"
android:layout_height="96dp"
android:layout_centerInParent="true"
android:src="@drawable/ic_launcher_background"
app:civ_border_color="#999999"
app:civ_border_width="2dp" />
</RelativeLayout>
代码总结完毕, 如果有不足或错误的地方非常欢迎指出来. 我也在学习中.
其实在写这个功能之前, 本人在"第一行代码"书里, 看到也有调用摄像头拍照并显示, 调用相册图片并显示的例子,本人照着书敲出来的, 希望对大家有所帮助:
在之前的基础上添加:
1. Main2Activity代码如下
public class Main2Activity extends AppCompatActivity {
public static final int TAKE_PHOTO= 1;
public static final int CHOSE_PHOTO= 2;
private ImageView picture;
private Uri uri;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
Button btn_take= (Button) findViewById(R.id.take_photo);
Button btn_chose= (Button) findViewById(R.id.chose_photo);
picture = (ImageView) findViewById(R.id.picture);
btn_take.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
File outoutImage= new File(getExternalCacheDir(), "output_image.jpd");
try {
if (outoutImage.exists()) {
outoutImage.delete();
}
outoutImage.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
if (Build.VERSION.SDK_INT>= 24) {
uri= FileProvider.getUriForFile(Main2Activity.this, "cwx.camerastudy.fileprovider", outoutImage);
} else {
uri= Uri.fromFile(outoutImage);
}
Intent intent= new Intent("android.media.action.IMAGE_CAPTURE");
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
startActivityForResult(intent, TAKE_PHOTO);
}
});
btn_chose.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (ContextCompat.checkSelfPermission(Main2Activity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(Main2Activity.this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
} else {
openAlbum();
}
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
switch (requestCode) {
case TAKE_PHOTO:
if (resultCode== RESULT_OK) {
try {
Bitmap bitmap= BitmapFactory.decodeStream(getContentResolver().openInputStream(uri));
picture.setImageBitmap(bitmap);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
break;
case CHOSE_PHOTO:
if (resultCode== RESULT_OK) {
if (Build.VERSION.SDK_INT>= 19) {
handleImageonKitKat(data);
} else {
handleImageBeforeKitKat(data);
}
}
break;
default:
break;
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length> 0 && grantResults[0]== PackageManager.PERMISSION_GRANTED) {
openAlbum();
} else {
Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();
}
break;
default:
break;
}
}
private void openAlbum() {
Intent intent= new Intent("android.intent.action.GET_CONTENT");
intent.setType("image/*");
startActivityForResult(intent, CHOSE_PHOTO);
}
private void handleImageonKitKat(Intent data) {
String imagePath= null;
Uri myuri= data.getData();
if (DocumentsContract.isDocumentUri(this, myuri)) {
String docId= DocumentsContract.getDocumentId(myuri);
if ("com.android.providers.media.documents".equals(myuri.getAuthority())) {
String id= docId.split(":")[1];
String selection= MediaStore.Images.Media._ID+ "="+ id;
imagePath= getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection);
} else if ("com.android.providers.downloads.documents".equals(myuri.getAuthority())) {
Uri contentUri= ContentUris.withAppendedId(Uri.parse("content://downloads/public_download"), Long.valueOf(docId));
imagePath= getImagePath(contentUri, null);
}
} else if ("content".equalsIgnoreCase(uri.getScheme())) {
imagePath= getImagePath(myuri, null);
} else if ("file".equalsIgnoreCase(myuri.getScheme())) {
imagePath= myuri.getPath();
}
displayImage(imagePath);
}
private void handleImageBeforeKitKat(Intent data) {
Uri uri= data.getData();
String imagePath= getImagePath(uri, null);
displayImage(imagePath);
}
private String getImagePath(Uri uri, String selection) {
String path= null;
Cursor cursor= 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;
}
private void displayImage(String imagePath) {
if (imagePath!= null) {
Bitmap bitmap= BitmapFactory.decodeFile(imagePath);
picture.setImageBitmap(bitmap);
/**
* 质量压缩
* 质量压缩不会减少图片的像素,它是在保持像素的前提下改变图片的位深及透明度,来达到压缩图片的目的,图片的长,宽,像素都不会改变,
* 那么bitmap所占内存大小是不会变的。 quality,可以调节你压缩的比例
* 质量压缩对png格式图片无效, png是无损压缩的.
*/
// ByteArrayOutputStream baos= new ByteArrayOutputStream();
// int quality= 2;
// Bitmap bitmap= BitmapFactory.decodeFile(imagePath);
// bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos);
// byte[] bytes= baos.toByteArray();
// Bitmap bm= BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
// picture.setImageBitmap(bm);
/**
* 采样率压缩
* 采样率压缩其原理是缩放bitamp的尺寸,通过调节其inSampleSize参数
* 比如调节为2,宽高会为原来的1/2,内存变回原来的1/4
*/
// BitmapFactory.Options options= new BitmapFactory.Options();
// options.inSampleSize= 2;
// Bitmap bitmap= BitmapFactory.decodeFile(imagePath, options);
// picture.setImageBitmap(bitmap);
/**
* 缩放法压缩
* 放缩法压缩使用的是通过矩阵对图片进行裁剪,也是通过缩放图片尺寸,来达到压缩图片的效果,和采样率的原理一样
*/
// Matrix matrix= new Matrix();
// matrix.setScale(0.5f, 0.5f);
// Bitmap bitmap= BitmapFactory.decodeFile(imagePath);
// Bitmap bm= Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
// picture.setImageBitmap(bm);
/**
* RGB_565压缩
* RGB_565压缩是通过改用内存占用更小的编码格式来达到压缩的效果
* Android默认的颜色模式为ARGB_8888,这个颜色模式色彩最细腻,显示质量最高
* 如果对透明度没有要求,建议可以改成RGB_565,相比ARGB_8888将节省一半的内存开销
*/
// BitmapFactory.Options options= new BitmapFactory.Options();
// options.inPreferredConfig= Bitmap.Config.RGB_565;
// Bitmap bitmap1= BitmapFactory.decodeFile(imagePath, options);
// picture.setImageBitmap(bitmap);
/**
* createScaledbitmap
* 将图片压缩成用户所期望的长度和宽度
* 如果用户期望的长度和宽度和原图长度宽度相差太多的话,图片会很不清晰。
*/
// Bitmap bitmap= BitmapFactory.decodeFile(imagePath);
// Bitmap bm= Bitmap.createScaledBitmap(bitmap, 150, 150, true);
// picture.setImageBitmap(bm);
} else {
Toast.makeText(this, "Failed to get image", Toast.LENGTH_SHORT).show();
}
}
}
2. activity_main2.xml代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button android:id="@+id/take_photo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Take Photo" />
<Button android:id="@+id/chose_photo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Chose Photo" />
<ImageView android:id="@+id/picture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"/>
</LinearLayout>
结束!