今天要來聊關於Android中的相機功能(・∀・)

就是辣個我們每天在用卻從來不知道是怎麼寫出來的東西XD

 

ㄏ (・ωー)~☆

就如正題,今天要寫的是相機功能

當初在接觸相機功能的時候,覺得這東西特別煩

明明就只是個照相功能,但是一下子預覽畫質太差,一下子又不明原因閃退,反正開發過程問題一堆QQ

唉...反正雖然說磨了很久,也不過是一天的事而已 C.C

總之現在就是完成了才會寫這篇XD

接著就來上功能吧~

高畫質

 

低畫質

再來是Github

->https://github.com/thumbb13555/CameraExample/tree/master


 

1.功能說明

簡述一下功能的重點,畫面長這樣

截圖 2020-05-23 下午9.19.09

 

不知道看不看得清楚,整個畫面分別有兩個ImageView

並且兩個Button都是開啟相機功能,而照完照片後照片都會直接顯示在ImageView內

但是兩者的差別是照完後顯示在ImageView的畫質,上面是較高畫質的,而下面則是低畫質

從Gif中肯定看不出來,仔細看照片應該就看得出來了

Screenshot_20200523-175451_CameraExample

...好啦我也不確定網誌是否真的真的看得出來囧

總之我說有就有啦-`д´-

 

重點來了,為什麼會有這兩者差異呢?

其實主要是因為傳輸照片的方法不一樣所致(´・_・`)

低畫質的方法是最簡單的,也是網路上最多人在教的

那兩者的傳出差異在哪?簡單來說

低畫質的照片,他照片取得來源是直接從相機畫面用intent傳遞過來

由於intent傳遞上有大小限制,因此系統自然不會傳很大的圖片

那麼高畫質的又是怎麼來?

高畫質主要是系統在拍照完成後,將圖片存在手機儲存裝置中

然後ImageView抓取的是抓圖片的路徑(String),因此就能順利保留原圖了

怎麼樣?聽起來不難吧~

下載

既然簡單,接著就實作摟(・∀・)


 

2.加入第三方庫Glide&撰寫UI界面

2-1 加入Glide

今天還有另一個標題,就是要將程式加入一個專門用來搞定跟圖片載入有關的第三方庫

首先先載入Glide吧

Glide官網

->https://github.com/bumptech/glide

找到build.gradle

截圖 2020-05-23 下午10.00.47

找到dependencies

截圖 2020-05-23 下午10.02.40

並加入這一行

implementation 'com.github.bumptech.glide:glide:4.11.0'

按下Sync即完成

截圖 2019-11-16 下午11.53.14

2-2 畫介面

介面部分分別由兩個ImageView及各對應一個按鈕

如下

截圖 2020-05-23 下午9.19.09

截圖 2020-05-23 下午10.09.28

 

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.5" />

    <Button
        android:id="@+id/buttonHigh"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
        android:text="高畫質相片拍攝"
        app:layout_constraintBottom_toTopOf="@+id/guideline2"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/buttonLow"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
        android:text="低畫質相片拍攝"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <ImageView
        android:id="@+id/imageViewHigh"
        android:layout_width="250dp"
        android:layout_height="250dp"
        app:layout_constraintBottom_toTopOf="@+id/buttonHigh"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@drawable/ic_launcher_background" />

    <ImageView
        android:id="@+id/imageViewLow"
        android:layout_width="250dp"
        android:layout_height="250dp"
        app:layout_constraintBottom_toTopOf="@+id/buttonLow"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline2"
        app:srcCompat="@drawable/ic_launcher_background" />
</androidx.constraintlayout.widget.ConstraintLayout>

 


 

3.載入相關權限

