show code block

2018年4月12日 星期四

Android方法 — 使用相機的正確姿勢(一)

前言:






這裡來實作相機的使用。

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

接下來你必須寫一個存入系統的方法

一樣先加入權限吧!
manifests內加入存入權限
 
我以為要存儲權限,但原生的相機貌似不用。(而且這篇我也忘了加入呼叫權限的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對應的就是剛剛上面寫得codegetExternalFilesDir()』的部分,也是你存到指定環境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之後的改變

有問題請提出吧。

你的問題會是我創作很大的動力!

Demo:https://github.com/nikeru8/CameraDemo


心得:

之後還寫了一篇
客製化相機(二) 關於證件照的。

請參閱囉



沒有留言:

張貼留言

協程(coroutine) - 協程為什麼要學它?

 Coroutine 協程 再強調一次 協程就是由kotlin官方所提供的線程api //Thread Thread { }.start() //Executor val execu...