1. 程式人生 > >Android多媒體開發 Pro Android Media 第二章 建立自定義相機應用 3

Android多媒體開發 Pro Android Media 第二章 建立自定義相機應用 3

擴充套件自定義相機應用程式

在我看來,Android 上的內建相機應用程式缺少幾個基本特徵。其中之一是,延遲一小段時間,10或者30秒,之後進行拍攝。此種功能對於那些可以安裝在三腳架上的相機來說,通常很實用。它提供了這樣的功能,攝影師設定好鏡頭,設定好計時器,然後自己跑到鏡頭裡。
雖然對於行動電話而言,可能不是很常用。但在某些特殊場景,卻非常有用的。例如,當我想要和同伴一起拍照時,就非常喜歡這個功能。目前當我嘗試這樣做時,因為反對著螢幕,看不見觸屏介面,拍照就變得非常麻煩。在螢幕裡到處摸索亂按,希望能碰巧按下快門按鈕。

建立一個基於計時器的相機應用程式

為了扭轉剛才所述的情況,我們可以為拍攝增加一個延遲時間。讓我們更新我們的SnapShot示例,拍攝動作在按下按鍵10秒後進行。為了實現這個目標,我們需要使用某些類似 java.util.Timer 的東西。不幸的是,在 android 系統,使用計時器比較複雜,它會引入單獨的執行緒。而單獨執行緒要與UI進行互動,需要通過Handler,才能讓主執行緒執行某一動作。
Handler的另一個用法是,排程某個動作,在未來發生。有了Handler的這一功能,就不必使用Timer了。
若要建立一個Handler物件,在將來執行某些動作,我們只需構造一個通用物件:
Handler timerHandler = new Handler();  然後,我們必須建立一個Runnable物件。Runnable將要執行的動作,放到它的run方法中。在我們的例子裡,我們想要在10秒以後,執行圖片拍攝:
Runnable timerTask = new Runnable() 
{  
    public void run()
    {
        camera.takePicture(null,null,null,TimerSnapShot.this);
     }
};
這就夠了。現在當我們按下按鈕時,我們只需要做好排程:
timerHandler.postDelayed(timerTask, 10000);
這會告訴 timerHandler 在10秒(10000 毫秒)後呼叫我們的timerTask。在下面的示例中,我們建立一個Handler,讓它每隔1秒,就呼叫某個方法。以這種方式,我們可以為使用者在螢幕上提供倒計時。
package com.apress.proandroidmedia.ch2.timersnapshot;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.List;
import android.app.Activity;
import android.content.ContentValues;
import android.content.res.Configuration;
import android.hardware.Camera;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.provider.MediaStore.Images.Media;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;  
public class TimerSnapShot extends Activity implements OnClickListener,
    SurfaceHolder.Callback, Camera.PictureCallback {

    SurfaceView cameraView;
    SurfaceHolder surfaceHolder;
    Camera camera;
這個 activity 非常類似我們的 SnapShot activity。我們打算新增一個 Button 來觸發的倒計時, 和一個 TextView 來顯示倒計時。
    Button startButton;
    TextView countdownTextView;
我們還需要一個 Handler,本例中名為 timerUpdateHandler,一個布林量(timerRunning),幫助我們記錄是否啟動了計時器,還有一個整數(currentTime),記錄倒計時讀數。
    Handler timerUpdateHandler;
    boolean timerRunning = false;
    int currentTime = 10;
 
    @Override
    public void onCreate(Bundle savedInstanceState)
    { 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        cameraView = (SurfaceView)this.findViewById(R.id.CameraView);
        surfaceHolder = cameraView.getHolder();
        surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 
        surfaceHolder.addCallback(this); 
下一步,我們將取得新UI元素(在佈局XML中定義)的引用,並使我們的 activity 成為 Button 的 OnClickListener。我們可以這樣做,是因為我們的 activity 實現了 OnClickListener。
        countdownTextView = (TextView) findViewById(R.id.CountDownTextView);
        startButton = (Button) findViewById(R.id.CountDownButton); 
        startButton.setOnClickListener(this);
最後,在我們onCreate方法中, 要做的是例項化Handler物件。
        timerUpdateHandler = new Handler();
    }
我們的onClick方法在按下startButton按鈕時被呼叫。我們會檢查timerRunning,看定時器例程是否已經執行,如果沒有,我們通過Handler物件timerUpdateHandler,非延遲呼叫 Runnable timerUpdateTask。
    public void onClick(View v)
    {
        if (!timerRunning)
        {
            timerRunning = true;
            timerUpdateHandler.post(timerUpdateTask);
        }
    }
這是我們的 Runnable 物件 timerUpdateTask。它包含run方法,由我們的timerUpdateHandler物件觸發。
    private Runnable timerUpdateTask = new Runnable()
    {
        public void run()
        {
如果記錄倒計時計數的整數currentTime大於1,則遞減之,並讓Handler在1秒後再度呼叫本Runnable。
            if (currentTime > 1)
            {
                currentTime--;
                timerUpdateHandler.postDelayed(timerUpdateTask, 1000); 
            }
            else
            {
如果currentTime不大於1,我們將讓相機進行拍照並重置所有的記錄變數。
                camera.takePicture(null,null ,TimerSnapShot.this);
                timerRunning = false;
                currentTime = 10;
             }
不管結果如何,我們將更新 TextView 來顯示當前的剩餘時間。
            countdownTextView.setText(""+currentTime);
         }
    };
本 activity 的其餘部分,與前述的SnapShot示例基本一樣。
    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h)
    {
        camera.startPreview();
    }

    public void surfaceCreated(SurfaceHolder holder)
    {
	camera = Camera.open();
        try {
            camera.setPreviewDisplay(holder);
            Camera.Parameters parameters =  camera.getParameters(); 
 
            if (this.getResources().getConfiguration().orientation 
                !=  Configuration.ORIENTATION_LANDSCAPE)
            {
                parameters.set("orientation", "portrait");

                // Android 2.2 及以上版本
                camera.setDisplayOrientation(90); 

                // Android 2.0 及以上版本
                parameters.setRotation(90);
             }
  
             camera.setParameters(parameters);
         }
         catch (IOException exception)
         {
             camera.release();
         } 
    }  

    public void surfaceDestroyed(SurfaceHolder holder)
    {
        camera.stopPreview();
        camera.release();
    }

    public void onPictureTaken(byte[] data, Camera camera)
    { 
        Uri imageFileUri =  getContentResolver()
             .insert(Media.EXTERNAL_CONTENT_URI, new ContentValues()); 

        try 
        {
            OutputStream imageFileOS =  getContentResolver()
                .openOutputStream(imageFileUri);
            imageFileOS.write(data);
            imageFileOS.flush();
            imageFileOS.close();

            Toast t = Toast.makeText(this,"Saved JPEG!",Toast.LENGTH_SHORT);
            t.show();
        } 
        catch (FileNotFoundException e) 
        {
            Toast t = Toast.makeText(this,e.getMessage(), Toast.LENGTH_SHORT);
            t.show();
        }
        catch (IOException e) 
        { 
            Toast t = Toast.makeText(this,e.getMessage(),Toast.LENGTH_SHORT);
            t.show(); 
       }

        camera.startPreview(); 
    }
}
XML 佈局有點不同。在此應用程式中,我們用於顯示相機預覽的 SurfaceView 包含在一個FrameLayout中,與之並列的還有 LinearLayout,其包含了用於顯示倒計時計數的 TextView 和 觸發倒計時的 Button。FrameLayout 讓所有子項以左上角對齊,彼此之間頂部對齊。這樣 TextView 和 Button 出現在相機預覽頂部。 
<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:orientation="vertical" 
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"  > 
 
    <FrameLayout android:id="@+id/FrameLayout01" 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

    <SurfaceView android:id="@+id/CameraView" 
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
    </SurfaceView> 

    <LinearLayout android:id="@+id/LinearLayout01" 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"> 
      
        <TextView android:id="@+id/CountDownTextView" 
            android:text="10"
            android:textSize="100dip" 
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical|center_horizontal|center">
        </TextView>  
        <Button android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/CountDownButton"
            android:text="Start Timer">
        </Button>      </LinearLayout>
    </FrameLayout> 
