本文實現的簡易的照相機,包括拍照、選圖、分享三個功能,主要涉及到Intent隱式啓動、Intent動作、Uri、危險權限讀取、圖像處理、手機旋轉等知識。
首先,設計程序佈局,僅有一個活動界面,如下所示:
設計程序的詳細步驟、所涉知識整理如下:
/**
* 項目目標:實現簡易照相機
* 實現方式:啓動系統照相機
* 學習目標:Intent隱式啓動、簡單模仿相機的圖片處理
*
* 實現步驟:
* 1、設計程序佈局
* 2、使用Intent啓動系統照相機
* 3、圖片處理:
* (1)、權限獲取
* (2)、保存圖片
* (3)、顯示圖片
* (4)、手機旋轉,圖像的處理
* 4、使用Intent瀏覽並選取照片
* 5、分享照片
*
* 詳細實現過程及知識整理:
* 一、使用Intent啓動系統照相機
* Intent it = new Intent("android.media.action.IMAGE_CAPTURE");
* startActivityForResult(it,100);
* 涉及動作:android.media.action.IMAGE_CAPTURE,意爲拍照。
* 啓動方式:要求動作完成後返回數據。
* 100作爲自定義標誌符,用於識別。即處理多個返回的數據時,可以確定是哪一個Intent。
*
* 二、圖片處理
* 1、權限獲取
* 若要保存圖片,必然涉及寫操作,需要寫權限纔可以,這在android系統中是一個危險權限,
* 實現讀寫操作,需要在AndroidManifest.xml中加入以下權限:
* <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
* <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
* 同時,還需要向用戶請求獲取這些權限,因爲上述兩個權限屬於STORAGE權限組,所以只要其中一個
* 權限被允許,其他同類權限也會被自動允許,故程序中可以只添加其中一個權限。
*
* 以寫權限爲例:
* if (ActivityCompat.checkSelfPermission(this,
* Manifest.permission.WRITE_EXTERNAL_STORAGE) !=
* PackageManager.PERMISSION_GRANTED) { //檢查是否已獲得寫入權限
* ActivityCompat.requestPermissions(this, //向用戶要求允許寫入權限
* new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},200);
* }
* 在向用戶請求權限的代碼中,方法的第二個參數必須爲數組形式,可以一次請求多個權限,第三個參數200
* 代表自定義的識別碼,用戶無論是否同意權限,程序都可以通過onRequestPermissionsResult方法接收
* 結果,此時可以作爲識別之用。如下代碼所示:
* public void onRequestPermissionsResult(int requestCode, String[] permissions,
* int[] grantResults){
* if (requestCode == 200){
* if (grantResults[0] == PackageManager.PERMISSION_GRANTED){ //用戶允許權限
* savePhoto(); //獲取權限後纔可以進行保存圖片操作
* }else{ //用戶拒絕權限
* Toast.makeText(this,"程序需要寫入權限才能運行",Toast.LENGTH_SHORT).show();
* }
* }
* }
* 其中grantResults數組中存放的就是用戶允許還是拒絕權限的結果,可以同時有多個,與要求權限時的
* 權限排列順序、數量相同。
*
* 2、保存圖片
* 因爲資源的存取只能通過內容提供者,利用URI的傳遞,以便同意管控權限而避免不當的存取。
* 首先使用getContentResolver()獲得系統的內容提供者,然後通過它在手機共享圖像文件路徑裏新增
* 一個文件,並傳回該文件的Uri對象,然後將該對象保存。
* 綜上所述,獲取權限後,啓動系統照相機,啓動時將Uri對象加入額外數據中,如下代碼所示:
* private void savePhoto(){
* imgUri = getContentResolver().insert(
* MediaStore.Images.Media.EXTERNAL_CONTENT_URI,new ContentValues());
* Intent it = new Intent("android.media.action.IMAGE_CAPTURE");
* it.putExtra(MediaStore.EXTRA_OUTPUT, imgUri);
* startActivityForResult(it, 100);
* }
* 其中putExtra方法中,通過內容提供者獲取的對象就是imgUri,名字爲MediaStore.EXTRA_OUTPUT,
* 相機程序會用這個名字讀取未來拍照存檔的URI。
*
* 3、顯示圖片
* (1)、用BitmapFactory來讀取圖像。
* Bitmap bmp = BitmapFactory.decodeStream(getContentResolver().
* openInputStream(imgUri), null, null);//讀取圖像內容並存儲爲Bitmap對象
* imv.setImageBitmap(bmp); //將Bitmap對象顯示在ImageView中
* 如此一來就會把圖片顯示出來了,但是這樣做有一個弊端,當圖片過大的時候,很有可能會因爲內存不
* 足而導致程序崩潰,所以需要對顯示的圖片進行約束,合理顯示,避免過大。
* (2)、用BitmapFactory.Options設置加載圖像文件的選項
* BitmapFactory.Options option = new BitmapFactory.Options(); //新建選項對象
* option.inJustDecodeBounds = true; //設置選項:只讀取圖片文件信息而不加載圖片文件
* BitmapFactory.decodeStream(imgUri.getPath(),option);//讀取圖像文件信息存入option中
* iw = option.outWidth(); //從option中讀取圖像文件的寬
* ih = option.outHeight(); //從option中讀取圖像文件的高
* 按比例縮小圖像:
* vw = imv.getWidth(); //獲取 ImageView 的寬度
* vh = imv.getHeight(); //獲取 ImageView 的高度
* int scaleFactor = Math.min(iw/vw, ih/vh); // 計算縮小比例
* option.inJustDecodeBounds = false; //關閉只加載圖片文件信息的選項
* option.inSampleSize = scaleFactor; //設置縮小比例,例如2則長寬都將縮小爲原來的1/2
* 這時圖像的處理之後的信息都在Options對象中,載入圖片:
* Bitmap bmp = BitmapFactory.decodeStream(
* getContentResolver().openInputStream(imgUri), null, option);
* 顯示圖片:imv.setImageBitmap(bmp); //顯示圖片
*
* 4、手機旋轉,圖像的處理。
* 如果手機開啓自動旋轉屏幕,則剛纔拍攝的照片在顯示中,會因爲屏幕的旋轉而消失。
* 解決方式有兩種:
* 方式一:在發生旋轉時,立即將所顯示照片的Uri存儲起來,等旋轉完並重新啓動Activity後,在按
* 照存儲的Uri將照片顯示出來,另外,還可以按照旋轉的方向載入不用的界面Layout文件來顯示。
* 方式二:關閉手機界面的自動旋轉功能。
* 用戶在拍攝時會有橫拍或豎拍,所以最好由程序決定要不要旋轉屏幕,而不是隨手機的旋轉而旋轉。
* (1)、關閉自動旋轉功能並設置屏幕爲直向顯示
* //設置屏幕不隨手機旋轉
* setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
* /設置屏幕直向顯示/
* setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
* (2)、處理橫拍和豎拍時,圖像寬高的縮小比例
* Boolean needRotate; //用來存儲是否需要旋轉
* int scaleFactor;
* if(iw < ih) { //如果圖片的寬度小於高度
* needRotate = false; //不需要旋轉
* scaleFactor = Math.min(iw/vw, ih/vh); // 計算縮小比例
* }
* else {
* needRotate = true; //需要旋轉
* scaleFactor = Math.min(iw/vh, ih/vw);// 將ImageView的寬高互換來計算縮小比例
* }
* (3)、用Matrix(android.graphics.Matrix)對象旋轉圖片
* Matrix是一個旋轉矩陣,用Bitmap.createBitmap()創建新的Bitmap時,可以用它來旋轉圖片。
* createBitmap(Bitmap src,int x,int y,int width,int height,Matrix m,boolean filter)
* src爲要複製的來源Bitmap對象。
* x、y指定要由來源Bitmap的那個位置開始複製(從左上角算起)。
* width、height爲新的Bitmap的寬高。
* m是旋轉矩陣,而最後一個參數filter設置了旋轉時需要傳入true。
* 注:生成新的Bitmap後,原來的Bitmap對象就會被系統回收。
* 具體代碼如下:
* if(needRotate) { //如果需要旋轉
* Matrix matrix = new Matrix(); //新建 Matrix 對象
* matrix.postRotate(90); //設置旋轉角度
* bmp = Bitmap.createBitmap(bmp , //用原來的 Bitmap 創建一個新的 Bitmap
* 0, 0, bmp.getWidth(), bmp.getHeight(), matrix, true);
* }
*
* 做了這麼多的圖像處理後,爲了更直觀的瞭解圖片從拍照到顯示的變化,可以加入以下代碼,用一個
* 彈窗顯示出圖片的詳細信息。
* new AlertDialog.Builder(this)
* .setTitle("圖像文件信息")
* .setMessage("圖像文件URI:" + imgUri.toString() +
* "\n原始尺寸:" + iw + "x" + ih +
* "\n載入尺寸:" + bmp.getWidth() + "x" + bmp.getHeight() +
* "\n顯示尺寸:" + vw + "x" + vh + (needRotate?"(旋轉)":""))
* .setNeutralButton("關閉",null)
* .show();
* 窗口中詳細的展示了圖像的URI路徑、拍照尺寸、Bitmap載入的尺寸、ImageView顯示的尺寸,還
* 包括是否做了旋轉操作。
*
*三、使用Intent瀏覽選擇圖片
* Android系統中內建了一個內容提供者,存儲着各種數據和多媒體文件等共享數據的相關信息。
* Android內建的圖庫程序就是從這個數據庫中讀取可共享的圖片文件信息,然後列出來供用戶選取。
* 所以將拍攝的照片設爲系統共享文件,並用廣播Intent的方式通知系統。
* //將imgUri所指的文件設爲系統共享媒體文件
* Intent it = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, imgUri);
* sendBroadcast(it); //通知系統
* 選取圖片:
* Intent it = new Intent(Intent.ACTION_GET_CONTENT); //動作設爲 "選取內容"
* it.setType("image/*"); //設置要選取的媒體類型爲:所有類型的圖片
* startActivityForResult(it, 101); //啓動意圖, 並要求返回選取的圖片
* 101是自定義的識別碼。上面的代碼中要求返回選取的圖片,所以在onActivityResult方法中可以
* 根據識別碼判斷是哪一個Intent的動作,並獲取這個圖片的Uri信息,然後將其顯示。
*
* 四、分享圖片
* 只要圖片不爲空,就可以使用動作ACTION_SEND分享圖片,代碼如下:
* if(imgUri != null){
* Intent it = new Intent(Intent.ACTION_SEND);
* it.setType("image/*");
* it.putExtra(Intent.EXTRA_STREAM,imgUri);
* startActivity(it);
* }
*
* */
程序具體代碼如下所示:
package com.my.camera;
import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ContentValues;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;
import java.io.IOException;
public class MainActivity extends AppCompatActivity {
Uri imgUri; //記錄圖片信息
ImageView imv; //視圖組件
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//設置屏幕不隨手機旋轉
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
//設置屏幕直向顯示
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
//關聯視圖組件
imv = (ImageView)findViewById(R.id.imageView);
}
/**
* 按鈕“拍照”的點擊事件
* */
public void onGet(View v){
//檢查是否獲得寫入權限,未獲得則向用戶請求
if(ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED){
//未獲得,向用戶請求
ActivityCompat.requestPermissions(this,new String[]
{Manifest.permission.WRITE_EXTERNAL_STORAGE},200);
}else{
//已經獲得權限
savePhoto();
}
}
/**
* 按鈕“圖庫”的點擊事件
* */
public void onPick(View v){
Intent it = new Intent(Intent.ACTION_GET_CONTENT); //設置動作爲選取內容
it.setType("image/*"); //設置要選取的媒體類型:所有類型圖片
startActivityForResult(it,101); //啓動Intent,並要求返回選取的圖像文件
}
/**
* 按鈕“分享”的點擊事件
* */
public void onShare(View v){
if(imgUri != null){
Intent it = new Intent(Intent.ACTION_SEND);
it.setType("image/*");
it.putExtra(Intent.EXTRA_STREAM,imgUri);
startActivity(it);
}
}
/**
* 返回用戶是否允許權限的結果,並處理
* */
public void onRequestPermissionsResult(int requestCode,String[] permissions, int[] grantResult){
if(requestCode == 200){
//用戶允許權限
if(grantResult[0] == PackageManager.PERMISSION_GRANTED){
savePhoto(); //允許寫權限纔可以保存圖片
}else{ //用戶拒絕
Toast.makeText(this,"程序需要寫入權限才能運行",Toast.LENGTH_SHORT).show();
}
}
}
private void savePhoto(){
//通過內容提供者新增一個圖像文件
imgUri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new ContentValues());
Intent it = new Intent("android.media.action.IMAGE_CAPTURE"); //指定動作,拍照
it.putExtra(MediaStore.EXTRA_OUTPUT,imgUri); //將uri加到拍照Intent的額外數據
startActivityForResult(it,100); //設置100爲識別碼,啓動活動
}
/**
* 處理通過Intent啓動的活動的返回結果
* 識別碼100,拍照活動返回的數據,將拍攝的圖片設爲共享,並通知系統
* 識別碼101,選取圖片活動返回的數據,直接讀取數據並存儲到imgUri中
* 調用showImg方法顯示圖片
* 根據識別碼來顯示不同的出錯Toast信息
* */
protected void onActivityResult(int requestCode,int resultCode,Intent data){
super.onActivityResult(requestCode,resultCode,data);
if(resultCode == Activity.RESULT_OK){
switch(requestCode){
case 100:
//設爲系統共享媒體文件
Intent it = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,imgUri);
sendBroadcast(it);
break;
case 101:
//選取圖片,直接獲取該圖片的數據
imgUri = data.getData();
break;
}
showImg(); //顯示圖片
}else{ //根據識別碼的不同,顯示不同的沒有獲取到圖片的信息
Toast.makeText(this,requestCode==100?"沒有拍到照片":"沒有選取照片",
Toast.LENGTH_SHORT).show();
}
}
/**
* 顯示圖片
* 避免圖像過大,進行處理後顯示
* 判斷圖片是橫拍還是直拍決定是否旋轉
* 顯示圖片處理過程的詳細信息
* */
public void showImg(){
int iw,ih,vw,vh; //iw,ih爲圖片寬高,vw,vh爲ImageView組件的寬高
boolean needRotate; //用來存儲是否需要旋轉
Bitmap bmp = null; //創建Bitmap對象
BitmapFactory.Options option = new BitmapFactory.Options(); //創建選項對象
option.inJustDecodeBounds = true; //設置選項:只讀取圖像文件信息而不載入圖像文件
try{
//讀取圖像文件信息存入option中
BitmapFactory.decodeStream(getContentResolver().openInputStream(imgUri),null,option);
}catch(IOException e){
Toast.makeText(this,"讀取照片信息時發生錯誤",Toast.LENGTH_SHORT).show();
return;
}
iw = option.outWidth; //從option中讀取圖像文件的寬度
ih = option.outHeight; //從option中讀取圖像文件的高度
vw = imv.getWidth(); //獲取ImageView的寬度
vh = imv.getHeight(); //獲取ImageView的高度
int scaleFactor;
if(iw < ih){
needRotate = false; //不需要旋轉
scaleFactor = Math.min(iw/vw,ih/vh); //計算縮小比例
}else{
needRotate = true;
scaleFactor = Math.min(ih/vw,iw/vh); //改用旋轉後的圖像寬、高計算縮小比例
}
option.inJustDecodeBounds = false; //關閉之加載圖像文件信息的選項
option.inSampleSize = scaleFactor; //設置縮小比例,若爲3,則長寬將縮小爲原來的1/3
try{
//加載圖片
bmp = BitmapFactory.decodeStream(getContentResolver().openInputStream(imgUri),
null,option);
}catch(IOException e){
Toast.makeText(this,"無法取得照片",Toast.LENGTH_SHORT).show();
}
if(needRotate){
Matrix matrix = new Matrix(); //創建Matrix對象
matrix.postRotate(90); //旋轉90°,順時針
//用原來的圖像產生一個新的圖片
bmp = Bitmap.createBitmap(bmp,0,0,bmp.getWidth(),bmp.getHeight(),matrix,true);
}
imv.setImageBitmap(bmp); //顯示圖片
//彈窗顯示圖片的信息
new AlertDialog.Builder(this)
.setTitle("圖像文件信息")
.setMessage("圖像文件URI:" + imgUri.toString() +
"\n原始尺寸:" + iw + "x" + ih +
"\n載入尺寸:" + bmp.getWidth() + "x" + bmp.getHeight() +
"\n顯示尺寸:" + vw + "x" + vh + (needRotate?"(旋轉)":""))
.setNeutralButton("關閉",null)
.show();
}
}
// 總結參考:《Android App開發入門 第2版》 機械工業出版社 施威銘 著 2017.8
程序的部分運行結果如下所示:
(哇,我在編輯的時候發現這個圖好大好大啊,不知道發佈出去之後會不會還這麼大。)
這個簡易照相機非常的簡單,使用起來非常的呆板,還需要不斷的完善。