扩展自定义相机应用程序

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



更多相关文章

  1. Android多媒体开发 Pro Android(安卓)Media 第二章 创建自定义相
  2. Android进阶——阿里Android开发手册学习笔记(一)
  3. Android(安卓)判断当前介面是否是在桌面
  4. AIDL --- Android中的远程接口[转]
  5. 起来越像Android了?iOS 14从Android(安卓)中“窃取“ 了这8个有用
  6. Android菜鸟的成长笔记(4)——你真的理解了吗?
  7. Android研究之英特尔 Android* 开发人员指南上的对等应用详解
  8. android中基于网络和GPS的不同精度定位
  9. 一个现有Android工程作为组件加入到另一个Android工程最简便方法

随机推荐

  1. android edittext 输入字数限制 超过最大
  2. Android Studio gradle Apk命名
  3. ANDROID中根据QQ号码或者QQ群号码,跳转到
  4. Android(Java):fragment
  5. Android(安卓)彩信发送
  6. Android OTA差分包升级失败
  7. 学习和研究Android
  8. Android开发人员面试整理
  9. 原创:Android 基础 控件 之 TextVIew(一)
  10. Android BaseAdapter 例子