虽然调用摄像头拍照既方便又快捷,但我们并不是每次都需要去当场拍一张照片的。因为每个人的手机相册里应该都会存有许许多多张照片,直接从相册里选取一张现有的照片会比打开相机拍一张照片更加常用。一个优秀的应用程序应该将这两种选择方式都提供给用户,由用户来决定使用哪一种。下面我们就来看一下,如何才能实现从相册中选择照片的功能。
还是在CameraAlbumTest项目的基础上进行修改,编辑activity_main.xml文件,在布局中添加一个按钮用于从相册中选择照片,代码如下所示:
<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" /> <ImageView android:id="@+id/picture" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" /> <Button android:id="@+id/choose_from_album" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Choose from Album" /> </LinearLayout>
package com.example.cameraalbumtest; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import androidx.core.content.FileProvider; import android.Manifest; import android.annotation.TargetApi; import android.content.ContentUris; import android.content.Intent; import android.content.pm.PackageManager; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.provider.DocumentsContract; import android.provider.MediaStore; import android.view.View; import android.widget.Button; import android.widget.ImageView; import android.widget.Toast; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; public class MainActivity extends AppCompatActivity { public static final int TAKE_PHOTO=1;//拍照 public static final int CHOOSE_PHOTO=2;//从相册取照片 private ImageView picture; private Uri imageUri; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //获取实例 Button takePhoto=(Button) findViewById(R.id.take_photo); picture =(ImageView) findViewById(R.id.picture); takePhoto.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //创建file对象,用于存储拍照后的图片 File outputImage=new File(getExternalCacheDir(),"output_image.jpg");//把图片进行命名 //调用getExternalCacheDir()可以得到手机SD卡的应用关联缓存目录 //所谓的应用关联缓存目录,就是指SD卡中专门用于存放当前应用缓存数据的位置 //具体的路径是/sdcard/Android/data/<package name>/cache //因为从Android 6.0系统开始,读写SD卡被列为了危险权限, // 如果将图片存放在SD卡的任何其他目录,都要进行运行时权限处理才行,而使用应用关联目录则可以跳过这一步。 try { if (outputImage.exists()) {//如果已经存在了图片,则删掉, outputImage.delete(); } outputImage.createNewFile();//将图片放入 }catch (IOException e){ e.printStackTrace(); } //获取Uri对象 //这个Uri对象标识着output_image.jpg这张图片的本地真实路径。 if (Build.VERSION.SDK_INT >= 24) { //调用FileProvider的getUriForFile() 方法将File 对象转换成一个封装过的Uri对象 imageUri = FileProvider.getUriForFile(MainActivity.this,"com.example.cameraalbumtest.fileprovider", outputImage); //FileProvider则是一种特殊的内容提供器,它使用了和内容提供器类似的机制来对数据进行保护, // 可以选择性地将封装过的Uri共享给外部,从而提高了应用的安全性。 //第一个参数要求传入Context 对象 //第二个参数可以是任意唯一的字符串 (需要在AndroidManifest.xml中声明) //第三个参数则是我们刚刚创建的File 对象 } else {//若系统的版本低于Android7.0,则调用下面的方法将File对象转换为Uri对象 imageUri = Uri.fromFile(outputImage); } //启动相机程序 Intent intent= new Intent("android.media.action.IMAGE_CAPTURE"); intent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri);//指定图片的输出地址 startActivityForResult(intent,TAKE_PHOTO);//调用startActivityForResult() 来启动活动。 } }); //从相册中取图片 Button chooseFromAlbum=(Button) findViewById(R.id.choose_from_album); chooseFromAlbum.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) {//定义该按钮点击事件 //申请一个运行时权限处理 //权限WRITE_EXTERNAL_STORAGE表示同时授予程序对SD卡读和写的能力。 if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1); }else {//授予了权限后,则调用openAlbum方法来获取图片 openAlbum(); } } }); } //定义openAlbum()方法来获取图片 private void openAlbum(){ Intent intent =new Intent("android.intent.action.GET_CONTENT");//构建一个intent对象,并将它的action指定 intent.setType("image/*"); startActivityForResult(intent, CHOOSE_PHOTO);//打开相册程序,选择照片 //给第二个参数传入的值变成了CHOOSE_PHOTO // 这样当从相册选择完图片回到onActivityResult() 方法时 // 就会进入CHOOSE_PHOTO 的case 来处理图片。 } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions,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; } } //使用startActivityForResult() 来启动活动的, // 因此拍完照后会有结果返回到onActivityResult() 方法中。 //在此函数中显示图像 @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case TAKE_PHOTO://拍照 if (resultCode == RESULT_OK) {//如果拍照成功 try { // 将拍摄的照片显示出来 //可以调用BitmapFactory的decodeStream() 方法将output_image.jpg这张照片解析成Bitmap 对象 Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri)); picture.setImageBitmap(bitmap);//将Bitmap对象,设置到ImageView中显示出来。 } catch (FileNotFoundException e) { e.printStackTrace(); } } break; case CHOOSE_PHOTO://打开相册 if (resultCode == RESULT_OK){ //判断手机的系统的版本号 if (Build.VERSION.SDK_INT>=19){ //4.4及以上系统使用这个方法处理图片 handleImageOnKitKat(data); } else { // 4.4以下系统使用这个方法处理图片 handleImageBeforeKitKat(data); } } default: break; } } //因为Android系统从4.4版本开始,选取相册中的图片不再返回图片真实的Uri了,而是一个封装过的Uri // 因此如果是4.4版本以上的手机就需要对这个Uri进行解析才行。 @TargetApi(19) private void handleImageOnKitKat(Intent data) {//用于解析Android4.4版本以上的封装过的Uri String imagePath = null; Uri uri = data.getData(); if (DocumentsContract.isDocumentUri(this, uri)) { // 如果是document类型的Uri,则通过document id处理 String docId = DocumentsContract.getDocumentId(uri); if("com.android.providers.media.documents".equals(uri.getAuthority())) { //如果Uri的authority是media格式的话,document id 还需要再进行一次解析 //要通过字符串分割的方式取出后半部分才能得到真正的数字id String id = docId.split(":")[1]; // 解析出数字格式的id String selection = MediaStore.Images.Media._ID + "=" + id; imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection); } 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); } } else if ("content".equalsIgnoreCase(uri.getScheme())) { // 如果是content类型的Uri,则使用普通方式处理 imagePath = getImagePath(uri, null); } else if ("file".equalsIgnoreCase(uri.getScheme())) { // 如果是file类型的Uri,直接获取图片路径即可 imagePath = uri.getPath(); } displayImage(imagePath); // 根据图片路径显示图片 } //它的Uri是没有封装过的,不需要任何解析 private void handleImageBeforeKitKat(Intent data) { Uri uri = data.getData(); String imagePath = getImagePath(uri, null);//直接将Uri传入到getImagePath() 方法当中就能获取到图片的真实路径了 displayImage(imagePath);//让图片显示到界面上 } //获取到图片的真实路径了 private String getImagePath(Uri uri, String selection) { String path = null; // 通过Uri和selection来获取真实的图片路径 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); } else { Toast.makeText(this, "failed to get image", Toast.LENGTH_SHORT).show(); } } }