使用surfaceView制作的拍照demo

这是一个用surfaceview来捕捉摄像头画面并拍照存储图片到sdcard的demo。众所周知,在一个应用中,我们可以通过intent来调用系统自带的相机功能进行拍

照,但,这样做不如自己写一个拍照界面来的酷!用surfaceview的方式来做,你可以随心所欲的设计自己的界面。

在这个例子中,我用代码制作了一个拍摄界面,里面只有三个控件,一个是自己封装的CameraView,它继承了SurfaceView,一个是悬浮在CameraView上的按

钮,点击它可以捕捉画面并把图像存储到sdCard的根目录下,还有一个是悬浮在CameraView上的TextView,它不过显示一行文字而已。

程序运行截图如下:

使用surfaceView制作的拍照demo_第1张图片

存储在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() {
		
		@Override
		public void onPictureTaken(byte[] data, Camera camera) {
			// TODO Auto-generated method stub
			
			if(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 block
					e.printStackTrace();
				}
			}	
			
		}
	};
	
	@Override
	protected 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);
		
		// 添加CameraView
		addCameraView(rl);
		// 添加拍摄按钮
		addTakePictureBtn(rl);
		// 添加提示文字
		addTextView(rl);
	    
	    // 设置布局
	    setContentView(rl);
	}

	@Override
	public 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{

		@Override
		public 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的holder
		mSurfaceHolder = 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;
		}
		
		@Override
		public void surfaceDestroyed(SurfaceHolder holder) {
			// TODO Auto-generated method stub
			// 停止预览
			mCamera.stopPreview();
			// 释放相机资源并置空
			mCamera.release();
			mCamera = null;
		}
		
		@Override
		public void surfaceCreated(SurfaceHolder holder) {

			// 设置预览
			try {
				mCamera.setPreviewDisplay(holder);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				// 如果出现异常,释放相机资源并置空
				mCamera.release();
				mCamera = null;
			}
		}
		
		// 当surface视图数据发生变化时,处理预览信息
		@Override
		public 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,android,android,application,application,Camera,Camera,SurfaceView,SurfaceView,SurfaceView)