使用surfaceView制作的拍照demo
16lz
2021-01-26
这是一个用surfaceview来捕捉摄像头画面并拍照存储图片到sdcard的demo。众所周知,在一个应用中,我们可以通过intent来调用系统自带的相机功能进行拍
照,但,这样做不如自己写一个拍照界面来的酷!用surfaceview的方式来做,你可以随心所欲的设计自己的界面。
在这个例子中,我用代码制作了一个拍摄界面,里面只有三个控件,一个是自己封装的CameraView,它继承了SurfaceView,一个是悬浮在CameraView上的按
钮,点击它可以捕捉画面并把图像存储到sdCard的根目录下,还有一个是悬浮在CameraView上的TextView,它不过显示一行文字而已。
程序运行截图如下:
存储在sdcard根目录下的图片如下:
你可以看到,在surfaceview中所呈现的图像和保存的图像是一样的,这样做保证了所见即所得。
代码如下:
这是MainActivity,程序一运行就打开的Activity:
public class MainActivity extends Activity {private CameraView mCameraView;private Button takePictureBtn;private Camera mCamera;private Bitmap mBitmap;private int bitmapWidth;private int bitmapHeight;private RelativeLayout rl;// 准备一个保存图片的pictureCallback对象public Camera.PictureCallback mPictureCallback = new Camera.PictureCallback() {@Overridepublic void onPictureTaken(byte[] data, Camera camera) {// TODO Auto-generated method stubif(camera != null){Toast.makeText(getApplicationContext(), "正在保存...", Toast.LENGTH_LONG).show();// 用BitmapFactory.decodeByteArray()方法可以把相机传回的裸数据转换成Bitmap对象// 这里通过BitmapFactory.Options类指定解码方法BitmapFactory.Options options = new BitmapFactory.Options();// 在解码图片的时候设置inJustDecodeBounds属性为true,可以避免内存分配//options.inJustDecodeBounds = true;这句话已开启就会死机mBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);bitmapWidth = options.outWidth;bitmapHeight = options.outHeight;// 把bitmap保存成一个存储卡中的文件File file = new File("/sdcard/YY"+ new DateFormat().format("yyyyMMdd_hhmmss", Calendar.getInstance(Locale.CHINA)) + ".png");try {file.createNewFile();BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(file));mBitmap.compress(Bitmap.CompressFormat.PNG, 100, os);os.flush();os.close();Toast.makeText(getApplicationContext(), "图片 " + bitmapWidth + "X" + bitmapHeight +" 保存完毕,在存储卡的根目录", Toast.LENGTH_LONG).show();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//窗口去掉标题requestWindowFeature(Window.FEATURE_NO_TITLE);//窗口设置为全屏getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);//设置窗口为半透明getWindow().setFormat(PixelFormat.TRANSLUCENT);// 创建布局rl = new RelativeLayout(this);// 添加CameraViewaddCameraView(rl);// 添加拍摄按钮addTakePictureBtn(rl);// 添加提示文字addTextView(rl); // 设置布局 setContentView(rl);}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {// Inflate the menu; this adds items to the action bar if it is present.getMenuInflater().inflate(R.menu.activity_main, menu);return true;}/** * 添加CamearView * @param rl 所在的布局 */private void addCameraView(RelativeLayout rl){// 打开相机this.mCamera = Camera.open();// 设置CameraView的大小和位置// 1、首先得到保存在sdcard的图片大小Camera.Parameters parameters = this.mCamera.getParameters();Size sdCardPictureSize = CameraView.getOptimalPictureSize(parameters.getSupportedPictureSizes(), 640, 480);// 2、得到设备的屏幕分辨率DisplayMetrics dm = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(dm); // 3、根据以上两个数据计算出CameraView的大小 float scale = (float)dm.heightPixels/(float)sdCardPictureSize.height; int cameraViewWidth = (int) (sdCardPictureSize.width * scale); int cameraViewHeight = (int) (sdCardPictureSize.height * scale); System.out.println("scale: " + scale); System.out.println("cameraView: " + cameraViewWidth + ", " + cameraViewHeight); // 4、把CameraView居中布局 RelativeLayout.LayoutParams rlp = new RelativeLayout.LayoutParams(cameraViewWidth, cameraViewHeight); rlp.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE); // 5、把cameraView添加到rl上 this.mCameraView = new CameraView(this); this.mCameraView.setCamera(mCamera); rl.addView(this.mCameraView, rlp);}/** * 添加拍摄按钮 * @param rl */private void addTakePictureBtn(RelativeLayout rl){this.takePictureBtn = new Button(this);this.takePictureBtn.setText("拍摄");this.takePictureBtn.setOnClickListener(new TakePictureBtnOnClickListener());// 把按钮放置在屏幕右下角RelativeLayout.LayoutParams rlp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);rlp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);rlp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);rlp.rightMargin = 15;rlp.bottomMargin = 15;rl.addView(takePictureBtn, rlp);}/** * 添加提示文字 * @param rl */private void addTextView(RelativeLayout rl){TextView textView = new TextView(this);textView.setText("请点击拍摄按钮拍摄:");// 把文字放在屏幕左上角RelativeLayout.LayoutParams rlp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);rlp.addRule(RelativeLayout.ALIGN_PARENT_LEFT);rlp.addRule(RelativeLayout.ALIGN_TOP);rl.addView(textView, rlp);}/** * 拍摄按钮的OnClickListener,在这里执行拍照。 * @author haozi * */class TakePictureBtnOnClickListener implements View.OnClickListener{@Overridepublic void onClick(View v) {if(mCamera != null){mCamera.takePicture(null, null, mPictureCallback);}else{Toast.makeText(getApplicationContext(), "Camera对象为空!", Toast.LENGTH_LONG).show();}}}}
这是封装好的CameraView控件,它继承了SurfaceView:
/** * 摄像的View * @author haozi * */public class CameraView extends SurfaceView {public Context mContext;private SurfaceHolder mSurfaceHolder;public CameraView(Context context, AttributeSet attrs) {super(context, attrs);this.mContext = context;}public CameraView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);this.mContext = context;}public CameraView(Context context) {super(context);this.mContext = context;}/** * 把照相机对象传入 * @param mCamera */public void setCamera(Camera mCamera){// 操作surface的holdermSurfaceHolder = this.getHolder();// 创建surfaceholder对象mSurfaceHolder.addCallback(new SurfaceHolderCallback(mCamera));// 设置push缓冲类型,说明surface数据由其他来源提供。而不是用自己的Canvas来绘图,在这里由摄像头来提供数据。mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);}/** * 得到最适合的预览大小 * @param sizes * @param w * @param h * @return */public static Size getOptimalPreviewSize(List<Size> sizes, int w, int h) { final double ASPECT_TOLERANCE = 0.1; double targetRatio = (double) w / h; if (sizes == null) return null; Size optimalSize = null; double minDiff = Double.MAX_VALUE; int targetHeight = h; // Try to find an size match aspect ratio and size for (Size size : sizes) { double ratio = (double) size.width / size.height; if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue; if (Math.abs(size.height - targetHeight) < minDiff) { optimalSize = size; minDiff = Math.abs(size.height - targetHeight); } } // Cannot find the one match the aspect ratio, ignore the requirement if (optimalSize == null) { minDiff = Double.MAX_VALUE; for (Size size : sizes) { if (Math.abs(size.height - targetHeight) < minDiff) { optimalSize = size; minDiff = Math.abs(size.height - targetHeight); } } } return optimalSize; }/** * 得到最合适的PictureSize * @param sizes * @param w * @param h * @return */public static Size getOptimalPictureSize(List<Size> sizes, int w, int h) { final double ASPECT_TOLERANCE = 0.1; double targetRatio = (double) w / h; if (sizes == null) return null; Size optimalSize = null; double minDiff = Double.MAX_VALUE; int targetHeight = h; // Try to find an size match aspect ratio and size for (Size size : sizes) { double ratio = (double) size.width / size.height; if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue; if (Math.abs(size.height - targetHeight) < minDiff) { optimalSize = size; minDiff = Math.abs(size.height - targetHeight); } } // Cannot find the one match the aspect ratio, ignore the requirement if (optimalSize == null) { minDiff = Double.MAX_VALUE; for (Size size : sizes) { if (Math.abs(size.height - targetHeight) < minDiff) { optimalSize = size; minDiff = Math.abs(size.height - targetHeight); } } } return optimalSize; }/** * 摄像头捕捉到的画面都会在这里被处理 * @author haozi * */class SurfaceHolderCallback implements SurfaceHolder.Callback{private Camera mCamera;public SurfaceHolderCallback(Camera mCamera){this.mCamera = mCamera;}@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {// TODO Auto-generated method stub// 停止预览mCamera.stopPreview();// 释放相机资源并置空mCamera.release();mCamera = null;}@Overridepublic void surfaceCreated(SurfaceHolder holder) {// 设置预览try {mCamera.setPreviewDisplay(holder);} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();// 如果出现异常,释放相机资源并置空mCamera.release();mCamera = null;}}// 当surface视图数据发生变化时,处理预览信息@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width,int height) {// TODO Auto-generated method stub// 如果相机资源并不为空if(mCamera != null){// 获得相机参数对象Camera.Parameters parameters = mCamera.getParameters();// 获取最合适的参数,为了做到拍摄的时候所见即所得,我让previewSize和pictureSize相等Size previewSize = getOptimalPictureSize(parameters.getSupportedPictureSizes(), 640, 480);Size pictureSize = getOptimalPictureSize(parameters.getSupportedPictureSizes(), 640, 480);System.out.println("---------------------------------------------------------------");System.out.println("previewSize: " + previewSize.width + ", " + previewSize.height);System.out.println("pictureSize: " + pictureSize.width + ", " + pictureSize.height);// 设置照片格式parameters.setPictureFormat(PixelFormat.JPEG);// 设置预览大小parameters.setPreviewSize(previewSize.width, previewSize.height);// 设置自动对焦,先进行判断List<String> focusModes = parameters.getSupportedFocusModes(); if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) { parameters.setFocusMode(Parameters.FOCUS_MODE_AUTO);}// 设置图片保存时候的分辨率大小parameters.setPictureSize(pictureSize.width, pictureSize.height);// 给相机对象设置刚才设置的参数mCamera.setParameters(parameters);// 开始预览mCamera.startPreview();}}}}
要使用摄像机,别忘了权限,这是AndroidManifest.xml的代码:
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.haozi.demo.screenshot4" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="10" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.haozi.demo.screenshot4.MainActivity" android:label="@string/app_name" android:screenOrientation="landscape" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.FLASHLIGHT"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-feature android:name="android.hardware.camera"/> <uses-feature android:name="android.hardware.autofocus"/></manifest>
因为是代码布局,所以就没有用到xml布局方式。ok,大功告成!
源码下载地址:http://download.csdn.net/detail/hello_haozi/5014830
更多相关文章
- Android开启网络adb调试
- android 遮罩层效果
- Android(安卓)API Guides---Camera
- Android(安卓)模拟器屏幕定制(修改控制器大小)
- 【已解决】Android真机设备调试时LogCat的日志无法输出的问题
- TextView的属性详解
- 支持单选,多选,还可以限制选择的数量的android流式布局
- 将Android(安卓)Studio的设置恢复到初始化(清除所有的设置)
- Android自定义标签列表控件LabelsView解析