构建自己的Camera应用程序

上一篇文章,我们学习了调用系统内置的Camera应用程序,并对图片做了一些处理,但是没有太多的灵活性。比如:我们希望延迟拍摄,简单的调用系统照相机,则不能实现。

下面我们来探讨如何利用底层的Camera类来构建一个照相应用程序,并学习如何利用所提供的功能。

Camera类的使用

1.首先在配置文件中添加Camera权限

 <uses-permission android:name="android.permission.CAMERA"/>

在开启摄像头之前,我们先获得取景器预览头像Surface。Surface是Android中的一个抽象类,表示绘制图形或图像的位置。绘图Surface主要用到SurfaceView这个类;

2.在布局中引入该控件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <SurfaceView
        android:id="@+id/camera_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

3.在代码中找到 SurfaceView控件,为他添加一个SurfaceHolder类,SurfaceHolder是Surface上的一个监控器,并给我们提供回调接口(接口中方法创建、销毁、更改等),我们可以在接口中处理我们的业务逻辑

<pre name="code" class="java"><span style="white-space:pre">	</span>SurfaceView <span style="font-family: Arial, Helvetica, sans-serif;">surfaceView=(SurfaceView) findViewById(R.id.camera_view);</span>
<span style="white-space:pre">	</span>SurfaceHolder <span style="font-family: Arial, Helvetica, sans-serif;">holder=surfaceView.getHolder();</span>

 
<span style="white-space:pre">	</span>/*
 <span style="white-space:pre">	</span>* 设置Surface是一个推送类型的Surface,意味着在Surface本身的外部维持绘图缓冲区,
 <span style="white-space:pre">	</span>* 该缓冲区由Camera管理,推送类型的surface是camera预览所需的surface
 <span style="white-space:pre">	</span>*/
<span style="white-space:pre">	</span>holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
<span style="white-space:pre">	</span>//实现SurfaceHolder.CallBack接口,从而在surface发生变化时获得通知
<span style="white-space:pre">	</span>holder.addCallback(this);
<span style="white-space:pre">	</span>/**
	 * 实现SurfaceHolder.CallBack接口回调方法
	 */
	@Override
	public void surfaceCreated(SurfaceHolder holder) {}
	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width,
			int height) {}
	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {}

活动和surface已经建立了,下面我们开始使用Camera对象

当活动创建的时候我们应该获得Camera对象,那么我们怎样知道获得已经创建了呢?

上面我们已经为活动添加了一个监听,并实现了回调surfaceCreated()执行时代表活动以创建

@Override
	public void surfaceCreated(SurfaceHolder holder) {
		//活动创建后,获得camera对象
		camera=Camera.open();
<span style="white-space:pre">	</span>}
接下来我们要将预览显示设置为正在使用的 SurfaceHolder 

<span style="white-space:pre">		</span>try {
<span style="white-space:pre">			</span>//给相机设置预览器holder
<span style="white-space:pre">			</span>camera.setPreviewDisplay(holder);
<span style="white-space:pre">		</span>} catch (IOException e) {
<span style="white-space:pre">			</span>//程序出现异常时我们应该释放Camera对象
<span style="white-space:pre">			</span>e.printStackTrace();
<span style="white-space:pre">			</span>camera.release();
<span style="white-space:pre">		</span>}
<span style="white-space:pre">		</span>//最后启动摄像头预览
<span style="white-space:pre">		</span>camera.startPreview();


在Surface销毁的时候我们也需要释放camera对象

@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		camera.stopPreview();
		camera.release();
	}
通过以上代码,我们已经可以预览照相界面,但你会发现预览界面没放正,向左倾斜了90度;产生这种情况的原因是因为Camera假设方向是水平或横向模式,我们需要对他做一个处理,处理方法是将活动窗体设置成横屏

setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

这样虽然能正确显示预览窗体,但是手机只是横屏显示,这样显然满足不了我们的需求。

下面我们来学习一种方法,当我们手机旋转的时候预览窗体依然能正确显示

