原来以为拍照比较简单,后来发现android对拍照的权限根据不同的版本要求不同,储存的权限也是要求动态或取的,动态或取其实讲白了,就是要app主动去获取,同事在xml也要写权限。本人花了一些时间整理了一下给大家。源文件在我的下载里。
最后鄙视一下那些自恃清高(大学霸)的人,一边卖着xamarin稀少的资源,一边管开发者们叫“伸手党”,令人反感。博客精神就是开源开放,让大家少花不必要的时间,大家的时间都很宝贵,有源码有demo干嘛不用。
本案例包括5部分:
1、Main.axml,其实比较简单,就两个按钮,一个imageview。
2、MainActivity.cs,也就是主窗体的cs文件,里面包括了拍照,拍完会看,浏览相册,看相册某一张的基本功能
3、BitmapHelpers.cs,看图片的类,没什么好说的,无非就是设定一下图片横看还是竖看,图片框的大小等信息
4、CameraHelper.cs,文件的路径类,读文件的取和保存
5、AndroidManifest.xml权限类的放置位置,该文件在vs 的 properties下
好了,直接上代码:
1、Main.axml,视图文件
<?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:text="浏览"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn_Browse" />
<Button
android:text="拍照"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn_Shot" />
<ImageView
android:src="@android:drawable/ic_menu_gallery"
android:layout_width="match_parent"
android:layout_height="418.5dp"
android:id="@+id/imageView1" />
</LinearLayout>
2、MainActivity.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.Provider;
using Android.Database;
using System.Threading;
using Java.IO;
using Android.Graphics;
using Environment = Android.OS.Environment;
using Uri = Android.Net.Uri;
using Android.Content.PM;
using Android;
namespace CallLocalPhoto
{
[Activity(Label = "CallLocalPhoto", MainLauncher = true)]
public class MainActivity : Activity
{
Button btn_Browse,btn_Shot;
ImageView iv;
public static File _file;
public static File _dir;
public static Bitmap bitmap;
private Java.IO.File originalFile;
private File finalFile;
private string imgName;
private int requestCode_browse=2;
private int requestCode_shot=0;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.Main);
//其他安卓芯片的默认拍照路径
originalFile = new Java.IO.File(Android.OS.Environment.GetExternalStoragePublicDirectory(
Android.OS.Environment.DirectoryPictures
), "zcb_pic_" + SystemClock.CurrentThreadTimeMillis() + ".jpg");
//高通芯片的默认拍照路径
finalFile = new Java.IO.File(Android.OS.Environment.GetExternalStoragePublicDirectory(
Android.OS.Environment.DirectoryDcim +@"/Camera"
), "zcb_pic_" + SystemClock.CurrentThreadTimeMillis() + ".jpg");
if (Android.OS.Build.VERSION.SdkInt >= BuildVersionCodes.M)
{
initPhotoError();
}
btn_Browse = FindViewById<Button>(Resource.Id.btn_Browse);
btn_Shot = FindViewById<Button>(Resource.Id.btn_Shot);
iv = FindViewById<ImageView>(Resource.Id.imageView1);
btn_Browse.Click += Btn_Browse_Click;
btn_Shot.Click += TakeAPicture;
if (IsThereAnAppToTakePictures()) //判断本设备是否存在拍照功能
{
//可以自己创建拍照的路径,也可以选择默认的相册路径
// CreateDirectoryForPictures();
}
else
{
Toast.MakeText(this, "无拍照功能 !", ToastLength.Long).Show();
}
}
/// <summary>
/// 判断是否具备拍照功能
/// </summary>
/// <returns></returns>
private bool IsThereAnAppToTakePictures()
{
Intent intent = new Intent(MediaStore.ActionImageCapture);
IList<ResolveInfo> availableActivities = PackageManager.QueryIntentActivities(intent, PackageInfoFlags.MatchDefaultOnly);
return availableActivities != null && availableActivities.Count > 0;
}
/// <summary>
/// 创建目录图片
/// </summary>
private void CreateDirectoryForPictures()
{
CameraHelper._dir = new File(
Environment.GetExternalStoragePublicDirectory(
Environment.DirectoryPictures), "CameraAppDemo"); //CameraAppDemo
if (!CameraHelper._dir.Exists())
{
CameraHelper._dir.Mkdirs();
Toast.MakeText(this, "有拍照功能 "+ CameraHelper._dir + "路径已创建成功!", ToastLength.Long).Show();
}
else
{
Toast.MakeText(this, "有拍照功能 " + CameraHelper._dir + "路径已存在!", ToastLength.Long).Show();
}
}
//浏览相册
private void Btn_Browse_Click(object sender, EventArgs e)
{
Intent _intentCut = new Intent(Intent.ActionPick, null);
_intentCut.SetType("image/*");// 设置文件类型
CameraHelper._file = finalFile;//照片的保存路径即相册
_intentCut.PutExtra(MediaStore.ExtraOutput, Android.Net.Uri.FromFile(CameraHelper._file));
StartActivityForResult(_intentCut, requestCode_browse); //2代表看相册
}
/// <summary>
/// 拍照
/// </summary>
/// <param name="sender"></param>
/// <param name="eventArgs"></param>
private void TakeAPicture(object sender, EventArgs eventArgs)
{
try
{
// 摄像头拍摄
Intent intent = new Intent(MediaStore.ActionImageCapture);
if (Android.OS.Build.VERSION.SdkInt >= BuildVersionCodes.M)
{
//判断是否授予了权限
if (ApplicationContext.CheckSelfPermission(Android.Manifest.Permission.Camera) != Android.Content.PM.Permission.Granted)
{
//没有权限,当场申请
RequestPermissions(new string[] { Android.Manifest.Permission.Camera }, 1);
Toast.MakeText(this, "没有拍照权限,正在授予", ToastLength.Long).Show();
}
else
{
Toast.MakeText(this, "有拍照权限了", ToastLength.Long).Show();
}
if (ApplicationContext.CheckSelfPermission(Manifest.Permission.ReadExternalStorage) != Android.Content.PM.Permission.Granted)
{
//RequestPermissions(new String[] {Manifest.Permission.ReadExternalStorage, Manifest.Permission.WriteExternalStorage,Manifest.Permission.Camera}, 1);
RequestPermissions(new String[] { Manifest.Permission.ReadExternalStorage, Manifest.Permission.WriteExternalStorage, }, 1);
Toast.MakeText(this, "没有读写扩展卡权限,正在授予", ToastLength.Long).Show();
}
else
{
Toast.MakeText(this, "有读写扩展卡权限了", ToastLength.Long).Show();
}
}
CameraHelper._file = finalFile;//照片的保存路径
intent.PutExtra(MediaStore.ExtraOutput, Android.Net.Uri.FromFile(CameraHelper._file));
StartActivityForResult(intent, requestCode_shot);
}
catch (Exception ex)
{
Toast.MakeText(this, "Shot Error:" + ex.ToString (), ToastLength.Long).Show();
}
}
private void initPhotoError()
{
// android 7.0系统解决拍照的问题
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.SetVmPolicy(builder.Build());
builder.DetectFileUriExposure();
}
/// <summary>
/// 选择图片后返回
/// </summary>
/// <param name="requestCode"></param>
/// <param name="ResultStatus"></param>
/// <param name="data"></param>
protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result ResultStatus, Intent data)
{
if (ResultStatus != Result.Ok)
{
return;
}
if (requestCode == requestCode_browse)//看相册请求
{
/*
* 若系统版本低于4.4,返回原uri
* 若高于4.4,解析uri后返回
* */
if (Android.OS.Build.VERSION.SdkInt >= BuildVersionCodes.Kitkat)
{
//var url = Android.Net.Uri.Parse("file://" + GetPath(BaseContext, data.Data));
var url = Android.Net.Uri.Parse("file://" + GetPath(BaseContext, data.Data));
data.SetData(url);
//将本地相册照片显示在控件上
iv.SetImageURI(Android.Net.Uri.FromFile(new File(GetPath(BaseContext, data.Data))));
string fileName = url.LastPathSegment.ToString();
Toast.MakeText(this, fileName, ToastLength.Long).Show();
}
}
try
{
if (requestCode == requestCode_shot)//拍照看照片请求
{
if (Android.OS.Build.VERSION.SdkInt >= BuildVersionCodes.Kitkat)
{
int height = Resources.DisplayMetrics.HeightPixels;
int width = iv.Height;
//获取拍照的位图
CameraHelper.bitmap = CameraHelper._file.Path.LoadAndResizeBitmap(width, height);
if (CameraHelper.bitmap != null)
{
//将图片绑定到控件上
iv.SetImageBitmap(CameraHelper.bitmap);
//清空bitmap 否则会出现oom问题
CameraHelper.bitmap = null;
}
// Dispose of the Java side bitmap.
GC.Collect();
string fileName = CameraHelper._file.Name.ToString();
Toast.MakeText(this, fileName, ToastLength.Long).Show();
}
}
}
catch (Exception ex)
{
Toast.MakeText(this, "拍照回调错误:" + ex.ToString(), ToastLength.Long).Show();
}
}
#region 高于 v4.4 版本 解析真实路径
public static String GetPath(Context context, Android.Net.Uri uri)
{
bool isKitKat = Build.VERSION.SdkInt >= BuildVersionCodes.Kitkat;
// DocumentProvider
if (isKitKat && DocumentsContract.IsDocumentUri(context, uri))
{
// ExternalStorageProvider
if (isExternalStorageDocument(uri))
{
String docId = DocumentsContract.GetDocumentId(uri);
String[] split = docId.Split(':');
String type = split[0];
if ("primary".Equals(type.ToLower()))
{
return Android.OS.Environment.ExternalStorageDirectory + "/" + split[1];
}
// TODO handle non-primary volumes
}
// DownloadsProvider
else if (isDownloadsDocument(uri))
{
String id = DocumentsContract.GetDocumentId(uri);
Android.Net.Uri contentUri = ContentUris.WithAppendedId(
Android.Net.Uri.Parse("content://downloads/public_downloads"), long.Parse(id));
return getDataColumn(context, contentUri, null, null);
}
// MediaProvider
else if (isMediaDocument(uri))
{
String docId = DocumentsContract.GetDocumentId(uri);
String[] split = docId.Split(':');
String type = split[0];
Android.Net.Uri contentUri = null;
if ("image".Equals(type))
{
contentUri = MediaStore.Images.Media.ExternalContentUri;
}
else if ("video".Equals(type))
{
contentUri = MediaStore.Video.Media.ExternalContentUri;
}
else if ("audio".Equals(type))
{
contentUri = MediaStore.Audio.Media.ExternalContentUri;
}
String selection = "_id=?";
String[] selectionArgs = new String[] {
split[1]
};
return getDataColumn(context, contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else if ("content".Equals(uri.Scheme.ToLower()))
{
// Return the remote address
if (isGooglePhotosUri(uri))
return uri.LastPathSegment;
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".Equals(uri.Scheme.ToLower()))
{
return uri.Path;
}
return null;
}
/**
* Get the value of the data column for this Uri. This is useful for
* MediaStore Uris, and other file-based ContentProviders.
*
* @param context The context.
* @param uri The Uri to query.
* @param selection (Optional) Filter used in the query.
* @param selectionArgs (Optional) Selection arguments used in the query.
* @return The value of the _data column, which is typically a file path.
*/
public static String getDataColumn(Context context, Android.Net.Uri uri, String selection, String[] selectionArgs)
{
ICursor cursor = null;
String column = "_data";
String[] projection = {
column
};
try
{
cursor = context.ContentResolver.Query(uri, projection, selection, selectionArgs,
null);
if (cursor != null && cursor.MoveToFirst())
{
int index = cursor.GetColumnIndexOrThrow(column);
return cursor.GetString(index);
}
}
finally
{
if (cursor != null)
cursor.Close();
}
return null;
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
public static bool isExternalStorageDocument(Android.Net.Uri uri)
{
return "com.android.externalstorage.documents".Equals(uri.Authority);
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
public static bool isDownloadsDocument(Android.Net.Uri uri)
{
return "com.android.providers.downloads.documents".Equals(uri.Authority);
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
public static bool isMediaDocument(Android.Net.Uri uri)
{
return "com.android.providers.media.documents".Equals(uri.Authority);
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is Google Photos.
*/
public static bool isGooglePhotosUri(Android.Net.Uri uri)
{
return "com.google.android.apps.photos.content".Equals(uri.Authority);
}
#endregion
}
}
3、BitmapHelpers.cs
namespace CallLocalPhoto
{
using System.IO;
using Android.Graphics;
public static class BitmapHelpers
{
public static Bitmap LoadAndResizeBitmap(this string fileName, int width, int height)
{
// First we get the the dimensions of the file on disk
BitmapFactory.Options options = new BitmapFactory.Options { InJustDecodeBounds = true };
BitmapFactory.DecodeFile(fileName, options);
// Next we calculate the ratio that we need to resize the image by
// in order to fit the requested dimensions.
int outHeight = options.OutHeight;
int outWidth = options.OutWidth;
int inSampleSize = 1;
if (outHeight > height || outWidth > width)
{
inSampleSize = outWidth > outHeight
? outHeight / height
: outWidth / width;
}
// Now we will load the image and have BitmapFactory resize it for us.
options.InSampleSize = inSampleSize;
options.InJustDecodeBounds = false;
Bitmap resizedBitmap = BitmapFactory.DecodeFile(fileName, options);
return resizedBitmap;
}
}
}
4、CameraHelper.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Java.IO;
namespace CallLocalPhoto
{
class CameraHelper
{
public static File _file;
public static File _dir;
public static Bitmap bitmap;
public static void CreateDirectoryForPictures()
{
_dir = new File( Android.OS.Environment.GetExternalStoragePublicDirectory( Android.OS.Environment.DirectoryPictures), "CameraAppDemo");
if (!_dir.Exists())
{
_dir.Mkdirs();
}
}
public static Bitmap LoadAndResizeBitmap(string fileName, int width, int height)
{
// First we get the the dimensions of the file on disk
BitmapFactory.Options options = new BitmapFactory.Options { InJustDecodeBounds = true };
BitmapFactory.DecodeFile(fileName, options);
// Next we calculate the ratio that we need to resize the image by
// in order to fit the requested dimensions.
int outHeight = options.OutHeight;
int outWidth = options.OutWidth;
int inSampleSize = 1;
if (outHeight > height || outWidth > width)
{
inSampleSize = outWidth > outHeight
? outHeight / height
: outWidth / width;
}
// Now we will load the image and have BitmapFactory resize it for us.
options.InSampleSize = inSampleSize;
options.InJustDecodeBounds = false;
Bitmap resizedBitmap = BitmapFactory.DecodeFile(fileName, options);
return resizedBitmap;
}
}
}
5、AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="CallLocalPhoto.CallLocalPhoto" android:versionCode="1" android:versionName="1.0" android:installLocation="auto">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="27" />
<uses-feature android:name="android.hardware.camera" />
<!-- 相机权限 -->
<uses-permission android:name="android.permission.CAMERA" />
<!--写入SD卡的权限:如果你希望保存相机拍照后的照片-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!--读取SD卡的权限:打开相册选取图片所必须的权限-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- 录音权限
<uses-permission android:name="android.permission.RECORD_AUDIO" />-->
<!--软件自主安装升级权限
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />-->
<application android:allowBackup="true" android:label="@string/app_name"></application>
</manifest>