Android从相机和图库捕获图像
在本教程中,我们将开发一个应用程序,该应用程序可以从相机或者图库中拾取图像并将其显示在ImageView中。
注意:以下代码适用于Android之前的牛轧糖版本。
有关最新的工作示例,请查看更新的文章。
Android Capture图像概述
随着Android Marshmallow的开始,需要首先实现运行时权限。
在Android Manifest.xml文件中的应用程序标签上方添加以下权限。
<uses-feature android:name="android.hardware.camera" android:required="false" <uses-feature android:name="android.hardware.camera.autofocus" android:required="false" <uses-feature android:name="android.hardware.camera.flash" android:required="false" <uses-permission android:name="android.permission.CAMERA" <uses-permission android:name="ANDROID.PERMISSION.READ_EXTERNAL_STORAGE"
通过添加android.hardware.camera,Play商店可以检测并阻止在没有相机的设备上安装该应用程序。
意图是将动作委派给另一个应用程序的标准方法。
要启动本机相机,Intent需要android.provider.MediaStore.ACTION_IMAGE_CAPTURE。
要从图库中选择图像,Intent需要以下参数:Intent.ACTION_GET_CONTENT。
在本教程中,我们将调用图像选择器,使我们可以从相机或者图库中选择图像,并以圆形图像视图和普通图像视图显示该图像。
在build.gradle文件中添加以下依赖项。compile'de.hdodenhof:circleimageview:2.1.0'
Android Capture图像代码
activity_main.xml的布局保持不变,除非将FAB按钮的图标更改更改为@android:drawable/ic_menu_camera
。
下面给出了" content_main.xml":
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android" xmlns:app="https://schemas.android.com/apk/res-auto" xmlns:tools="https://schemas.android.com/tools" android:id="@+id/content_main" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:background="#000000" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context="com.theitroad.imagepicker.MainActivity" tools:showIn="@layout/activity_main"> <RelativeLayout android:layout_width="250dp" android:layout_height="250dp" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:background="@drawable/image_border" android:clickable="true" android:orientation="vertical"> <ImageView android:id="@+id/imageView" android:layout_width="match_parent" android:layout_height="match_parent" android:adjustViewBounds="true" android:scaleType="centerCrop" </RelativeLayout> <de.hdodenhof.circleimageview.CircleImageView android:id="@+id/img_profile" android:layout_width="100dp" android:layout_height="100dp" android:layout_gravity="center_horizontal" android:src="@drawable/profile" app:civ_border_width="5dp" app:civ_border_color="#FFFFFF" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" </RelativeLayout>
下面给出了MainActivity.java的代码
public class MainActivity extends AppCompatActivity { Bitmap myBitmap; Uri picUri; private ArrayList permissionsToRequest; private ArrayList permissionsRejected = new ArrayList(); private ArrayList permissions = new ArrayList(); private final static int ALL_PERMISSIONS_RESULT = 107; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { startActivityForResult(getPickImageChooserIntent(), 200); } }); permissions.add(CAMERA); permissionsToRequest = findUnAskedPermissions(permissions); //get the permissions we have asked for before but are not granted.. //we will store this in a global list to access later. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (permissionsToRequest.size() > 0) requestPermissions(permissionsToRequest.toArray(new String[permissionsToRequest.size()]), ALL_PERMISSIONS_RESULT); } } @Override public boolean onCreateOptionsMenu(Menu menu) { //Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { //Handle action bar item clicks here. The action bar will //automatically handle clicks on the Home/Up button, so long //as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } /** * Create a chooser intent to select the source to get image from.<br * The source can be camera's (ACTION_IMAGE_CAPTURE) or gallery's (ACTION_GET_CONTENT).<br * All possible sources are added to the intent chooser. */ public Intent getPickImageChooserIntent() { //Determine Uri of camera image to save. Uri outputFileUri = getCaptureImageOutputUri(); List allIntents = new ArrayList(); PackageManager packageManager = getPackageManager(); //collect all camera intents Intent captureIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE); List listCam = packageManager.queryIntentActivities(captureIntent, 0); for (ResolveInfo res : listCam) { Intent intent = new Intent(captureIntent); intent.setComponent(new ComponentName(res.activityInfo.packageName, res.activityInfo.name)); intent.setPackage(res.activityInfo.packageName); if (outputFileUri != null) { intent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri); } allIntents.add(intent); } //collect all gallery intents Intent galleryIntent = new Intent(Intent.ACTION_GET_CONTENT); galleryIntent.setType("image/*"); List listGallery = packageManager.queryIntentActivities(galleryIntent, 0); for (ResolveInfo res : listGallery) { Intent intent = new Intent(galleryIntent); intent.setComponent(new ComponentName(res.activityInfo.packageName, res.activityInfo.name)); intent.setPackage(res.activityInfo.packageName); allIntents.add(intent); } //the main intent is the last in the list (fucking android) so pickup the useless one Intent mainIntent = allIntents.get(allIntents.size() - 1); for (Intent intent : allIntents) { if (intent.getComponent().getClassName().equals("com.android.documentsui.DocumentsActivity")) { mainIntent = intent; break; } } allIntents.remove(mainIntent); //Create a chooser from the main intent Intent chooserIntent = Intent.createChooser(mainIntent, "Select source"); //Add all other intents chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, allIntents.toArray(new Parcelable[allIntents.size()])); return chooserIntent; } /** * Get URI to image received from capture by camera. */ private Uri getCaptureImageOutputUri() { Uri outputFileUri = null; File getImage = getExternalCacheDir(); if (getImage != null) { outputFileUri = Uri.fromFile(new File(getImage.getPath(), "profile.png")); } return outputFileUri; } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { Bitmap bitmap; if (resultCode == Activity.RESULT_OK) { ImageView imageView = (ImageView) findViewById(R.id.imageView); if (getPickImageResultUri(data) != null) { picUri = getPickImageResultUri(data); try { myBitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), picUri); myBitmap = rotateImageIfRequired(myBitmap, picUri); myBitmap = getResizedBitmap(myBitmap, 500); CircleImageView croppedImageView = (CircleImageView) findViewById(R.id.img_profile); croppedImageView.setImageBitmap(myBitmap); imageView.setImageBitmap(myBitmap); } catch (IOException e) { e.printStackTrace(); } } else { bitmap = (Bitmap) data.getExtras().get("data"); myBitmap = bitmap; CircleImageView croppedImageView = (CircleImageView) findViewById(R.id.img_profile); if (croppedImageView != null) { croppedImageView.setImageBitmap(myBitmap); } imageView.setImageBitmap(myBitmap); } } } private static Bitmap rotateImageIfRequired(Bitmap img, Uri selectedImage) throws IOException { ExifInterface ei = new ExifInterface(selectedImage.getPath()); int orientation = ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); switch (orientation) { case ExifInterface.ORIENTATION_ROTATE_90: return rotateImage(img, 90); case ExifInterface.ORIENTATION_ROTATE_180: return rotateImage(img, 180); case ExifInterface.ORIENTATION_ROTATE_270: return rotateImage(img, 270); default: return img; } } private static Bitmap rotateImage(Bitmap img, int degree) { Matrix matrix = new Matrix(); matrix.postRotate(degree); Bitmap rotatedImg = Bitmap.createBitmap(img, 0, 0, img.getWidth(), img.getHeight(), matrix, true); img.recycle(); return rotatedImg; } public Bitmap getResizedBitmap(Bitmap image, int maxSize) { int width = image.getWidth(); int height = image.getHeight(); float bitmapRatio = (float) width/(float) height; if (bitmapRatio > 0) { width = maxSize; height = (int) (width/bitmapRatio); } else { height = maxSize; width = (int) (height * bitmapRatio); } return Bitmap.createScaledBitmap(image, width, height, true); } /** * Get the URI of the selected image from {@link #getPickImageChooserIntent()}.<br * Will return the correct URI for camera and gallery image. * * @param data the returned data of the activity result */ public Uri getPickImageResultUri(Intent data) { boolean isCamera = true; if (data != null) { String action = data.getAction(); isCamera = action != null && action.equals(MediaStore.ACTION_IMAGE_CAPTURE); } return isCamera ? getCaptureImageOutputUri() : data.getData(); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); //save file url in bundle as it will be null on scren orientation //changes outState.putParcelable("pic_uri", picUri); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); //get the file url picUri = savedInstanceState.getParcelable("pic_uri"); } private ArrayList findUnAskedPermissions(ArrayList wanted) { ArrayList result = new ArrayList(); for (String perm : wanted) { if (!hasPermission(perm)) { result.add(perm); } } return result; } private boolean hasPermission(String permission) { if (canMakeSmores()) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { return (checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED); } } return true; } private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) { new AlertDialog.Builder(this) .setMessage(message) .setPositiveButton("OK", okListener) .setNegativeButton("Cancel", null) .create() .show(); } private boolean canMakeSmores() { return (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1); } @TargetApi(Build.VERSION_CODES.M) @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { switch (requestCode) { case ALL_PERMISSIONS_RESULT: for (String perms : permissionsToRequest) { if (hasPermission(perms)) { } else { permissionsRejected.add(perms); } } if (permissionsRejected.size() > 0) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (shouldShowRequestPermissionRationale(permissionsRejected.get(0))) { showMessageOKCancel("These permissions are mandatory for the application. Please allow access.", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { //Log.d("API123", "permisionrejected " + permissionsRejected.size()); requestPermissions(permissionsRejected.toArray(new String[permissionsRejected.size()]), ALL_PERMISSIONS_RESULT); } } }); return; } } } break; } } }
从上面的代码中可以得出很多推断。
当用户启动活动时,我们需要请求Camera运行时权限。
在开始获取一些结果的意图时,我们需要使用相关参数调用startActivityForResult
我们没有使用对话框来分别调用"相机和画廊意图",而是使用了getPickImageChooserIntent()方法,该方法为所有相机和画廊意图创建一个选择器意图(请注意文档意图)。
Intent.EXTRA_INITIAL_INTENTS用于在一个位置添加多个应用程序意图对于摄像机意图,将传递MediaStore.EXTRA_OUTPUT作为另外内容以指定图像存储路径。
否则,您将只获得一幅小分辨率的图像。相机返回的图像的URI路径是在getCaptureImageOutputUri()方法内部获取的。
onActivityResult本质上返回图像的URI。
某些设备的确将位图返回为data.getExtras().get(" data");
。单击图像后,返回时的相机屏幕将重新启动活动,从而使从方法getCaptureImageOutputUri()存储的URI变为空。
因此,我们必须使用" onSaveInstanceState()"和" onRestoreInstanceState()"来存储和还原该URI。
- 在下面的代码行中,从URI中检索位图。
myBitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(),picUri);
众所周知,像三星 galaxy这样的设备可以横向捕获图像。
检索图像并按原样显示可能会导致其显示方向错误。
因此,我们将方法称为" rotateImageIfRequired(myBitmap,picUri);"。ExifInterface是用于在JPEG文件或者RAW图像文件中读写Exif标签的类。
最后,我们调用方法getResizedBitmap()来按宽度或者高度(以较大者为准)缩放位图,并使用setImageBitmap将图像设置为图像视图。
实际应用程序的输出如下。
注意:由于明显的原因,要从相机捕获并显示图像,您需要在智能手机上运行该应用程序。