一.实现流程
这幅图是从API文档(最好是看英文版的)整理出来的,从这副图上面我们可以看出,主要是有6步,其中难点是创建相机预览类。
二.权限声明
这个不讲了,直接加入声明权限代码,不明白的可以网上查查看
1.
<uses-permission android:name=
"android.permission.CAMERA"
/>
2.
<uses-feature android:name=
"android.hardware.camera"
/>
3.
<uses-permission android:name=
"android.permission.WRITE_EXTERNAL_STORAGE"
/>
4.
<uses-permission android:name=
"android.permission.ACCESS_FINE_LOCATION"
/>
5.
<uses-feature android:name=
"android.hardware.camera.autofocus"
/>
6.
<uses-permission android:name=
"android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
/>
三 检查相机和获取相机实例
新建CameraCheck类,主要有2个方法,代码如下
01.
public
class
CameraCheck {
02.
03.
public
static
boolean
CheckCamera(Context mContext) {
04.
if
(mContext.getPackageManager().hasSystemFeature(
05.
PackageManager.FEATURE_CAMERA)) {
06.
return
true
;
07.
}
else
{
08.
Toast.makeText(mContext,
"相机不存在!"
, Toast.LENGTH_SHORT).show();
09.
return
false
;
10.
}
11.
}
12.
13.
/** A safe way to get an instance of the Camera object. */
14.
public
static
Camera getCameraInstance(Context mContext) {
15.
Camera c =
null
;
16.
if
(CheckCamera(mContext)) {
17.
try
{
18.
c = Camera.open();
19.
}
catch
(Exception e) {
20.
c=
null
;
21.
}
22.
}
23.
return
c;
// returns null if camera is unavailable
24.
}
25.
}
第一个方法用来检查相机是否存在,这个方法是来自API文档,使用方法
mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)
hasSystemFeature(String name)方法返回设备是否支持name功能的真假值;
通过方法getCameraInstance返回相机的实例,通过调用该方法,mContext能够获得该相机资源,只有获得了该相机资源才能够对相机进行操作。
四.创建相机预览类(重点)
我们在拍照之前需要对取景进行预览,这里我们需要使用SurfaceView控件,关于SurfaceView控件我们先简单的了解一下(别急,磨刀不误砍柴工)。
SurfaceView是View的子类,所以它拥有View的一切方法和属性,这一点我们从命名上面就可以看出来,比如绘制方法、大小等属性;它比View多了一个Surface的东西,Surface是专门用来绘制的类,而SurfaceView可以控制surface绘制的大小、位置等等;
可能有人会问,那为什么要专门这样一个类来绘制呢?不是有OnDraw()方法吗?相比于OnDraw()方法它有很多优势,如下总结:
(1)在频繁更新UI线程的情况下,可以使用封装好的surface来频繁的更新,因为surface可以使用后台线程对UI界面进行绘制,而OnDraw()等绘制方法很难做到(除非你频繁的调用handler来更新主界面,这得多麻烦啊!);
(2)SurfaceView可以用来绘制2D或者3D图形,绘制一些动态曲线等,它显示的速度会比一般的快很多,因为他是通过硬件加速的方式来绘制的。
(3)它可以用来接受硬件的数据来绘制图像。
所以,通过以上几点我们可以知道,用它来接受相机的预览是理所当然的。那么它的使用方法是怎么样的呢?下创建一个surfaceView的继承类一般需要实现如下几个方法:
(1)surfaceCreated(SurfaceHolderholder):在该类创建的时候调用,这里一般需要实现一些初始化的工作,SurfaceHodler用来设定surface的大小位置等等;
(2)surfaceChanged(SurfaceHolderholder, int format, int width,int height)在surface大小发生改变时候调用,这里实现图形的绘制;
(3)surfaceDestroyed(SurfaceHolderholder)在surface销毁时候调用,这里一般对资源进行释放;
(4)实现SurfaceHodler.CallBack回调方法,在surfaceView创建完成后自动调用类本身;
在实现之前我们先来看我们的需求,我们要实现的功能:预览、拍照、自动聚焦、触摸聚焦、连续拍照、照片存储。下面我们来创建一个SurfaceView类CameraPreview,它继承了SurfaceView,并实现接口SurfaceHolder.Callback
因此我们需要在surfaceCreated方法中创建一个camer实例,这个实例可以在这个类中进行调用,实现代码如下:
01.
/**
02.
* 创建的时候自动调用该方法
03.
*/
04.
@Override
05.
public
void
surfaceCreated(SurfaceHolder holder) {
06.
if
(mCamera ==
null
) {
07.
mCamera = CameraCheck.getCameraInstance(mContext);
08.
}
09.
try
{
10.
if
(mCamera!=
null
){
11.
mCamera.setPreviewDisplay(holder);
12.
}
13.
}
catch
(IOException e) {
14.
if
(
null
!= mCamera) {
15.
mCamera.release();
16.
mCamera =
null
;
17.
isPreview=
false
;
18.
}
19.
e.printStackTrace();
20.
}
21.
22.
}
这句代码 mCamera.setPreviewDisplay(holder)的意思是创建一个预览的hodler;我们在surfaceChanged中进行预览窗口的绘制调用的是startPreview()方法来开始绘制,代码如下:
01.
/**
02.
* 当surface的大小发生改变的时候自动调用的
03.
*/
04.
@Override
05.
public
void
surfaceChanged(SurfaceHolder holder,
int
format,
int
width,
06.
int
height) {
07.
if
(mHolder.getSurface() ==
null
) {
08.
return
;
09.
}
10.
try
{
11.
setCameraParms();
12.
mCamera.setPreviewDisplay(holder);
13.
mCamera.startPreview();
14.
reAutoFocus();
15.
}
catch
(Exception e) {
16.
Log.d(TAG,
"Error starting camera preview: "
+ e.getMessage());
17.
}
18.
}
其中有2个比较关键的方法没有实现, setCameraParms()和reAutoFocus(),setCameraParms();函数用来设置预览图片的参数,其中关键的为预览图片的大小和拍照保存的尺寸大小,很多的网上实现的程序拍出来的照片很小模糊的原因就是没有设置好照片的尺寸,这个照片的尺寸是根据手机本身能够支持的尺寸有很大关系。reAutoFocus()是自动聚焦的方法,需要动态获取reAutoFocus()函数是自动聚焦的实现;
我们首先来看一下setCameraParms()方法的实现:
01.
private
void
setCameraParms(){
02.
Camera.Parameters myParam = mCamera.getParameters();
03.
List<Camera.Size> mSupportedsizeList =myParam.getSupportedPictureSizes();
04.
if
(mSupportedsizeList.size() >
1
) {
05.
Iterator<Camera.Size> itos = mSupportedsizeList.iterator();
06.
while
(itos.hasNext()){
07.
Camera.Size curSize = itos.next();
08.
int
curSupporSize=curSize.width * curSize.height;
09.
int
fixPictrueSize= setFixPictureWidth * setFixPictureHeight;
10.
if
( curSupporSize>fixPictrueSize && curSupporSize <= maxPictureSize) {
11.
setFixPictureWidth = curSize.width;
12.
setFixPictureHeight = curSize.height;
13.
}
14.
}
15.
}
16.
myParam.setJpegQuality(
100
);
17.
mCamera.setParameters(myParam);
18.
if
(myParam.getMaxNumDetectedFaces() >
0
){
19.
mCamera.startFaceDetection();
20.
}
21.
}
22.
通过myParam.getSupportedPictureSizes();获取到手机支持的所有尺寸的枚举,并设置最大的固定尺寸这里设置最大为maxPictureSize = 5000000
reAutoFocus()的实现为:
01.
<span style=
"white-space:pre"
> </span>
/**
02.
* Call the camera to Auto Focus
03.
*/
04.
public
void
reAutoFocus() {
05.
if
(isSupportAutoFocus) {
06.
mCamera.autoFocus(
new
AutoFocusCallback() {
07.
@Override
08.
public
void
onAutoFocus(
boolean
success, Camera camera) {
09.
}
10.
});
11.
}
12.
}
使用回调函数autoFocus来实现自动聚焦
五.拍照
拍照方法有一个难点是横竖屏拍照的转换和存储,网上大都实现的是默认的横屏拍照,一旦换成竖屏后预览就会出现问题,而且存储的照片也有问题,因此为了解决这个问题,我们需要时刻监听方向传感器的变化,得到当前的旋转角度,我么可以通过调用OrientationEventListener系统监听类来得到当前角度,自定义MyOrientationDetector代码如下:
01.
/**
02.
* 方向变化监听器,监听传感器方向的改变
03.
* @author zw.yan
04.
*
05.
*/
06.
public
class
MyOrientationDetector
extends
OrientationEventListener{
07.
int
Orientation;
08.
public
MyOrientationDetector(Context context ) {
09.
super
(context );
10.
}
11.
@Override
12.
public
void
onOrientationChanged(
int
orientation) {
13.
Log.i(
"MyOrientationDetector "
,
"onOrientationChanged:"
+orientation);
14.
this
.Orientation=orientation;
15.
Log.d(
"MyOrientationDetector"
,
"当前的传感器方向为"
+orientation);
16.
}
17.
18.
public
int
getOrientation(){
19.
return
Orientation;
20.
}
21.
}
在预览类中我们定义拍照方法TakePhone(),代码如下:
01.
/**
02.
* 调整照相的方向,设置拍照相片的方向
03.
*/
04.
private
void
takePhoto() {
05.
cameraOrientation =
new
MyOrientationDetector(mContext);
06.
if
(mCamera !=
null
) {
07.
int
orientation = cameraOrientation.getOrientation();
08.
Camera.Parameters cameraParameter = mCamera.getParameters();
09.
cameraParameter.setRotation(
90
);
10.
cameraParameter.set(
"rotation"
,
90
);
11.
if
((orientation >=
45
) && (orientation <
135
)) {
12.
cameraParameter.setRotation(
180
);
13.
cameraParameter.set(
"rotation"
,
180
);
14.
}
15.
if
((orientation >=
135
) && (orientation <
225
)) {
16.
cameraParameter.setRotation(
270
);
17.
cameraParameter.set(
"rotation"
,
270
);
18.
}
19.
if
((orientation >=
225
) && (orientation <
315
)) {
20.
cameraParameter.setRotation(
0
);
21.
cameraParameter.set(
"rotation"
,
0
);
22.
}
23.
mCamera.setParameters(cameraParameter);
24.
mCamera.takePicture(shutterCallback, pictureCallback, mPicture);
25.
}
26.
}
在角度范围内自动调整旋转图片的角度,具体旋转的方式如代码,从而使存储的图片能够正常显示。
六.图片保存
在拍照时需要对图片进行保存,但是不能影响图片的下一次拍照,因此我们需要采用异步线程的方式,可以使用AsyncTask类,在拍照完成时进行调用如下代码:
01.
public
class
SavePictureTask
extends
AsyncTask<
byte
[], String, String> {
02.
@SuppressLint
(
"SimpleDateFormat"
)
03.
@Override
04.
protected
String doInBackground(
byte
[]... params) {
05.
File pictureFile = FileUtil.getOutputMediaFile(MEDIA_TYPE_IMAGE,
06.
mContext);
07.
if
(pictureFile ==
null
) {
08.
Toast.makeText(mContext,
"请插入存储卡!"
, Toast.LENGTH_SHORT).show();
09.
return
null
;
10.
}
11.
try
{
12.
FileOutputStream fos =
new
FileOutputStream(pictureFile);
13.
fos.write(params[
0
]);
14.
fos.flush();
15.
fos.close();
16.
}
catch
(FileNotFoundException e) {
17.
Log.d(TAG,
"File not found: "
+ e.getMessage());
18.
}
catch
(IOException e) {
19.
Log.d(TAG,
"Error accessing file: "
+ e.getMessage());
20.
}
21.
22.
return
null
;
23.
}
24.
}
这是基本对文件异步线程的IO操作有什么不明白的可以去看对应的API文档。
下面我将整个类贴出来:
001.
/**
002.
* sufaceView 的预览类,其中SurfaceHolder.CallBack用来监听Surface的变化,
003.
* 当Surface发生改变的时候自动调用该回调方法
004.
* 通过调用方SurfaceHolder.addCallBack来绑定该方法
005.
* @author zw.yan
006.
*
007.
*/
008.
public
class
CameraPreview
extends
SurfaceView
implements
009.
SurfaceHolder.Callback {
010.
011.
private
String TAG =
"CameraPreview"
;
012.
/**
013.
* Surface的控制器,用来控制预览等操作
014.
*/
015.
private
SurfaceHolder mHolder;
016.
/**
017.
* 相机实例
018.
*/
019.
private
Camera mCamera =
null
;
020.
/**
021.
* 图片处理
022.
*/
023.
public
static
final
int
MEDIA_TYPE_IMAGE =
1
;
024.
/**
025.
* 预览状态标志
026.
*/
027.
private
boolean
isPreview =
false
;
028.
/**
029.
* 设置一个固定的最大尺寸
030.
*/
031.
private
int
maxPictureSize =
5000000
;
032.
/**
033.
* 是否支持自动聚焦,默认不支持
034.
*/
035.
private
Boolean isSupportAutoFocus =
false
;
036.
/**
037.
* 获取当前的context
038.
*/
039.
private
Context mContext;
040.
/**
041.
* 当前传感器的方向,当方向发生改变的时候能够自动从传感器管理类接受通知的辅助类
042.
*/
043.
MyOrientationDetector cameraOrientation;
044.
/**
045.
* 设置最适合当前手机的图片宽度
046.
*/
047.
int
setFixPictureWidth =
0
;
048.
/**
049.
* 设置当前最适合的图片高度
050.
*/
051.
int
setFixPictureHeight =
0
;
052.
053.
@SuppressWarnings
(
"deprecation"
)
054.
public
CameraPreview(Context context) {
055.
super
(context);
056.
this
.mContext = context;
057.
isSupportAutoFocus = context.getPackageManager().hasSystemFeature(
058.
PackageManager.FEATURE_CAMERA_AUTOFOCUS);
059.
mHolder = getHolder();
060.
//兼容android 3.0以下的API,如果超过3.0则不需要设置该方法
061.
if
(Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB){
062.
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
063.
}
064.
mHolder.addCallback(
this
);
//绑定当前的回调方法
065.
}
066.
067.
/**
068.
* 创建的时候自动调用该方法
069.
*/
070.
@Override
071.
public
void
surfaceCreated(SurfaceHolder holder) {
072.
if
(mCamera ==
null
) {
073.
mCamera = CameraCheck.getCameraInstance(mContext);
074.
}
075.
try
{
076.
if
(mCamera!=
null
){
077.
mCamera.setPreviewDisplay(holder);
078.
}
079.
}
catch
(IOException e) {
080.
if
(
null
!= mCamera) {
081.
mCamera.release();
082.
mCamera =
null
;
083.
isPreview=
false
;
084.
}
085.
e.printStackTrace();
086.
}
087.
088.
}
089.
/**
090.
* 当surface的大小发生改变的时候自动调用的
091.
*/
092.
@Override
093.
public
void
surfaceChanged(SurfaceHolder holder,
int
format,
int
width,
094.
int
height) {
095.
if
(mHolder.getSurface() ==
null
) {
096.
return
;
097.
}
098.
try
{
099.
setCameraParms();
100.
mCamera.setPreviewDisplay(holder);
101.
mCamera.startPreview();
102.
reAutoFocus();
103.
}
catch
(Exception e) {
104.
Log.d(TAG,
"Error starting camera preview: "
+ e.getMessage());
105.
}
106.
}
107.
108.
private
void
setCameraParms(){
109.
Camera.Parameters myParam = mCamera.getParameters();
110.
List<Camera.Size> mSupportedsizeList =myParam.getSupportedPictureSizes();
111.
if
(mSupportedsizeList.size() >
1
) {
112.
Iterator<Camera.Size> itos = mSupportedsizeList.iterator();
113.
while
(itos.hasNext()){
114.
Camera.Size curSize = itos.next();
115.
int
curSupporSize=curSize.width * curSize.height;
116.
int
fixPictrueSize= setFixPictureWidth * setFixPictureHeight;
117.
if
( curSupporSize>fixPictrueSize && curSupporSize <= maxPictureSize) {
118.
setFixPictureWidth = curSize.width;
119.
setFixPictureHeight = curSize.height;
120.
}
121.
}
122.
}
123.
myParam.setJpegQuality(
100
);
124.
mCamera.setParameters(myParam);
125.
if
(myParam.getMaxNumDetectedFaces() >
0
){
126.
mCamera.startFaceDetection();
127.
}
128.
}
129.
130.
@Override
131.
public
void
surfaceDestroyed(SurfaceHolder holder) {
132.
mCamera.stopPreview();
133.
mCamera.release();
134.
mCamera =
null
;
135.
}
136.
137.
/**
138.
* Call the camera to Auto Focus
139.
*/
140.
public
void
reAutoFocus() {
141.
if
(isSupportAutoFocus) {
142.
mCamera.autoFocus(
new
AutoFocusCallback() {
143.
@Override
144.
public
void
onAutoFocus(
boolean
success, Camera camera) {
145.
}
146.
});
147.
}
148.
}
149.
/**
150.
* 自动聚焦,然后拍照
151.
*/
152.
public
void
takePicture() {
153.
if
(mCamera !=
null
) {
154.
mCamera.autoFocus(autoFocusCallback);
155.
}
156.
}
157.
158.
private
AutoFocusCallback autoFocusCallback =
new
AutoFocusCallback() {
159.
160.
public
void
onAutoFocus(
boolean
success, Camera camera) {
161.
// TODO Auto-generated method stub
162.
163.
if
(success) {
164.
Log.i(TAG,
"autoFocusCallback: success..."
);
165.
takePhoto();
166.
}
else
{
167.
Log.i(TAG,
"autoFocusCallback: fail..."
);
168.
if
(isSupportAutoFocus) {
169.
takePhoto();
170.
}
171.
}
172.
}
173.
};
174.
/**
175.
* 调整照相的方向,设置拍照相片的方向
176.
*/
177.
private
void
takePhoto() {
178.
cameraOrientation =
new
MyOrientationDetector(mContext);
179.
if
(mCamera !=
null
) {
180.
int
orientation = cameraOrientation.getOrientation();
181.
Camera.Parameters cameraParameter = mCamera.getParameters();
182.
cameraParameter.setRotation(
90
);
183.
cameraParameter.set(
"rotation"
,
90
);
184.
if
((orientation >=
45
) && (orientation <
135
)) {
185.
cameraParameter.setRotation(
180
);
186.
cameraParameter.set(
"rotation"
,
180
);
187.
}
188.
if
((orientation >=
135
) && (orientation <
225
)) {
189.
cameraParameter.setRotation(
270
);
190.
cameraParameter.set(
"rotation"
,
270
);
191.
}
192.
if
((orientation >=
225
) && (orientation <
315
)) {
193.
cameraParameter.setRotation(
0
);
194.
cameraParameter.set(
"rotation"
,
0
);
195.
}
196.
mCamera.setParameters(cameraParameter);
197.
mCamera.takePicture(shutterCallback, pictureCallback, mPicture);
198.
}
199.
}
200.
201.
private
ShutterCallback shutterCallback =
new
ShutterCallback() {
202.
@Override
203.
public
void
onShutter() {
204.
// TODO Auto-generated method stub
205.
}
206.
};
207.
208.
private
PictureCallback pictureCallback =
new
PictureCallback() {
209.
210.
@Override
211.
public
void
onPictureTaken(
byte
[] arg0, Camera arg1) {
212.
// TODO Auto-generated method stub
213.
214.
}
215.
};
216.
private
PictureCallback mPicture =
new
PictureCallback() {
217.
218.
@Override
219.
public
void
onPictureTaken(
byte
[] data, Camera camera) {
220.
new
SavePictureTask().execute(data);
221.
mCamera.startPreview();
//重新开始预览
222.
}
223.
};
224.
225.
public
class
SavePictureTask
extends
AsyncTask<
byte
[], String, String> {
226.
@SuppressLint
(
"SimpleDateFormat"
)
227.
@Override
228.
protected
String doInBackground(
byte
[]... params) {
229.
File pictureFile = FileUtil.getOutputMediaFile(MEDIA_TYPE_IMAGE,
230.
mContext);
231.
if
(pictureFile ==
null
) {
232.
Toast.makeText(mContext,
"请插入存储卡!"
, Toast.LENGTH_SHORT).show();
233.
return
null
;
234.
}
235.
try
{
236.
FileOutputStream fos =
new
FileOutputStream(pictureFile);
237.
fos.write(params[
0
]);
238.
fos.flush();
239.
fos.close();
240.
}
catch
(FileNotFoundException e) {
241.
Log.d(TAG,
"File not found: "
+ e.getMessage());
242.
}
catch
(IOException e) {
243.
Log.d(TAG,
"Error accessing file: "
+ e.getMessage());
244.
}
245.
246.
return
null
;
247.
}
248.
}
249.
@Override
250.
public
boolean
onTouchEvent(MotionEvent event) {
251.
reAutoFocus();
252.
return
false
;
253.
}
254.
255.
256.
}
文件的布局和调用如下:
01.
public
class
CameraActivity
extends
Activity{
02.
03.
private
CameraPreview mPreview;
04.
public
static
final
int
MEDIA_TYPE_IMAGE =
1
;
05.
public
static
final
int
MEDIA_TYPE_VIDEO =
2
;
06.
private
String TAG=
"CameraActivity"
;
07.
private
FrameLayout preview;
08.
private
ImageButton captureButton;
09.
@Override
10.
protected
void
onCreate(Bundle savedInstanceState) {
11.
// TODO Auto-generated method stub
12.
super
.onCreate(savedInstanceState);
13.
requestWindowFeature(Window.FEATURE_NO_TITLE);
14.
this
.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
15.
setContentView(R.layout.camera_preview);
16.
mPreview =
new
CameraPreview(
this
);
17.
preview = (FrameLayout) findViewById(R.id.camera_preview);
18.
preview.addView(mPreview);
19.
captureButton = (ImageButton) findViewById(R.id.button_capture);
20.
captureButton.setOnClickListener(
new
View.OnClickListener() {
21.
@Override
22.
public
void
onClick(View v) {
23.
mPreview.takePicture();
24.
}
25.
26.
});
27.
}
28.
29.
@Override
30.
protected
void
onDestroy() {
31.
// TODO Auto-generated method stub
32.
super
.onDestroy();
33.
}
34.
35.
36.
37.
}
01.
<?xml version=
"1.0"
encoding=
"utf-8"
?>
02.
<LinearLayout xmlns:android=
"http://schemas.android.com/apk/res/android"
03.
android:orientation=
"horizontal"
04.
android:layout_width=
"match_parent"
05.
android:layout_height=
"match_parent"
06.
android:background=
"#000"
>
07.
<FrameLayout
08.
android:id=
"@+id/camera_preview"
09.
android:layout_width=
"match_parent"
10.
android:layout_height=
"match_parent"
11.
android:layout_weight=
"1"
12.
/>
13.
14.
<ImageButton
15.
android:id=
"@+id/button_capture"
16.
android:layout_width=
"wrap_content"
17.
android:layout_height=
"wrap_content"
18.
android:layout_gravity=
"center"
19.
android:src=
"@drawable/camera_icon"
20.
android:background=
"#00000000"
21.
/>
22.
</LinearLayout>
代码下载地址:http://www.it165.net/uploadfile/files/2014/0825/CameraLibary.rar