上一篇文章,我们学习了调用系统内置的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接口回调方法 */@Overridepublic void surfaceCreated(SurfaceHolder holder) {}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width,int height) {}@Overridepublic 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();
@Override通过以上代码,我们已经可以预览照相界面,但你会发现预览界面没放正,向左倾斜了90度;产生这种情况的原因是因为Camera假设方向是水平或横向模式,我们需要对他做一个处理,处理方法是将活动窗体设置成横屏
public void surfaceDestroyed(SurfaceHolder holder) {
camera.stopPreview();
camera.release();
}
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
这样虽然能正确显示预览窗体,但是手机只是横屏显示,这样显然满足不了我们的需求。
下面我们来学习一种方法,当我们手机旋转的时候预览窗体依然能正确显示
这里需要用到camera类中的一个嵌套类Camera.Parameters类。这个类有一系列重要的属性和设置,可以用来改变Camera对象的运行的方式,那么这个类中的某些参数来帮助我们解决预览窗体问题,应该在创建Camera对象后设置Parameters(surfaceCreated方法中)
主要思路:我们首先获得当前屏幕的方向,然后通过屏幕方向设置Camera.Parameters类的“orientation”值
//首先判断当前屏幕的方向这里我们实现一个照相机界面颜色效果,对应的获取器和设置器的方法是getColorEffect和setColorEffect,同时还存在一个getSupportedColorEffects方法
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);
}
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>