使用surfaceView制作的拍照demo

来源:互联网 发布:数据库checkpoint 编辑:程序博客网 时间:2024/06/06 08:45

这是一个用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


原创粉丝点击