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> 

Android多媒体开发 Pro Android Media 第二章 创建自定义相机应用 3

图 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应用。没有什么能够限制你能做的,从简单地创建一个倒计时拍照应用程序,到建立你自己的定时系统,以及更多。继续前进,我们看看我们能对捕获的图像做些什么。



Android多媒体开发 Pro Android Media 第二章 创建自定义相机应用 3,布布扣,bubuko.com

Android多媒体开发 Pro Android Media 第二章 创建自定义相机应用 3

上一篇:android DPI与分辨率的关系及计算方式


下一篇:[Android] android核心分析