相机开发(聚焦、横竖屏拍照、照片存储、连续拍照等)

一.实现流程

这幅图是从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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值