</LinearLayout>
最後,我們需要確保我們的 AndroidManifest.xml 檔案包含Camera許可權。 
<uses-permission android:name="android.permission.CAMERA">
</uses-permission> 


圖 2-5. 帶倒計時相機

建立一個定時攝影應用程式

我們都看到過漂亮的定時攝影例子。就是在一段時間內,拍攝多張照片,每次間隔相同的時間。可以是每分鐘一張,每小時一張,甚至每星期一張。通過一系列定時拍攝的照片,我們可以看到事物隨時間的變化,比如觀察正在建造的建築物,記錄一朵花如何生長和開放。
現在,我們已建立一個基於計時器的相機應用程式,將它升級為一個定時程式是相當簡單。首先我們會更改了一些例項變數和新增一個常量。
...
public class TimelapseSnapShot extends Activity implements OnClickListener,
  SurfaceHolder.Callback, Camera.PictureCallback { 
    SurfaceView cameraView;
    SurfaceHolder surfaceHolder;
    Camera camera; 
我們把Button重新命名為startStopButton,因為它現在會處理兩個操作。另外對其他變數的名字也做些小的修改。
    Button startStopButton;
    TextView countdownTextView; 
    Handler timerUpdateHandler;
    boolean timelapseRunning = false;
整數currentTime將以秒為單位,記錄照片的時間間隔, 而不是從總延時往下遞減,如在前面的例子中那樣。常數 SECONDS_BETWEEN_PHOTOS 設定為 60。如同它的名字所暗示,這將用於確定照片之間的等待時間。
    int currentTime = 0;
    public static final int SECONDS_BETWEEN_PHOTOS = 60;  // 一分鐘
onCreate方法大部分保持不變 - 只是使用新的變數名。
    @Override  
    public void onCreate(Bundle savedInstanceState)
    {          super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        cameraView = (SurfaceView) this.findViewById(R.id.CameraView);

        surfaceHolder = cameraView.getHolder();
        surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        surfaceHolder.addCallback(this);

        countdownTextView = (TextView)findViewById(R.id.CountDownTextView);
        startStopButton = (Button) findViewById(R.id.CountDownButton);
        startStopButton.setOnClickListener(this);
        timerUpdateHandler = new Handler(); 
    }
從基於計時器的應用程式,變為一個定時器應用程式,大部分變化來自 onClick 方法 和  Runnable 方法。前者在按鈕被按下時觸發,後者由Handler進行排程。onClick 方法首先檢查定時程序是否已經開始(Button 已經按過),如果沒有,它將其設定為執行態,並以 Runnable 為引數,呼叫 Handler 的post方法。如果是在定時過程中,按下按鈕意味著停止定時,從而 timerUpdateHandler 的 removeCallbacks 方法被呼叫。這將清除任何掛起的Runnable物件。
public void onClick(View v)
{
    if (!timelapseRunning)
    {
        startStopButton.setText("Stop");
        timelapseRunning = true;
        timerUpdateHandler.post(timerUpdateTask);
     }
     else 
     { 
         startStopButton.setText("Start");
         timelapseRunning = false;
         timerUpdateHandler.removeCallbacks(timerUpdateTask);
      }
}
我們用一個Handler來做排程,當時間到了之後,Handler將呼叫Runnable。在我們Handler的run方法中,我們先檢查整數currentTime是否小於我們照片間隔秒數 (SECONDS_BETWEEN_PHOTOS)。如果是,我們只需增加currentTime。如果currentTime不小於等待週期,我們告訴Camera執行拍照,並將currentTime設定回 0,繼續計數。每次迴圈之後,我們以新currentTime的值,更新TextView顯示,並排程下一次迴圈。
private Runnable timerUpdateTask = new Runnable()
{ 
    public void run()
    { 
        if (currentTime < SECONDS_BETWEEN_PHOTOS) 
        {
             currentTime++;
         }
         else 
         {
             camera.takePicture(null,null,null,TimelapseSnapShot.this);
             currentTime = 0;
         }           timerUpdateHandler.postDelayed(timerUpdateTask, 1000);
         countdownTextView.setText(""+currentTime);
    }
};
本例的res/layout/main.xml 介面,當然還有AndroidManifest.xml 跟單計時器版相同。

摘要

正如你所看到的,有眾多原因我們可能想要建立我們自己的基於相機的應用程式,而不是隻在我們的應用程式中使用內建的Camera應用。沒有什麼能夠限制你能做的,從簡單地建立一個倒計時拍照應用程式,到建立你自己的定時系統,以及更多。繼續前進,我們看看我們能對捕獲的影象做些什麼。