前言:
這裡來實作相機的使用。
7.0牛扎糖官方改過權限,所以造成7.0以下會有FileUriExposedException的風險
這邊讓你迎刃而解
文獻來自官方檔案:
https://developer.android.com/training/camera/photobasics.html#TaskScalePhoto
實作:
先講一下要動到的地方先在AndroidManifest.xml內給權限吧
<manifest> ... <uses-feature android:name="android.hardware.camera" android:required="true" /> ... </manifest>
在上述的程式中,android:required=”true”可以判斷使用者的手機裝置是否有相機。
假設我們改成false,我們就必須透過寫code的方式由我們自己去確認。
讓我們來開相機吧
處理開到系統相機Intent的部分
private static final int REQUEST_CAPTURE_IMAGE = 100; private void openCameraIntent() { Intent pictureIntent = new Intent( MediaStore.ACTION_IMAGE_CAPTURE ); if(pictureIntent.resolveActivity(getPackageManager()) != null) { startActivityForResult(pictureIntent, REQUEST_CAPTURE_IMAGE); } }
我們再來處理拍完照後,回來App的畫面處理
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_CAPTURE_IMAGE && resultCode == RESULT_OK) { if (data != null && data.getExtras() != null) { Bitmap imageBitmap = (Bitmap) data.getExtras().get("data"); mImageView.setImageBitmap(imageBitmap); } } }
簡單的拍照,返回照片就完成囉!!
下載Demo:https://github.com/nikeru8/CameraDemo/commits/master
但上面只是做好看的,因為並沒有『儲存』這個動作。
拿上面的程式碼拍完照後,會發現沒有在相簿裡出現!?
當然啊,你又沒寫XD
接下來你必須寫一個存入系統的方法
我以為要存儲權限,但原生的相機貌似不用。(而且這篇我也忘了加入呼叫權限的code XD
現在讓我們創建一個方法,創立文件名稱“日期_檔名”,把拍下來的照片寫入外部的檔案目錄內。
String imageFilePath; private File createImageFile() throws IOException { String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date()); String imageFileName = "IMG_" + timeStamp + "_"; File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES); File image = File.createTempFile( imageFileName, /* prefix */ ".jpg", /* suffix */ storageDir /* directory */ ); imageFilePath = image.getAbsolutePath(); return image; }
上面的方法已經寫好了圖片儲存的地點,接下來開始存圖片了。
把創建資料夾的路徑帶給相機,並開啟系統資料夾!
下面複寫了剛剛上面開啟相機的方式
private static final int REQUEST_CAPTURE_IMAGE = 100; private void openCameraIntent() { Intent pictureIntent = new Intent( MediaStore.ACTION_IMAGE_CAPTURE); if (pictureIntent.resolveActivity(getPackageManager()) != null) { //創建一個資料夾去存圖片 File photoFile = null; try { photoFile = createImageFile(); } catch (IOException ex) { //當創建資料夾失敗... } //當創建的資料夾不為null直,把創建資料夾的路徑帶給相機,並開啟系統資料夾 if (photoFile != null) { Uri photoURI = FileProvider.getUriForFile(this, "com.example.android.provider", photoFile); pictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI); startActivityForResult(pictureIntent, REQUEST_CAPTURE_IMAGE); } } }
仔細看一下上述的程式碼,你一定會發現!“com.example.android.provider"是哪來的?在API等級 24以上的手機,都需要使用FileProvider去和你app內的manifest File 做連結。
讓我們開始FileProvider的步驟吧!
在manifest內加入provider
<application> ... <provider android:name="android.support.v4.content.FileProvider" android:authorities="${applicationId}.provider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths"> </meta-data> </provider> ... </application>
發現${applicationId}.provider 了嗎?這個就是串聯剛剛的“com.example.android.provider"。
直接把 ${applicationId}改成你App的Id名稱,這邊拿我的專案當範例
這兩邊的名稱要是一樣的,之後會長這樣
android:authorities="com.hello.kaiser.startcamera.provider"
發現紅色的地方了嗎?
“@xml/file_paths”
這邊就要自己創建了。
資料夾創建在res下面
變成
之後創建名為file_paths的xml檔案
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-path name="my_images" path="Android/data/com.example.package.name/files/Pictures" /> </paths>
上述程式碼中的path對應的就是剛剛上面寫得code『getExternalFilesDir()』的部分,也是你存到指定環境Environment.DIRECTORY_PICTURES的地方。
所以path必須做更改。
同樣拿我的專案舉例子,path就會改成
path="Android/data/com.hello.kaiser.startcamera/files/Pictures"長這樣子。
就差不多完成囉!
此時,拍完照片 > 返回你的app,就剩下顯示影像的工作了。
是寫在onActivityResult的地方!
onActivityResult(int requestCode, int resultCode, Intent data)
但又跟我們一開始寫的方式不一樣,此時帶回來的data會為nulll,所以判斷onActivityResult的部分只需要判斷requestCode是否為我們剛剛帶入的REQUEST_CAPTURE_IMAGE就行了,剛剛在上面提過獲取了imageFilePath路徑的方法。
現在直接在onActivityReslut內調用
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_CAPTURE_IMAGE && resultCode == RESULT_OK) { setPic(); } } private void setPic() { // 獲取你在actiivity_layout地方的ImageView大小 int targetW = mImageView.getWidth(); int targetH = mImageView.getHeight(); //獲取剛剛拍照圖片的大小 BitmapFactory.Options bmOptions = new BitmapFactory.Options(); bmOptions.inJustDecodeBounds = true; BitmapFactory.decodeFile(imageFilePath, bmOptions); int photoW = bmOptions.outWidth; int photoH = bmOptions.outHeight; //修改你要顯示圖片的尺寸大小 int scaleFactor = Math.min(photoW / targetW, photoH / targetH); //使用Bitmap size去調整ImageView內顯示的圖片 bmOptions.inJustDecodeBounds = false; bmOptions.inSampleSize = scaleFactor; bmOptions.inPurgeable = true; Bitmap bitmap = BitmapFactory.decodeFile(imageFilePath, bmOptions); //顯影像 mImageView.setImageBitmap(bitmap); }
可以開啟你的程式,試著拍照吧。
會存在手機裡面囉!
在onActivityReslut內可以使用更簡潔的方式,使用Glide
直接
Glide.with(this).load(imageFilePath).into(mImageView);
Glide介紹及使用方式:
http://nikeru8.blogspot.tw/2017/03/third-party-frescopicasso.html
官方:
https://github.com/bumptech/glide
需要注意的事情是,目前的第三方套件,套用方式已經改變
請在你的Gradle內加入
repositories { mavenCentral() google() } dependencies { implementation 'com.github.bumptech.glide:glide:4.7.1' annotationProcessor 'com.github.bumptech.glide:compiler:4.7.1' }
這是在android3.0之後的改變
有問題請提出吧。
你的問題會是我創作很大的動力!
心得:
之後還寫了一篇客製化相機(二) 關於證件照的。
請參閱囉
沒有留言:
張貼留言