今天要來聊關於Android中的相機功能(・∀・)
就是辣個我們每天在用卻從來不知道是怎麼寫出來的東西XD
ㄏ (・ωー)~☆
就如正題,今天要寫的是相機功能
當初在接觸相機功能的時候,覺得這東西特別煩
明明就只是個照相功能,但是一下子預覽畫質太差,一下子又不明原因閃退,反正開發過程問題一堆QQ
唉...反正雖然說磨了很久,也不過是一天的事而已 C.C
總之現在就是完成了才會寫這篇XD
接著就來上功能吧~
再來是Github
->https://github.com/thumbb13555/CameraExample/tree/master
1.功能說明
簡述一下功能的重點,畫面長這樣
不知道看不看得清楚,整個畫面分別有兩個ImageView
並且兩個Button都是開啟相機功能,而照完照片後照片都會直接顯示在ImageView內
但是兩者的差別是照完後顯示在ImageView的畫質,上面是較高畫質的,而下面則是低畫質
從Gif中肯定看不出來,仔細看照片應該就看得出來了
...好啦我也不確定網誌是否真的真的看得出來囧
總之我說有就有啦-`д´-
重點來了,為什麼會有這兩者差異呢?
其實主要是因為傳輸照片的方法不一樣所致(´・_・`)
低畫質的方法是最簡單的,也是網路上最多人在教的
那兩者的傳出差異在哪?簡單來說
低畫質的照片,他照片取得來源是直接從相機畫面用intent傳遞過來的
由於intent傳遞上有大小限制,因此系統自然不會傳很大的圖片
那麼高畫質的又是怎麼來?
高畫質主要是系統在拍照完成後,將圖片存在手機儲存裝置中
然後ImageView抓取的是抓圖片的路徑(String),因此就能順利保留原圖了
怎麼樣?聽起來不難吧~
既然簡單,接著就實作摟(・∀・)
2.加入第三方庫Glide&撰寫UI界面
2-1 加入Glide
今天還有另一個標題,就是要將程式加入一個專門用來搞定跟圖片載入有關的第三方庫
首先先載入Glide吧
Glide官網
->https://github.com/bumptech/glide
找到build.gradle
找到dependencies
並加入這一行
implementation 'com.github.bumptech.glide:glide:4.11.0'
按下Sync即完成
2-2 畫介面
介面部分分別由兩個ImageView及各對應一個按鈕
如下
<?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
然後命名為xml
接著加入xml檔案
xml(右鍵)->New XML Resource File
File name自己取就可以
Root element的部分改成"paths"
完成會像這樣
接著打開它,撰寫以下內容
<?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
<?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,並加入複寫
先來實作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(); } } }
兩個部分的內容,供參考囉!
最後附上全部
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版的,雖然最後是在官網翻到,但是還是反覆換好多個關鍵字才找到
高畫質傳輸的部分我也老實說不是我想出來的,是我之前在別的文章看到的
...總之就是花了不少心思啦,恩!
那麼今天的文章到這邊,希望對各位有幫助
掰逼!
留言列表