Android多媒體開發 Pro Android Media 第二章 建立自定義相機應用 3
阿新 • • 發佈:2019-02-05
擴充套件自定義相機應用程式
在我看來,Android 上的內建相機應用程式缺少幾個基本特徵。其中之一是,延遲一小段時間,10或者30秒,之後進行拍攝。此種功能對於那些可以安裝在三腳架上的相機來說,通常很實用。它提供了這樣的功能,攝影師設定好鏡頭,設定好計時器,然後自己跑到鏡頭裡。雖然對於行動電話而言,可能不是很常用。但在某些特殊場景,卻非常有用的。例如,當我想要和同伴一起拍照時,就非常喜歡這個功能。目前當我嘗試這樣做時,因為反對著螢幕,看不見觸屏介面,拍照就變得非常麻煩。在螢幕裡到處摸索亂按,希望能碰巧按下快門按鈕。
建立一個基於計時器的相機應用程式
為了扭轉剛才所述的情況,我們可以為拍攝增加一個延遲時間。讓我們更新我們的SnapShot示例,拍攝動作在按下按鍵10秒後進行。為了實現這個目標,我們需要使用某些類似 java.util.Timer 的東西。不幸的是,在 android 系統,使用計時器比較複雜,它會引入單獨的執行緒。而單獨執行緒要與UI進行互動,需要通過Handler,才能讓主執行緒執行某一動作。若要建立一個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秒,就呼叫某個方法。以這種方式,我們可以為使用者在螢幕上提供倒計時。
這個 activity 非常類似我們的 SnapShot activity。我們打算新增一個 Button 來觸發的倒計時, 和一個 TextView 來顯示倒計時。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;
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 跟單計時器版相同。