到這邊開始問題就比較多了(´・_・`)

統計一下要載入的東西

1.針對手機儲存裝置讀取寫入的相關權限

2.相機權限

3.相機功能取得(跟權限有點不一樣...)

4.加入provider

5.準備一個路徑檔案

...雖然看起來很多,不過1~4都會在AndroidManifest.xml內

不過我先來處理第五項好了~總之先在res下新增一個xml的資料夾

res右鍵->New->Directory

截圖 2020-05-23 下午10.20.49

 

然後命名為xml

截圖 2020-05-23 下午10.23.05

接著加入xml檔案

xml(右鍵)->New XML Resource File

截圖 2020-05-23 下午10.26.06

 

File name自己取就可以

Root element的部分改成"paths"

截圖 2020-05-23 下午10.28.42

完成會像這樣

截圖 2020-05-23 下午10.36.11

接著打開它,撰寫以下內容

 

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.jetec.cameraexample/files/Pictures" />
</paths>

標註底色的部分要置換成自己的package name,其他的不能打錯哦~

否則會閃退_(:3」∠)_

 

接著是AndroidManifest.xml

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.jetec.cameraexample">
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.CAMERA" />

    <uses-feature
        android:name="android.hardware.camera"
        android:required="true" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.jetec.cameraexample.CameraEx"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />

        </provider>
    </application>

 

首先是紅底色的部分,此處是加入讀寫及相機權限

再來是粉底色的部分,這邊是加入相機功能

以上兩個部分都直接複製過去就可以囉(・∀・)

最後是綠底色的部分

一樣可以直接複製過去,但就是上方藍底色的部分可以照自己的意思取名,這邊是給順取一個臨時權限用的

下方的則要注意剛才加入的xml檔案名稱位置要一致

 

建議到這邊先執行看看,如果能順利執行那權限上應該就沒什麼問題了

 


 

4.撰寫功能

4-1 撰寫低畫質照相功能

首先先來完成低畫質的照相吧

首先來到MainActivity.java

onCreate外按下control+O

找到onActivityResult,並加入複寫

截圖 2020-05-23 下午11.05.02

 

先來實作onCreate部分,請先加入以下程式

public class MainActivity extends AppCompatActivity {
    public static final String TAG = MainActivity.class.getSimpleName()+"My";

    private String mPath = "";//設置高畫質的照片位址
    public static final int CAMERA_PERMISSION = 100;//檢測相機權限用
    public static final int REQUEST_HIGH_IMAGE = 101;//檢測高畫質相機回傳
    public static final int REQUEST_LOW_IMAGE = 102;//檢測低畫質相機回傳

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btHigh = findViewById(R.id.buttonHigh);
        Button btLow = findViewById(R.id.buttonLow);
        /**取得相機權限*/
        if (checkSelfPermission(Manifest.permission.CAMERA)!= PackageManager.PERMISSION_GRANTED)
            requestPermissions(new String[]{Manifest.permission.CAMERA},CAMERA_PERMISSION);
        /**按下低畫質照相之拍攝按鈕*/
        btLow.setOnClickListener(v -> {
            Intent lowIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            //檢查是否已取得權限
            if (lowIntent.resolveActivity(getPackageManager()) == null) return;
            startActivityForResult(lowIntent,REQUEST_LOW_IMAGE);
        });

    }
    /**取得照片回傳*/
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        /**可在此檢視回傳為哪個相片,requestCode為上述自定義,resultCode為-1就是有拍照,0則是使用者沒拍照*/
        Log.d(TAG, "onActivityResult: requestCode: "+requestCode+", resultCode "+resultCode);
        /**如果是低畫質的相片回傳*/
        if (requestCode == REQUEST_LOW_IMAGE && resultCode == -1) {
            ImageView imageLow = findViewById(R.id.imageViewLow);
            Bundle getImage = data.getExtras();
            Bitmap getLowImage = (Bitmap) getImage.get("data");
            //以Glide設置圖片
            Glide.with(this)
                    .load(getLowImage)
                    .centerCrop()
                    .into(imageLow);
        }else{
            Toast.makeText(this, "未作任何拍攝", Toast.LENGTH_SHORT).show();
        }
    }
}

 

這時候可以先執行,低畫質的照相功能應該就會實現囉

低畫質階段完成

畫質真的很Low,對吧(╯=▃=)╯︵┻━┻

所以接下來要做高畫質囉!(・ωー)~☆

 

4-2 撰寫高畫質照相功能

**以下範例會暫時將剛才寫的低畫質部分去掉,僅留下必要判斷式**

public class MainActivity extends AppCompatActivity {
    public static final String TAG = MainActivity.class.getSimpleName()+"My";

    private String mPath = "";//設置高畫質的照片位址
    public static final int CAMERA_PERMISSION = 100;//檢測相機權限用
    public static final int REQUEST_HIGH_IMAGE = 101;//檢測高畫質相機回傳
    public static final int REQUEST_LOW_IMAGE = 102;//檢測低畫質相機回傳

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btHigh = findViewById(R.id.buttonHigh);
        Button btLow = findViewById(R.id.buttonLow);
        /**取得相機權限*/
        if (checkSelfPermission(Manifest.permission.CAMERA)!= PackageManager.PERMISSION_GRANTED)
            requestPermissions(new String[]{Manifest.permission.CAMERA},CAMERA_PERMISSION);
        /**按下低畫質照相之拍攝按鈕*/
        btLow.setOnClickListener(v -> {

        });
        /**按下高畫質照相之拍攝按鈕*/
        btHigh.setOnClickListener(v->{
            Intent highIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            //檢查是否已取得權限
            if (highIntent.resolveActivity(getPackageManager()) == null) return;
            //取得相片檔案的URI位址及設定檔案名稱
            File imageFile = getImageFile();
            if (imageFile == null) return;
            //取得相片檔案的URI位址
            Uri imageUri = FileProvider.getUriForFile(
                    this,
                    "com.jetec.cameraexample.CameraEx",//記得要跟AndroidManifest.xml中的authorities 一致
                    imageFile
            );
            highIntent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri);
            startActivityForResult(highIntent,REQUEST_HIGH_IMAGE);//開啟相機
        });
    }
    /**取得相片檔案的URI位址及設定檔案名稱*/
    private File getImageFile()  {
        String time = new SimpleDateFormat("yyMMdd").format(new Date());
        String fileName = time+"_";
        File dir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
        try {
            //給予檔案命名及檔案格式
            File imageFile = File.createTempFile(fileName,".jpg",dir);
            //給予全域變數中的照片檔案位置,方便後面取得
            mPath = imageFile.getAbsolutePath();
            return imageFile;
        } catch (IOException e) {
            return null;
        }
    }
    /**取得照片回傳*/
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        /**可在此檢視回傳為哪個相片,requestCode為上述自定義,resultCode為-1就是有拍照,0則是使用者沒拍照*/
        Log.d(TAG, "onActivityResult: requestCode: "+requestCode+", resultCode "+resultCode);
        /**如果是高畫質的相片回傳*/
        if (requestCode == REQUEST_HIGH_IMAGE && resultCode == -1){
            ImageView imageHigh = findViewById(R.id.imageViewHigh);
            new Thread(()->{
                //在BitmapFactory中以檔案URI路徑取得相片檔案,並處理為AtomicReference<Bitmap>,方便後續旋轉圖片
                AtomicReference<Bitmap> getHighImage = new AtomicReference<>(BitmapFactory.decodeFile(mPath));
                Matrix matrix = new Matrix();
                matrix.setRotate(90f);//轉90度
                getHighImage.set(Bitmap.createBitmap(getHighImage.get()
                        ,0,0
                        ,getHighImage.get().getWidth()
                        ,getHighImage.get().getHeight()
                        ,matrix,true));
                runOnUiThread(()->{
                    //以Glide設置圖片(因為旋轉圖片屬於耗時處理,故會LAG一下,且必須使用Thread執行緒)
                    Glide.with(this)
                            .load(getHighImage.get())
                            .centerCrop()
                            .into(imageHigh);
                });
            }).start();
        }/**如果是低畫質相片回傳*/
        else if (requestCode == REQUEST_LOW_IMAGE && resultCode == -1){

        }/***/
        else{
            Toast.makeText(this, "未作任何拍攝", Toast.LENGTH_SHORT).show();
        }
    }
}

 

兩個部分的內容,供參考囉!

 

最後附上全部

MainActivity.java

public class MainActivity extends AppCompatActivity {
    public static final String TAG = MainActivity.class.getSimpleName()+"My";

    private String mPath = "";//設置高畫質的照片位址
    public static final int CAMERA_PERMISSION = 100;//檢測相機權限用
    public static final int REQUEST_HIGH_IMAGE = 101;//檢測高畫質相機回傳
    public static final int REQUEST_LOW_IMAGE = 102;//檢測低畫質相機回傳

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btHigh = findViewById(R.id.buttonHigh);
        Button btLow = findViewById(R.id.buttonLow);
        /**取得相機權限*/
        if (checkSelfPermission(Manifest.permission.CAMERA)!= PackageManager.PERMISSION_GRANTED)
            requestPermissions(new String[]{Manifest.permission.CAMERA},CAMERA_PERMISSION);
        /**按下低畫質照相之拍攝按鈕*/
        btLow.setOnClickListener(v -> {
            Intent lowIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            //檢查是否已取得權限
            if (lowIntent.resolveActivity(getPackageManager()) == null) return;
            startActivityForResult(lowIntent,REQUEST_LOW_IMAGE);
        });
        /**按下高畫質照相之拍攝按鈕*/
        btHigh.setOnClickListener(v->{
            Intent highIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            //檢查是否已取得權限
            if (highIntent.resolveActivity(getPackageManager()) == null) return;
            //取得相片檔案的URI位址及設定檔案名稱
            File imageFile = getImageFile();
            if (imageFile == null) return;
            //取得相片檔案的URI位址
            Uri imageUri = FileProvider.getUriForFile(
                    this,
                    "com.jetec.cameraexample.CameraEx",//記得要跟AndroidManifest.xml中的authorities 一致
                    imageFile
            );
            highIntent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri);
            startActivityForResult(highIntent,REQUEST_HIGH_IMAGE);//開啟相機
        });
    }
    /**取得相片檔案的URI位址及設定檔案名稱*/
    private File getImageFile()  {
        String time = new SimpleDateFormat("yyMMdd").format(new Date());
        String fileName = time+"_";
        File dir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
        try {
            //給予檔案命名及檔案格式
            File imageFile = File.createTempFile(fileName,".jpg",dir);
            //給予全域變數中的照片檔案位置,方便後面取得
            mPath = imageFile.getAbsolutePath();
            return imageFile;
        } catch (IOException e) {
            return null;
        }
    }
    /**取得照片回傳*/
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        /**可在此檢視回傳為哪個相片,requestCode為上述自定義,resultCode為-1就是有拍照,0則是使用者沒拍照*/
        Log.d(TAG, "onActivityResult: requestCode: "+requestCode+", resultCode "+resultCode);
        /**如果是高畫質的相片回傳*/
        if (requestCode == REQUEST_HIGH_IMAGE && resultCode == -1){
            ImageView imageHigh = findViewById(R.id.imageViewHigh);
            new Thread(()->{
                //在BitmapFactory中以檔案URI路徑取得相片檔案,並處理為AtomicReference<Bitmap>,方便後續旋轉圖片
                AtomicReference<Bitmap> getHighImage = new AtomicReference<>(BitmapFactory.decodeFile(mPath));
                Matrix matrix = new Matrix();
                matrix.setRotate(90f);//轉90度
                getHighImage.set(Bitmap.createBitmap(getHighImage.get()
                        ,0,0
                        ,getHighImage.get().getWidth()
                        ,getHighImage.get().getHeight()
                        ,matrix,true));
                runOnUiThread(()->{
                    //以Glide設置圖片(因為旋轉圖片屬於耗時處理,故會LAG一下,且必須使用Thread執行緒)
                    Glide.with(this)
                            .load(getHighImage.get())
                            .centerCrop()
                            .into(imageHigh);
                });
            }).start();
        }/**如果是低畫質相片回傳*/
        else if (requestCode == REQUEST_LOW_IMAGE && resultCode == -1){
            ImageView imageLow = findViewById(R.id.imageViewLow);
            Bundle getImage = data.getExtras();
            Bitmap getLowImage = (Bitmap) getImage.get("data");
            //以Glide設置圖片
            Glide.with(this)
                    .load(getLowImage)
                    .centerCrop()
                    .into(imageLow);
        }/**未拍攝*/
        else{
            Toast.makeText(this, "未作任何拍攝", Toast.LENGTH_SHORT).show();
        }
    }
}

 


 

結語

不知道藉由以上的文章,大家對這功能有沒有多瞭解了一些些呢XD

最前面也有說,當初在寫這功能時真的花了我不少心思(´υ`)

例如Manifest中的Provide部分,之前大部分的文章都是還沒升級androidx版的,雖然最後是在官網翻到,但是還是反覆換好多個關鍵字才找到

高畫質傳輸的部分我也老實說不是我想出來的,是我之前在別的文章看到的

 

...總之就是花了不少心思啦,恩!

那麼今天的文章到這邊,希望對各位有幫助

掰逼!

tumblr_inline_pbljnbIsdC1w3x5f0_500

arrow
arrow

    碼農日常 發表在 痞客邦 留言(1) 人氣()