楼主在进行android开发时用到了自定义摄像头拍照并将所拍摄的照片转化成二进制流输出这一功能(当然程序里也附带将图片存储在sd卡里的功能),花了好多天的时间查了很多资料最后终于把它给搞出来了。。。
来~~~直接上图~~~
首先先搞出界面布局来
布局定制出来是这样的:
这里的布局其实挺简单的,只有一个Framelayout和一个Button按钮
Framelayout用于显示相机,Button按钮添加OnClickListene用于获取图像。
然后我们再看活动是如何创建的:
上代码:
package myfinalcamera.caicai.com.myfinalcamera; import android.hardware.Camera; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.FrameLayout; import android.widget.Toast; public class MyCamera extends AppCompatActivity { public static final int MEDIA_TYPE_IMAGE=1; public static final int MEDIA_TYPE_VIDEO=2; private Camera mCamera; private CameraPreview mPreview; private FrameLayout preview; private Button captureButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_camera); mCamera=getCameraInstance.getCamera(); mPreview=new CameraPreview(this,mCamera); preview =(FrameLayout) findViewById(R.id.camera_preview); preview.addView(mPreview); captureButton=(Button) findViewById(R.id.button_capture); captureButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Save save=new Save(); try { mCamera.takePicture(save.shutter,save.raw,save.jpeg); Toast.makeText(MyCamera.this,"拍照成功",Toast.LENGTH_SHORT).show(); } catch (Exception e) { e.printStackTrace(); Toast.makeText(MyCamera.this,"拍照失败",Toast.LENGTH_SHORT).show(); } } }); } }
我们来看一下活动中代码的执行顺序:
onCreate方法里活动首先调用getCameraInstance里的方法getCamera()来获取一个相机对象
当然getCameraInstance类是自己创建的一个类,下面是它里面的代码:
package myfinalcamera.caicai.com.myfinalcamera; import android.hardware.Camera; /** * Created by caicai on 2017/5/5. */ public class getCameraInstance { public static Camera getCamera(){ Camera c=null; try { c=Camera.open(1); } catch (Exception e) { e.printStackTrace(); } return c; } }
这里的代码就比较简单了,定义一个camera c,初始化为null。然后尝试打开摄像头,注意这里的c.open(1)
参数为“1”的含义为打开前摄像头,如果想打开后摄像头为c.open(0)或者直接省略参数。如果打开成功则返回camera对象,失败则抛出异常。
~~~
再接着看活动,定义一个mCamera用于接收调用getCamera()返回的相机对象。
将得到的camera作为参数传递给相机预览对象,显示当前相机里的图像。
当然这个相机预览类也是自己写的:附代码
CameraPreview继承自SurfaceView实现了SurfaceHolder.Callback和Camera.AutoFocusCallback接口。SurfaceView允许其他线程(非UI线程)多次绘制图形,即可在其界面上显示当前相机捕获的图像。
~~~
接着活动继续往下讲:
获得mpreview相机预览对象后,定义一个FrameLayout布局对象preview指向布局里的camera_preview,preview调用addView将相机预览视图添加到FramLayout布局里。这样就可以再布局里的camera_preview里显示了。
~~~
camera_preview里已经能显示了,再就是实现点击按钮实现拍照功能了
定义一个Buttou对象captureButton指向布局文件的button_capture_button按钮,为capture_button添加点击事件,当点击按钮时调用takePicture(save.shutter,save.row,save.jpeg)方法,第一个参数为快门,第二个参数为原数据,第三个参数为jpeg格式的数据,前两个我们可以不管直接在save类里实例化一个CameraCallback对象和PictureCallback对象里面的代码不需要改动,需要改的是第三个参数,因为要涉及到照片的存储和生成二进制流输出。这里又涉及到一个自定义类。
package myfinalcamera.caicai.com.myfinalcamera; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.hardware.Camera; import android.util.Log; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; /** * Created by caicai on 2017/5/5. */ public class Save { private static final String TAG = "Save"; public Camera.ShutterCallback shutter = new Camera.ShutterCallback() { @Override public void onShutter() { Log.i(TAG, "shutter"); } }; // 获得没有压缩过的图片数据 public Camera.PictureCallback raw = new Camera.PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera camera) { Log.i(TAG, "raw"); } }; //创建jpeg图片回调数据对象 public Camera.PictureCallback jpeg = new Camera.PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera camera) { BufferedOutputStream bos = null; Bitmap bm = null; try { bm = BitmapFactory.decodeByteArray(data, 0, data.length); String path= ImagePathName.returnPathName().toString(); File file =new File(path); Log.i(TAG, file.toString()); if (!file.exists()) { file.createNewFile(); } bos = new BufferedOutputStream(new FileOutputStream(file)); bm.compress(Bitmap.CompressFormat.JPEG, 100, bos); Log.i(TAG, "jpeg保存成功"); Log.i(TAG, new String(data)); } catch (IOException e) { e.printStackTrace(); Log.i(TAG, "jpeg保存失败"); } finally { try { bos.flush(); bos.close(); bm.recycle(); Log.i(TAG, "关闭成功 "); } catch (IOException e) { e.printStackTrace(); Log.i(TAG, "关闭失败"); } } } }; }
下面只看第三个参数调用的代码,这里定义了一个Bitmap对象bm和缓冲输出流对象bom,接着用bm接收拍照生成的data数据。定义图片的存储路径和名称,调用自己写的生成图片保存路径和名称的方法:
package myfinalcamera.caicai.com.myfinalcamera; import android.os.Environment; import android.util.Log; import android.widget.Toast; import java.io.File; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Random; /** * Created by caicai on 2017/5/5. */ public class ImagePathName { public static File returnPathName(){ String TAG="ImagePathName"; File filepath=null; String name=null; int x=(int)(Math.random()*100); Date date=new Date(System.currentTimeMillis()); SimpleDateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd"); name=dateFormat.format(date)+x; // Log.i(TAG, name.toString()); String stat= Environment.getExternalStorageState(); try { if(!Environment.MEDIA_MOUNTED.equals(stat)){ //进入这里说明sd卡没有挂载,则就返回提示信息并且return //Toast.makeText(myfinalcamera.caicai.com.myfinalcamera.MyCamera,"请检查sd卡状态",Toast.LENGTH_SHORT).show(); Log.i(TAG, "请检查sd卡状态"); return null; } filepath=new File(Environment.getExternalStorageDirectory(),name+".jpg"); // Toast.makeText(this,"成功获取路径信息"+filepath.toString(),Toast.LENGTH_SHORT).show(); Log.i(TAG, "成功获取路径信息"+filepath.toString()); } catch (Exception e) { e.printStackTrace(); Log.i(TAG, "异常"); } return filepath; } }
返回图片存储路径后,将数据以jpeg的形式存储到指定路径中去。在存储结束后我还用Log.i(TAG,new String(data));在控制台输出了一下照片的字符串类型数据。代码的最后就是清空缓冲区关闭缓冲输出流对象等。
最后别忘了在AndroidManifest.xml里将添加权限,在模拟器或真机上运行时,也要允许程序在相应的模拟器或真机上有相应的权限。