这里需要用到camera类中的一个嵌套类Camera.Parameters类。这个类有一系列重要的属性和设置,可以用来改变Camera对象的运行的方式,那么这个类中的某些参数来帮助我们解决预览窗体问题,应该在创建Camera对象后设置Parameters(surfaceCreated方法中

主要思路:我们首先获得当前屏幕的方向,然后通过屏幕方向设置Camera.Parameters类的“orientation”值

//首先判断当前屏幕的方向
if(getResources().getConfiguration().orientation==Configuration.ORIENTATION_LANDSCAPE){//横向
	//设置Camera的参数值
	parameters.set("orientation", "landscape");
	//图像应该旋转的角度(有效度数0,90,180,270)
	camera.setDisplayOrientation(0);
	/*
	* 对于2.2以上版本可用,实际上并不执行旋转,它会告知Camera对象在EXIF数据中指定该图像需要旋转的角度,
	 * 如没有设置该属性,在其他应用程序中查看图像时,可能会侧面显示
	*/
	parameters.setRotation(0);
}else if(getResources().getConfiguration().orientation==Configuration.ORIENTATION_PORTRAIT){//纵向
	parameters.set("orientation", "portrait");
	camera.setDisplayOrientation(90);
	parameters.setRotation(90);
}
		
这里我们实现一个照相机界面颜色效果,对应的获取器和设置器的方法是getColorEffect和setColorEffect,同时还存在一个getSupportedColorEffects方法

1.首先通过getSupportedColorEffects方法获取支持的颜色效果

2.然后遍历效果,找到我们需要的颜色效果

3.调用setColorEffect方法设置相机颜色效果

代码如下:

//获取支持的颜色效果
List<String> colorEffects=parameters.getSupportedColorEffects();
//获取集合迭代器
Iterator<String> iterator=colorEffects.iterator();
//遍历集合
while(iterator.hasNext()){
	String colorEffect=iterator.next();
	//找到我们需要的颜色效果
	if(colorEffect.equals(Camera.Parameters.EFFECT_SOLARIZE)){
	//设置颜色效果
		parameters.setColorEffect(colorEffect);
		break;
	}
}
camera.setParameters(parameters);

下面我们要改变摄像头预览的大小

和上面更改相机颜色效果一样

1.首先通过getSupportedPreviewSizes方法获取支持大小,返回Camera.Size对象

2.然后遍历支持大小,找到我们需要的最大值

3.调用setPreviewSize方法设置相机预览大小

4.告知surfaceview预览对象,设置显示大小(此步骤必须做,否则不能改变大小,还会影响预览效果)

<span style="white-space:pre">		</span>private static final int LARGEST_WIDTH=200;//我们定义显示的预览宽度
		private static final int LARGEST_HEIGHT=200;//我们定义显示的预览高度
		int bestWidth=0;
		int bestHeight=0;
		//获取设备支持的所有大小,返回Camera.Size对象
		List<Camera.Size> sizes=parameters.getSupportedPreviewSizes();
		if(sizes.size()>1){
			Iterator<Camera.Size> iterator=sizes.iterator();
			while(iterator.hasNext()){
				Camera.Size size=iterator.next();
				//获取小于我们给定指定最大值为我们应该改变的大小
				if(size.width>bestWidth&&size.width<=LARGEST_WIDTH&&
						size.height>bestHeight&&size.height<=LARGEST_HEIGHT){
					bestWidth=size.width;
					bestHeight=size.height;
				}
			}
			//设置预览显示大小
			if(bestWidth!=0&&bestHeight!=0){
				parameters.setPreviewSize(bestWidth, bestHeight);
				//告知摄像头预览对象surfaceView以该大小进行显示,如果不设置,将不会改变预览大小,预览会扭曲低质量
				surfaceView.setLayoutParams(new LinearLayout.LayoutParams(bestWidth, bestHeight));
			}
		}
		camera.setParameters(parameters);

上面我们对相机的一些参数进行了配置,下面我们就来拍一拍,并获取拍摄的照片

这里我们要用Camera类来获取拍摄的照片,调用它的takePicture方法;该方法接收3||4个参数,所有这些参数都是回调方法,takepicture方法的最简单方式是将所有的参数都设置为null。尽管能够捕获照片,但是不能获得它的引用。因此至少应该实现一种回调方法,一种最安全的回调方法是Camera.PictureCallback.onPictureTaken,它确保被调用,并且在压缩图像时被调用。我们将实现Camera.PictureCallback接口,并重写onPictureTaken方法

关于camera的其他回调方法

1.Camera.PreviewCallBack接口

它的回调方法onPreviewFrame(byte[] data,Camera camera)方法,当存在预览帧时调用该方法,可以传入保存当前图像像素的字节数组

添加回调三种方法:

setPreviewCallback(Camera.PreviewCallback)

setOneShotPreviewCallback(Camera.PreviewCallback)

setPreviewCallbackWithBuffer(Camera.PreviewCallback)

2.Camera.AutoFocusCallback接口

定义了onAutoFocus方法,方完成一个自动聚焦活动是调用它。通过传入此回调接口的一个实例,在调用Camera对象上的autoFocus方法时会触发自动聚焦

3.Camera.ErrorCallback接口

定义了onError方法,当发生一个camera错误时调用它

4.Camera.OnZoomChangeListener

定义了onZoomChange方法,当正在进行或完成平滑缩放时调用它

5.Camera.ShutterCallback

定义了onShutter方法,当捕获图像时立刻调用它



拍摄图片后并保存到图库的完整代码如下

package com.qq.mycamera;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;

import android.app.Activity;
import android.content.ContentValues;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.hardware.Camera;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore.Images.Media;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;
import android.widget.Toast;

public class MainActivity extends Activity implements SurfaceHolder.Callback,Camera.PictureCallback,OnClickListener{

	private SurfaceView surfaceView;
	private ImageView imageView;
	private SurfaceHolder holder;
	private Camera camera;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		surfaceView=(SurfaceView) findViewById(R.id.camera_view);
		holder=surfaceView.getHolder();
		/*
		 * 设置Surface是一个推送类型的Surface,意味着在Surface本身的外部维持绘图缓冲区,
		 * 该缓冲区由Camera管理,推送类型的surface是camera预览所需的surface
		 */
		holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
		//实现SurfaceHolder.CallBack接口,从而在surface发生变化时获得通知
		holder.addCallback(this);
		
		surfaceView.setFocusable(true);//可聚焦,默认情况下surface不可聚焦
		surfaceView.setFocusableInTouchMode(true);//设置为不禁用,触摸模式下,通常会禁用焦点
		surfaceView.setClickable(true);//可单击
		//为预览视图添加监听
		surfaceView.setOnClickListener(this);
		imageView=(ImageView) findViewById(R.id.photoIv);
	}
	/**
	 * 实现SurfaceHolder.CallBack接口回调方法
	 */
	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		//活动创建后,获得camera对象
		camera=Camera.open();
		Camera.Parameters parameters=camera.getParameters();
		//首先判断当前屏幕的方向
		if(getResources().getConfiguration().orientation==Configuration.ORIENTATION_LANDSCAPE){//横向
			//设置Camera的参数值
			parameters.set("orientation", "landscape");
			//图像应该旋转的角度(有效度数0,90,180,270)
			camera.setDisplayOrientation(0);
			/*
			 * 对于2.2以上版本可用,实际上并不执行旋转,它会告知Camera对象在EXIF数据中指定该图像需要旋转的角度,
			 * 如没有设置该属性,在其他应用程序中查看图像时,可能会侧面显示
			 */
			parameters.setRotation(0);
		}else if(getResources().getConfiguration().orientation==Configuration.ORIENTATION_PORTRAIT){//纵向
			parameters.set("orientation", "portrait");
			camera.setDisplayOrientation(90);
			parameters.setRotation(90);
		}
		try {
			//给相机设置预览器holder
			camera.setPreviewDisplay(holder);
		} catch (IOException e) {
			//程序出现异常时我们应该释放Camera对象
			e.printStackTrace();
			camera.release();
		}
		//最后启动摄像头预览
		camera.startPreview();
	}
	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width,
			int height) {
		
	}
	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		camera.stopPreview();
		camera.release();
	}
	/**
	 * 该回调方法第一个参数:实际图像的数据的字节数组
	 * 第二个参数:捕获该图像的Camera对象的引用
	 */
	@Override
	public void onPictureTaken(byte[] data, Camera camera) {
		//获取图片的宽高
		BitmapFactory.Options options=new BitmapFactory.Options();
		options.inSampleSize=8;
		Bitmap bitmap=BitmapFactory.decodeByteArray(data, 0, data.length,options);
		imageView.setImageBitmap(bitmap);
		//将图片保存到mediaStore中
		Uri uri=getContentResolver().insert(Media.EXTERNAL_CONTENT_URI, new ContentValues());
		try {
			//获取图片文件的流
			OutputStream os=getContentResolver().openOutputStream(uri);
			//将数据写到文件中
			os.write(data);
			os.flush();
			os.close();
			Toast.makeText(this,"照片已保存", 0).show();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			Toast.makeText(this, e.getMessage(), 0).show();
		} catch (IOException e) {
			e.printStackTrace();
			Toast.makeText(this, e.getMessage(), 0).show();
		}
		//调用takePicture方法后,调用startPreview方法可以安全的重新启动它
		camera.startPreview();
	}
	@Override
	public void onClick(View v) {
		camera.takePicture(null, null,this);
	}

}

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <SurfaceView
        android:id="@+id/camera_view"
        android:layout_width="match_parent"
        android:layout_height="200dp" />

    <ImageView
        android:id="@+id/photoIv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/ic_launcher" />

</LinearLayout>























评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值