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


更多相关文章

  1. Android开启网络adb调试
  2. android 遮罩层效果
  3. Android(安卓)API Guides---Camera
  4. Android(安卓)模拟器屏幕定制(修改控制器大小)
  5. 【已解决】Android真机设备调试时LogCat的日志无法输出的问题
  6. TextView的属性详解
  7. 支持单选,多选,还可以限制选择的数量的android流式布局
  8. 将Android(安卓)Studio的设置恢复到初始化(清除所有的设置)
  9. Android自定义标签列表控件LabelsView解析

随机推荐

  1. 外行人都能看懂的SpringCloud,错过了血亏!
  2. 图说yield
  3. 开发者必备Linux命令
  4. 翻译社重大改版
  5. JVM故障诊断和处理工具
  6. Linux防火墙Firewall和Iptables的使用
  7. macrotask与microtask
  8. 开发者必备Docker命令
  9. [一些勘误]ubuntu16.04的Python版本,Pytho
  10. 【MySQL】 explicit_defaults_for_timest