經過無盡的搜尋資料,參考國內外的程式高手們分享的程式碼,我總算融會貫通後,成功地寫出來完整功能的相機程式了....
可能有人會質疑:利用 Intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE) ;
直接呼叫內建的相機功能就好啦,各種功能樣樣齊全呢!
但是之所以要自己寫相機程式必定有其特殊需求,例如想設計類似大頭貼的攝影程式,就是在相機預覽畫面中先疊上一張圖,像這樣:
是的,那只能要自己去 Layout 、自己寫camera方法去呼叫相機了,先從Layout開始說明:
要如何將圖檔放在相機預覽畫面上呢?答案就是通常我們要放相機預覽畫面,必須要使用 SurfaceView,所以只要再SurfaceView的Background屬性中指定圖檔就可以了。
另外,Android的拍照、錄影,都是預設橫式的,但是我需要的是直式的拍照啊?這部分我也嘗試了很久,例如使用 camera.setDisplayOrientation(90); 嘗試將顯示畫面選轉90度,但是實際手機預覽畫面卻明顯失真、比例都扁掉了...所以最終,我就將直式的Layout更改成橫式的就好囉,對程式而言是標準的橫式,但是人拿起手機拍照就像是直式拍照囉:
另外一個大缺點,就是儲存後的照片也是橫式的,所以想要轉成直式的照片,就需要利用程式在照片存檔之前先選轉乘90度再儲存,如此一來,實際手機畫面看到的就是直式的照片,所以拍照的部分,除了是要指定拍照後為JPEG檔案,品質參數以及拍完照後的儲存方式,使用獨立的副程式處理:
- private final class TakePictureCallback implements PictureCallback {
- @Override
- public void onPictureTaken(byte[] data, Camera camera) {
- try {
- Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0,data.length);
- Matrix matrix = new Matrix(); //
- matrix.setRotate(90); //
- bitmap = Bitmap.createBitmap(bitmap, 0, 0, //將相片旋轉90度後回存
- bitmap.getWidth(), //
- bitmap.getHeight(),matrix, true); //
-
- String Path = createFilePath(); //載入儲存路徑
- //createFilePath() 為處理儲存檔案、儲存路徑的副程式
- FileOutputStream outStream = new FileOutputStream(Path);
- bitmap.compress(CompressFormat.JPEG, 100, outStream);
- outStream.close();
- camera.stopPreview();
- camera.startPreview();//開始預覽
- } catch (Exception e) {
- Log.e(TAG, e.toString());
- }
- }
- }
以下是處理檔案的副程式:
- private String createFilePath() {
- File dir = Environment.getExternalStorageDirectory();
- File appDir = new File(dir, "GSC/downline"); //指定資料夾
- if(!appDir.exists()) appDir.mkdir();
- String name = System.currentTimeMillis() + ".jpg"; //檔案以系統時間來命名
- return new File(appDir, name).getAbsolutePath();
- }
自動對焦功能,我希望在拍照前可以進行自動對焦,因此,我將自動對焦部分包成一個副程式,副程式當中才去呼叫拍照的程式,因此拍照前都會先自動對焦了,因此我在處理"拍照按鈕"的部分,呼叫的是autoFoucs這支副程式,而不是TalkPictureCallback,在autoFoucs裡面才呼叫TalkPictureCallback:
- Camera.AutoFocusCallback autoFocus = new Camera.AutoFocusCallback() {
- @Override
- public void onAutoFocus(boolean success, Camera camera)
- {// TODO Auto-generated method stub
- if(success){
- camera.takePicture(null, null, new TakePictureCallback());
- //TakePictureCallback()是另外處理拍照的副程式
- }else{
- Toast.makeText(camera_downline.this, R.string.focusError, Toast.LENGTH_LONG).show();
- }
- }
- };
當然,相機最主要的程式碼,包含一剛開始在開始構圖預覽、轉換、結束以及相關重要的參數如下:
- private SurfaceHolder.Callback surfaceholdercallback = new SurfaceHolder.Callback(){
- @Override
- public void surfaceChanged(SurfaceHolder holder, int format, int width,
- int height) {
- }
- @Override
- public void surfaceCreated(SurfaceHolder holder) {
- try{
- if(camera==null){
- camera = Camera.open();
- }else{
- Toast.makeText(camera_downline.this, "相機正在使用中", 1).show();
- }
- WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
- Display display = wm.getDefaultDisplay();//設定這變數為預設的螢幕大小
- parameters = camera.getParameters();
- parameters.setPreviewSize(display.getWidth(), display.getHeight());
- //設置預覽照片的大小
- parameters.setPreviewFrameRate(3);//每秒三張
- parameters.setPictureFormat(PixelFormat.JPEG);//照片的輸出格式
- parameters.set("jpeg-quality", 85);//照片質量
- parameters.setPictureSize(display.getWidth(), display.getHeight());
- //設置照片的Size, 這邊也可以指定Pixel: Ex: 768, 1024
- camera.setParameters(parameters);
- camera.setPreviewDisplay(surfaceview.getHolder());
- //透過Surface View顯示預覽畫面
- camera.startPreview();//開始預覽
- preview = true;
- }catch(IOException e){
- e.printStackTrace();
- }
- }
- @Override
- public void surfaceDestroyed(SurfaceHolder holder) {
- // TODO Auto-generated method stub
- if (camera != null) {
- if (preview)
- camera.stopPreview();
- camera.release();
- }
- }
- };
閃光燈的部分,放在按鈕裡面處裡就可以了,可以使用ToggleButton來去開啟或者關閉,因為我的ToggleButton是用自己設計的底圖,因此希望按下去的時候可以更換另外一個底圖,以下是 flash按鈕的程式碼:
- flash.setOnClickListener(new OnClickListener(){
- //定義返回按鍵後動作
- public void onClick(View arg0){
- if (flash.isChecked()){
- flash.setBackgroundDrawable(
- getResources().getDrawable(R.drawable.record_flesh_on));
- //點選後更換底圖
- parameters.setFlashMode(Parameters.FLASH_MODE_ON);//開啟閃光燈
- camera.setParameters(parameters);
- Toast.makeText(camera_downline.this, R.string.fleshOn, 1).show();
- }else {
- flash.setBackgroundDrawable(
- getResources().getDrawable(R.drawable.record_flesh_off));
- //點選後更換底圖
- parameters.setFlashMode(Parameters.FLASH_MODE_OFF); 關閉閃光燈
- camera.setParameters(parameters);
- Toast.makeText(camera_downline.this, R.string.fleshOff, 1).show();
- }
- }
- });
整體的程式碼大概就是如此了,另外,通常拍照的時候,希望是全螢幕、沒有軟體抬頭、或者保持螢幕開啟,可以在onCreate程式開頭加上以下這段程式碼:
- public void onCreate(Bundle savedInstanceState){
- super.onCreate(savedInstanceState);
- Window window = getWindow();
- requestWindowFeature(Window.FEATURE_NO_TITLE);//取消軟體抬頭
- window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
- WindowManager.LayoutParams.FLAG_FULLSCREEN);//全螢幕
- window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- //螢幕保持開啟
以上,如果有甚麼更好方式,歡迎與我討論...