虽然调用摄像头拍照既方便又快捷,但我们并不是每次都需要去当场拍一张照片的。因为每个人的手机相册里应该都会存有许许多多张照片,直接从相册里选取一张现有的照片会比打开相机拍一张照片更加常用。一个优秀的应用程序应该将这两种选择方式都提供给用户,由用户来决定使用哪一种。下面我们就来看一下,如何才能实现从相册中选择照片的功能。
还是在CameraAlbumTest项目的基础上进行修改,编辑activity_main.xml文件,在布局中添加一个按钮用于从相册中选择照片,代码如下所示:
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 android:orientation="vertical" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" > 5 6 <Button 7 android:id="@+id/take_photo" 8 android:layout_width="match_parent" 9 android:layout_height="wrap_content" 10 android:text="Take Photo" 11 /> 12 13 <ImageView 14 android:id="@+id/picture" 15 android:layout_width="wrap_content" 16 android:layout_height="wrap_content" 17 android:layout_gravity="center_horizontal" 18 /> 19 20 <Button 21 android:id="@+id/choose_from_album" 22 android:layout_width="match_parent" 23 android:layout_height="wrap_content" 24 android:text="Choose from Album" 25 /> 26 27 28 </LinearLayout>
1 package com.example.cameraalbumtest; 2 3 import androidx.appcompat.app.AppCompatActivity; 4 import androidx.core.app.ActivityCompat; 5 import androidx.core.content.ContextCompat; 6 import androidx.core.content.FileProvider; 7 8 import android.Manifest; 9 import android.annotation.TargetApi; 10 import android.content.ContentUris; 11 import android.content.Intent; 12 import android.content.pm.PackageManager; 13 import android.database.Cursor; 14 import android.graphics.Bitmap; 15 import android.graphics.BitmapFactory; 16 import android.net.Uri; 17 import android.os.Build; 18 import android.os.Bundle; 19 import android.provider.DocumentsContract; 20 import android.provider.MediaStore; 21 import android.view.View; 22 import android.widget.Button; 23 import android.widget.ImageView; 24 import android.widget.Toast; 25 26 import java.io.File; 27 import java.io.FileNotFoundException; 28 import java.io.IOException; 29 30 public class MainActivity extends AppCompatActivity { 31 32 public static final int TAKE_PHOTO=1;//拍照 33 public static final int CHOOSE_PHOTO=2;//从相册取照片 34 35 private ImageView picture; 36 37 private Uri imageUri; 38 39 @Override 40 protected void onCreate(Bundle savedInstanceState) { 41 super.onCreate(savedInstanceState); 42 setContentView(R.layout.activity_main); 43 44 //获取实例 45 Button takePhoto=(Button) findViewById(R.id.take_photo); 46 picture =(ImageView) findViewById(R.id.picture); 47 48 takePhoto.setOnClickListener(new View.OnClickListener() { 49 @Override 50 public void onClick(View v) { 51 //创建file对象,用于存储拍照后的图片 52 File outputImage=new File(getExternalCacheDir(),"output_image.jpg");//把图片进行命名 53 //调用getExternalCacheDir()可以得到手机SD卡的应用关联缓存目录 54 //所谓的应用关联缓存目录,就是指SD卡中专门用于存放当前应用缓存数据的位置 55 //具体的路径是/sdcard/Android/data/<package name>/cache 56 57 //因为从Android 6.0系统开始,读写SD卡被列为了危险权限, 58 // 如果将图片存放在SD卡的任何其他目录,都要进行运行时权限处理才行,而使用应用关联目录则可以跳过这一步。 59 try { 60 if (outputImage.exists()) {//如果已经存在了图片,则删掉, 61 outputImage.delete(); 62 } 63 outputImage.createNewFile();//将图片放入 64 }catch (IOException e){ 65 e.printStackTrace(); 66 } 67 68 //获取Uri对象 69 //这个Uri对象标识着output_image.jpg这张图片的本地真实路径。 70 if (Build.VERSION.SDK_INT >= 24) { 71 //调用FileProvider的getUriForFile() 方法将File 对象转换成一个封装过的Uri对象 72 imageUri = FileProvider.getUriForFile(MainActivity.this,"com.example.cameraalbumtest.fileprovider", outputImage); 73 //FileProvider则是一种特殊的内容提供器,它使用了和内容提供器类似的机制来对数据进行保护, 74 // 可以选择性地将封装过的Uri共享给外部,从而提高了应用的安全性。 75 //第一个参数要求传入Context 对象 76 //第二个参数可以是任意唯一的字符串 (需要在AndroidManifest.xml中声明) 77 //第三个参数则是我们刚刚创建的File 对象 78 } else {//若系统的版本低于Android7.0,则调用下面的方法将File对象转换为Uri对象 79 imageUri = Uri.fromFile(outputImage); 80 } 81 82 83 //启动相机程序 84 Intent intent= new Intent("android.media.action.IMAGE_CAPTURE"); 85 intent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri);//指定图片的输出地址 86 startActivityForResult(intent,TAKE_PHOTO);//调用startActivityForResult() 来启动活动。 87 } 88 }); 89 90 //从相册中取图片 91 Button chooseFromAlbum=(Button) findViewById(R.id.choose_from_album); 92 chooseFromAlbum.setOnClickListener(new View.OnClickListener() { 93 @Override 94 public void onClick(View v) {//定义该按钮点击事件 95 //申请一个运行时权限处理 96 //权限WRITE_EXTERNAL_STORAGE表示同时授予程序对SD卡读和写的能力。 97 if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED) 98 { 99 ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1); 100 }else {//授予了权限后,则调用openAlbum方法来获取图片 101 openAlbum(); 102 } 103 104 } 105 }); 106 107 } 108 109 //定义openAlbum()方法来获取图片 110 private void openAlbum(){ 111 Intent intent =new Intent("android.intent.action.GET_CONTENT");//构建一个intent对象,并将它的action指定 112 intent.setType("image/*"); 113 startActivityForResult(intent, CHOOSE_PHOTO);//打开相册程序,选择照片 114 //给第二个参数传入的值变成了CHOOSE_PHOTO 115 // 这样当从相册选择完图片回到onActivityResult() 方法时 116 // 就会进入CHOOSE_PHOTO 的case 来处理图片。 117 } 118 119 @Override 120 public void onRequestPermissionsResult(int requestCode, String[] permissions,int[] grantResults){ 121 switch (requestCode){ 122 case 1: 123 if (grantResults.length>0 && grantResults[0]==PackageManager.PERMISSION_GRANTED){ 124 openAlbum(); 125 }else { 126 Toast.makeText(this,"You denied the permission",Toast.LENGTH_SHORT).show(); 127 } 128 break; 129 default: 130 break; 131 } 132 } 133 134 135 //使用startActivityForResult() 来启动活动的, 136 // 因此拍完照后会有结果返回到onActivityResult() 方法中。 137 //在此函数中显示图像 138 @Override 139 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 140 super.onActivityResult(requestCode, resultCode, data); 141 switch (requestCode) { 142 case TAKE_PHOTO://拍照 143 if (resultCode == RESULT_OK) {//如果拍照成功 144 try { 145 // 将拍摄的照片显示出来 146 //可以调用BitmapFactory的decodeStream() 方法将output_image.jpg这张照片解析成Bitmap 对象 147 Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri)); 148 picture.setImageBitmap(bitmap);//将Bitmap对象,设置到ImageView中显示出来。 149 } catch (FileNotFoundException e) { 150 e.printStackTrace(); 151 } 152 } 153 break; 154 case CHOOSE_PHOTO://打开相册 155 if (resultCode == RESULT_OK){ 156 //判断手机的系统的版本号 157 if (Build.VERSION.SDK_INT>=19){ 158 //4.4及以上系统使用这个方法处理图片 159 handleImageOnKitKat(data); 160 } else { 161 // 4.4以下系统使用这个方法处理图片 162 handleImageBeforeKitKat(data); 163 } 164 } 165 default: 166 break; 167 } 168 } 169 170 //因为Android系统从4.4版本开始,选取相册中的图片不再返回图片真实的Uri了,而是一个封装过的Uri 171 // 因此如果是4.4版本以上的手机就需要对这个Uri进行解析才行。 172 @TargetApi(19) 173 private void handleImageOnKitKat(Intent data) {//用于解析Android4.4版本以上的封装过的Uri 174 String imagePath = null; 175 Uri uri = data.getData(); 176 177 if (DocumentsContract.isDocumentUri(this, uri)) { 178 // 如果是document类型的Uri,则通过document id处理 179 String docId = DocumentsContract.getDocumentId(uri); 180 if("com.android.providers.media.documents".equals(uri.getAuthority())) { 181 //如果Uri的authority是media格式的话,document id 还需要再进行一次解析 182 //要通过字符串分割的方式取出后半部分才能得到真正的数字id 183 String id = docId.split(":")[1]; // 解析出数字格式的id 184 String selection = MediaStore.Images.Media._ID + "=" + id; 185 imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection); 186 } else if ("com.android.providers.downloads.documents".equals(uri. getAuthority())) { 187 Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(docId)); 188 imagePath = getImagePath(contentUri, null); 189 } 190 } else if ("content".equalsIgnoreCase(uri.getScheme())) { 191 // 如果是content类型的Uri,则使用普通方式处理 192 imagePath = getImagePath(uri, null); 193 } else if ("file".equalsIgnoreCase(uri.getScheme())) { 194 // 如果是file类型的Uri,直接获取图片路径即可 195 imagePath = uri.getPath(); 196 } 197 displayImage(imagePath); // 根据图片路径显示图片 198 } 199 200 //它的Uri是没有封装过的,不需要任何解析 201 private void handleImageBeforeKitKat(Intent data) { 202 Uri uri = data.getData(); 203 String imagePath = getImagePath(uri, null);//直接将Uri传入到getImagePath() 方法当中就能获取到图片的真实路径了 204 displayImage(imagePath);//让图片显示到界面上 205 } 206 207 //获取到图片的真实路径了 208 private String getImagePath(Uri uri, String selection) { 209 String path = null; 210 // 通过Uri和selection来获取真实的图片路径 211 Cursor cursor = getContentResolver().query(uri, null, selection, null, null); 212 if (cursor != null) { 213 if (cursor.moveToFirst()) { 214 path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA)); 215 } 216 cursor.close(); 217 } 218 return path; 219 } 220 221 222 //将图片显示到界面上 223 private void displayImage(String imagePath) { 224 if (imagePath != null) { 225 Bitmap bitmap = BitmapFactory.decodeFile(imagePath); 226 picture.setImageBitmap(bitmap); 227 } else { 228 Toast.makeText(this, "failed to get image", Toast.LENGTH_SHORT).show(); 229 } 230 } 231 }