雜家前文是在2012年的除夕之夜倉促完成,后來很多人指出了一些問題,瑣事纏身一直沒有進行升級。后來隨着我自己的使用,越來越發現不出個升級版的demo是不行了。有時候就連我自己用這個demo測一些性能、功能點,用着都不順手。當初代碼是在linux下寫的,弄到windows里下全是亂碼。還要自己改幾分鍾才能改好。另外,很多人說不能正常預覽,原因是我在布局里把Surfaceview的尺寸寫死了。再有就是initCamera()的時候設參數失敗,直接黑屏退出,原因也是我把預覽尺寸和照片尺寸寫死了。再有就是照片變形的問題。為此,今天出一個升級版的demo,爭取全面適配所有機型。
上圖為此次的代碼結構,activity包里就是放CameraActivity,日后添加圖庫瀏覽功能再加GalleryActivity。為了使Camera的邏輯和界面的UI耦合度降至最低,封裝了CameraInterface類,里面操作Camera的打開、預覽、拍照、關閉。preview包里是自定義的Surfaceview。在util包里放着CamParaUtil是專門用來設置、打印Camera的PreviewSize、PictureSize、FocusMode的,並能根據Activity傳進來的長寬比(主要是16:9 或 4:3兩種尺寸)自動尋找適配的PreviewSize和PictureSize,消除變形。默認的是全屏,因為一些手機全屏時,屏幕的長寬比不是16:9或4:3所以在找尺寸時也是存在一些偏差的。其中有個值,就是判斷兩個float是否相等,這個參數比較關鍵,里面設的0.03.經我多個手機測試,這個參數是最合適的,否則的話有些奇葩手機得到的尺寸拍出照片變形。下面上源碼:
一、布局 activity_camera.xml
- <span style="font-family:Comic Sans MS;font-size:18px;"><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context=".CameraActivity" >
- <FrameLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" >
- <org.yanzi.camera.preview.CameraSurfaceView
- android:id="@+id/camera_surfaceview"
- android:layout_width="0dip"
- android:layout_height="0dip" />
- </FrameLayout>
- <ImageButton
- android:id="@+id/btn_shutter"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:background="@drawable/btn_shutter_background"
- android:layout_alignParentBottom="true"
- android:layout_centerHorizontal="true"
- android:layout_marginBottom="10dip"/>
- </RelativeLayout>
- </span>
二、AndroidManifest.xml
- <span style="font-family:Comic Sans MS;font-size:18px;"><?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="org.yanzi.playcamera"
- android:versionCode="1"
- android:versionName="1.0" >
- <uses-sdk
- android:minSdkVersion="9"
- android:targetSdkVersion="17" />
- <!-- 增加文件存儲和訪問攝像頭的權限 -->
- <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.CAMERA" />
- <uses-feature android:name="android.hardware.camera" />
- <application
- android:allowBackup="true"
- android:icon="@drawable/ic_launcher_icon"
- android:label="@string/app_name"
- android:theme="@style/AppTheme" >
- <activity
- android:name="org.yanzi.activity.CameraActivity"
- android:label="@string/app_name"
- android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
- android:screenOrientation="portrait">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
- </manifest>
- </span>
三、下面是java代碼
1、CameraActivity.Java
- <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.activity;
- import org.yanzi.camera.CameraInterface;
- import org.yanzi.camera.CameraInterface.CamOpenOverCallback;
- import org.yanzi.camera.preview.CameraSurfaceView;
- import org.yanzi.playcamera.R;
- import org.yanzi.util.DisplayUtil;
- import android.app.Activity;
- import android.graphics.Point;
- import android.os.Bundle;
- import android.view.Menu;
- import android.view.SurfaceHolder;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.view.ViewGroup.LayoutParams;
- import android.widget.ImageButton;
- public class CameraActivity extends Activity implements CamOpenOverCallback {
- private static final String TAG = "yanzi";
- CameraSurfaceView surfaceView = null;
- ImageButton shutterBtn;
- float previewRate = -1f;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- Thread openThread = new Thread(){
- @Override
- public void run() {
- // TODO Auto-generated method stub
- CameraInterface.getInstance().doOpenCamera(CameraActivity.this);
- }
- };
- openThread.start();
- setContentView(R.layout.activity_camera);
- initUI();
- initViewParams();
- shutterBtn.setOnClickListener(new BtnListeners());
- }
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- // Inflate the menu; this adds items to the action bar if it is present.
- getMenuInflater().inflate(R.menu.camera, menu);
- return true;
- }
- private void initUI(){
- surfaceView = (CameraSurfaceView)findViewById(R.id.camera_surfaceview);
- shutterBtn = (ImageButton)findViewById(R.id.btn_shutter);
- }
- private void initViewParams(){
- LayoutParams params = surfaceView.getLayoutParams();
- Point p = DisplayUtil.getScreenMetrics(this);
- params.width = p.x;
- params.height = p.y;
- previewRate = DisplayUtil.getScreenRate(this); //默認全屏的比例預覽
- surfaceView.setLayoutParams(params);
- //手動設置拍照ImageButton的大小為120dip×120dip,原圖片大小是64×64
- LayoutParams p2 = shutterBtn.getLayoutParams();
- p2.width = DisplayUtil.dip2px(this, 80);
- p2.height = DisplayUtil.dip2px(this, 80);;
- shutterBtn.setLayoutParams(p2);
- }
- @Override
- public void cameraHasOpened() {
- // TODO Auto-generated method stub
- SurfaceHolder holder = surfaceView.getSurfaceHolder();
- CameraInterface.getInstance().doStartPreview(holder, previewRate);
- }
- private class BtnListeners implements OnClickListener{
- @Override
- public void onClick(View v) {
- // TODO Auto-generated method stub
- switch(v.getId()){
- case R.id.btn_shutter:
- CameraInterface.getInstance().doTakePicture();
- break;
- default:break;
- }
- }
- }
- }
- </span>
2、CameraInterface.java
- <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.camera;
- import java.io.IOException;
- import java.util.List;
- import org.yanzi.util.CamParaUtil;
- import org.yanzi.util.FileUtil;
- import org.yanzi.util.ImageUtil;
- import android.graphics.Bitmap;
- import android.graphics.BitmapFactory;
- import android.graphics.PixelFormat;
- import android.hardware.Camera;
- import android.hardware.Camera.PictureCallback;
- import android.hardware.Camera.ShutterCallback;
- import android.hardware.Camera.Size;
- import android.util.Log;
- import android.view.SurfaceHolder;
- public class CameraInterface {
- private static final String TAG = "yanzi";
- private Camera mCamera;
- private Camera.Parameters mParams;
- private boolean isPreviewing = false;
- private float mPreviwRate = -1f;
- private static CameraInterface mCameraInterface;
- public interface CamOpenOverCallback{
- public void cameraHasOpened();
- }
- private CameraInterface(){
- }
- public static synchronized CameraInterface getInstance(){
- if(mCameraInterface == null){
- mCameraInterface = new CameraInterface();
- }
- return mCameraInterface;
- }
- /**打開Camera
- * @param callback
- */
- public void doOpenCamera(CamOpenOverCallback callback){
- Log.i(TAG, "Camera open....");
- mCamera = Camera.open();
- Log.i(TAG, "Camera open over....");
- callback.cameraHasOpened();
- }
- /**開啟預覽
- * @param holder
- * @param previewRate
- */
- public void doStartPreview(SurfaceHolder holder, float previewRate){
- Log.i(TAG, "doStartPreview...");
- if(isPreviewing){
- mCamera.stopPreview();
- return;
- }
- if(mCamera != null){
- mParams = mCamera.getParameters();
- mParams.setPictureFormat(PixelFormat.JPEG);//設置拍照后存儲的圖片格式
- CamParaUtil.getInstance().printSupportPictureSize(mParams);
- CamParaUtil.getInstance().printSupportPreviewSize(mParams);
- //設置PreviewSize和PictureSize
- Size pictureSize = CamParaUtil.getInstance().getPropPictureSize(
- mParams.getSupportedPictureSizes(),previewRate, 800);
- mParams.setPictureSize(pictureSize.width, pictureSize.height);
- Size previewSize = CamParaUtil.getInstance().getPropPreviewSize(
- mParams.getSupportedPreviewSizes(), previewRate, 800);
- mParams.setPreviewSize(previewSize.width, previewSize.height);
- mCamera.setDisplayOrientation(90);
- CamParaUtil.getInstance().printSupportFocusMode(mParams);
- List<String> focusModes = mParams.getSupportedFocusModes();
- if(focusModes.contains("continuous-video")){
- mParams.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
- }
- mCamera.setParameters(mParams);
- try {
- mCamera.setPreviewDisplay(holder);
- mCamera.startPreview();//開啟預覽
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- isPreviewing = true;
- mPreviwRate = previewRate;
- mParams = mCamera.getParameters(); //重新get一次
- Log.i(TAG, "最終設置:PreviewSize--With = " + mParams.getPreviewSize().width
- + "Height = " + mParams.getPreviewSize().height);
- Log.i(TAG, "最終設置:PictureSize--With = " + mParams.getPictureSize().width
- + "Height = " + mParams.getPictureSize().height);
- }
- }
- /**
- * 停止預覽,釋放Camera
- */
- public void doStopCamera(){
- if(null != mCamera)
- {
- mCamera.setPreviewCallback(null);
- mCamera.stopPreview();
- isPreviewing = false;
- mPreviwRate = -1f;
- mCamera.release();
- mCamera = null;
- }
- }
- /**
- * 拍照
- */
- public void doTakePicture(){
- if(isPreviewing && (mCamera != null)){
- mCamera.takePicture(mShutterCallback, null, mJpegPictureCallback);
- }
- }
- /*為了實現拍照的快門聲音及拍照保存照片需要下面三個回調變量*/
- ShutterCallback mShutterCallback = new ShutterCallback()
- //快門按下的回調,在這里我們可以設置類似播放“咔嚓”聲之類的操作。默認的就是咔嚓。
- {
- public void onShutter() {
- // TODO Auto-generated method stub
- Log.i(TAG, "myShutterCallback:onShutter...");
- }
- };
- PictureCallback mRawCallback = new PictureCallback()
- // 拍攝的未壓縮原數據的回調,可以為null
- {
- public void onPictureTaken(byte[] data, Camera camera) {
- // TODO Auto-generated method stub
- Log.i(TAG, "myRawCallback:onPictureTaken...");
- }
- };
- PictureCallback mJpegPictureCallback = new PictureCallback()
- //對jpeg圖像數據的回調,最重要的一個回調
- {
- public void onPictureTaken(byte[] data, Camera camera) {
- // TODO Auto-generated method stub
- Log.i(TAG, "myJpegCallback:onPictureTaken...");
- Bitmap b = null;
- if(null != data){
- b = BitmapFactory.decodeByteArray(data, 0, data.length);//data是字節數據,將其解析成位圖
- mCamera.stopPreview();
- isPreviewing = false;
- }
- //保存圖片到sdcard
- if(null != b)
- {
- //設置FOCUS_MODE_CONTINUOUS_VIDEO)之后,myParam.set("rotation", 90)失效。
- //圖片竟然不能旋轉了,故這里要旋轉下
- Bitmap rotaBitmap = ImageUtil.getRotateBitmap(b, 90.0f);
- FileUtil.saveBitmap(rotaBitmap);
- }
- //再次進入預覽
- mCamera.startPreview();
- isPreviewing = true;
- }
- };
- }
- </span>
3、CameraSurfaceView.java
- <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.camera.preview;
- import org.yanzi.camera.CameraInterface;
- import android.content.Context;
- import android.graphics.PixelFormat;
- import android.util.AttributeSet;
- import android.util.Log;
- import android.view.SurfaceHolder;
- import android.view.SurfaceView;
- public class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
- private static final String TAG = "yanzi";
- CameraInterface mCameraInterface;
- Context mContext;
- SurfaceHolder mSurfaceHolder;
- public CameraSurfaceView(Context context, AttributeSet attrs) {
- super(context, attrs);
- // TODO Auto-generated constructor stub
- mContext = context;
- mSurfaceHolder = getHolder();
- mSurfaceHolder.setFormat(PixelFormat.TRANSPARENT);//translucent半透明 transparent透明
- mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
- mSurfaceHolder.addCallback(this);
- }
- @Override
- public void surfaceCreated(SurfaceHolder holder) {
- // TODO Auto-generated method stub
- Log.i(TAG, "surfaceCreated...");
- }
- @Override
- public void surfaceChanged(SurfaceHolder holder, int format, int width,
- int height) {
- // TODO Auto-generated method stub
- Log.i(TAG, "surfaceChanged...");
- }
- @Override
- public void surfaceDestroyed(SurfaceHolder holder) {
- // TODO Auto-generated method stub
- Log.i(TAG, "surfaceDestroyed...");
- CameraInterface.getInstance().doStopCamera();
- }
- public SurfaceHolder getSurfaceHolder(){
- return mSurfaceHolder;
- }
- }
- </span>
4、CamParaUtil.java
- <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.util;
- import java.util.Collections;
- import java.util.Comparator;
- import java.util.List;
- import android.hardware.Camera;
- import android.hardware.Camera.Size;
- import android.util.Log;
- public class CamParaUtil {
- private static final String TAG = "yanzi";
- private CameraSizeComparator sizeComparator = new CameraSizeComparator();
- private static CamParaUtil myCamPara = null;
- private CamParaUtil(){
- }
- public static CamParaUtil getInstance(){
- if(myCamPara == null){
- myCamPara = new CamParaUtil();
- return myCamPara;
- }
- else{
- return myCamPara;
- }
- }
- public Size getPropPreviewSize(List<Camera.Size> list, float th, int minWidth){
- Collections.sort(list, sizeComparator);
- int i = 0;
- for(Size s:list){
- if((s.width >= minWidth) && equalRate(s, th)){
- Log.i(TAG, "PreviewSize:w = " + s.width + "h = " + s.height);
- break;
- }
- i++;
- }
- if(i == list.size()){
- i = 0;//如果沒找到,就選最小的size
- }
- return list.get(i);
- }
- public Size getPropPictureSize(List<Camera.Size> list, float th, int minWidth){
- Collections.sort(list, sizeComparator);
- int i = 0;
- for(Size s:list){
- if((s.width >= minWidth) && equalRate(s, th)){
- Log.i(TAG, "PictureSize : w = " + s.width + "h = " + s.height);
- break;
- }
- i++;
- }
- if(i == list.size()){
- i = 0;//如果沒找到,就選最小的size
- }
- return list.get(i);
- }
- public boolean equalRate(Size s, float rate){
- float r = (float)(s.width)/(float)(s.height);
- if(Math.abs(r - rate) <= 0.03)
- {
- return true;
- }
- else{
- return false;
- }
- }
- public class CameraSizeComparator implements Comparator<Camera.Size>{
- public int compare(Size lhs, Size rhs) {
- // TODO Auto-generated method stub
- if(lhs.width == rhs.width){
- return 0;
- }
- else if(lhs.width > rhs.width){
- return 1;
- }
- else{
- return -1;
- }
- }
- }
- /**打印支持的previewSizes
- * @param params
- */
- public void printSupportPreviewSize(Camera.Parameters params){
- List<Size> previewSizes = params.getSupportedPreviewSizes();
- for(int i=0; i< previewSizes.size(); i++){
- Size size = previewSizes.get(i);
- Log.i(TAG, "previewSizes:width = "+size.width+" height = "+size.height);
- }
- }
- /**打印支持的pictureSizes
- * @param params
- */
- public void printSupportPictureSize(Camera.Parameters params){
- List<Size> pictureSizes = params.getSupportedPictureSizes();
- for(int i=0; i< pictureSizes.size(); i++){
- Size size = pictureSizes.get(i);
- Log.i(TAG, "pictureSizes:width = "+ size.width
- +" height = " + size.height);
- }
- }
- /**打印支持的聚焦模式
- * @param params
- */
- public void printSupportFocusMode(Camera.Parameters params){
- List<String> focusModes = params.getSupportedFocusModes();
- for(String mode : focusModes){
- Log.i(TAG, "focusModes--" + mode);
- }
- }
- }
- </span>
5、DisplayUtil.java
- <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.util;
- import android.content.Context;
- import android.graphics.Point;
- import android.util.DisplayMetrics;
- import android.util.Log;
- public class DisplayUtil {
- private static final String TAG = "DisplayUtil";
- /**
- * dip轉px
- * @param context
- * @param dipValue
- * @return
- */
- public static int dip2px(Context context, float dipValue){
- final float scale = context.getResources().getDisplayMetrics().density;
- return (int)(dipValue * scale + 0.5f);
- }
- /**
- * px轉dip
- * @param context
- * @param pxValue
- * @return
- */
- public static int px2dip(Context context, float pxValue){
- final float scale = context.getResources().getDisplayMetrics().density;
- return (int)(pxValue / scale + 0.5f);
- }
- /**
- * 獲取屏幕寬度和高度,單位為px
- * @param context
- * @return
- */
- public static Point getScreenMetrics(Context context){
- DisplayMetrics dm =context.getResources().getDisplayMetrics();
- int w_screen = dm.widthPixels;
- int h_screen = dm.heightPixels;
- Log.i(TAG, "Screen---Width = " + w_screen + " Height = " + h_screen + " densityDpi = " + dm.densityDpi);
- return new Point(w_screen, h_screen);
- }
- /**
- * 獲取屏幕長寬比
- * @param context
- * @return
- */
- public static float getScreenRate(Context context){
- Point P = getScreenMetrics(context);
- float H = P.y;
- float W = P.x;
- return (H/W);
- }
- }
- </span>
6、FileUtil.java
- <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.util;
- import java.io.BufferedOutputStream;
- import java.io.File;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import android.graphics.Bitmap;
- import android.os.Environment;
- import android.util.Log;
- public class FileUtil {
- private static final String TAG = "FileUtil";
- private static final File parentPath = Environment.getExternalStorageDirectory();
- private static String storagePath = "";
- private static final String DST_FOLDER_NAME = "PlayCamera";
- /**初始化保存路徑
- * @return
- */
- private static String initPath(){
- if(storagePath.equals("")){
- storagePath = parentPath.getAbsolutePath()+"/" + DST_FOLDER_NAME;
- File f = new File(storagePath);
- if(!f.exists()){
- f.mkdir();
- }
- }
- return storagePath;
- }
- /**保存Bitmap到sdcard
- * @param b
- */
- public static void saveBitmap(Bitmap b){
- String path = initPath();
- long dataTake = System.currentTimeMillis();
- String jpegName = path + "/" + dataTake +".jpg";
- Log.i(TAG, "saveBitmap:jpegName = " + jpegName);
- try {
- FileOutputStream fout = new FileOutputStream(jpegName);
- BufferedOutputStream bos = new BufferedOutputStream(fout);
- b.compress(Bitmap.CompressFormat.JPEG, 100, bos);
- bos.flush();
- bos.close();
- Log.i(TAG, "saveBitmap成功");
- } catch (IOException e) {
- // TODO Auto-generated catch block
- Log.i(TAG, "saveBitmap:失敗");
- e.printStackTrace();
- }
- }
- }
- </span>
7、ImageUtil.java
- <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.util;
- import android.graphics.Bitmap;
- import android.graphics.Matrix;
- public class ImageUtil {
- /**
- * 旋轉Bitmap
- * @param b
- * @param rotateDegree
- * @return
- */
- public static Bitmap getRotateBitmap(Bitmap b, float rotateDegree){
- Matrix matrix = new Matrix();
- matrix.postRotate((float)rotateDegree);
- Bitmap rotaBitmap = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), matrix, false);
- return rotaBitmap;
- }
- }
- </span>
幾點說明:
1、包括我之前的博文在內的大量網上鏈接,都是在Surfaceview create的時候進行打開Camera的操作,在Surfaceview Changed的時候進行開預覽。而Surfaceview create的時候一定是在setContentView之后,Surfaceview實例化之后。為了優化開啟Camera時間,我再setContentView之前new了一個線程專門去Open Camera。經過測試,但就執行Camera.open()這句話一般需要140ms左右。如果放在主線程里無疑是一種浪費。而在140ms之后,Surfaceview里因為無需觸發關於Camera的操作,所以加載的特別快。也就是說Open完后,Surfaceview一定完成了實例化。所以我設置了CamOpenOverCallback回調,在Camera打開完畢后通知Activity立即執行開預覽的操作。
2、開預覽因為用Surfaceview預覽,需傳遞Surfaceview的SurfaceHolder。
3、CameraInterface是個單例模式,所有關於Camera的流程性操作一律封裝在這里面。
4、Activity設置了全屏無標題且強制豎屏,像這種操作能再xml寫就不要再java代碼里弄。
圖片資源上,雜家還真是一番精心挑選,對比了Camera360、相機360、美顏相機,UI上總的來說三個app感覺都很垃圾,都整的太復雜了,圖片也不好看。最后勉強用了相機360里的一個button,自己想用PS把按鍵點擊時的圖標色彩P亮點,一個沒留神,還給P的更暗了色彩。汗,不過按鍵的對比效果更明顯了。日后,會將一些OpenCV4Android的一些小demo都整合到PlayCamera系列。
下為效果圖:
------------本文系原創,轉載請注明作者yanzi1225627
版本號:PlayCamera_V1.0.0[2014-6-22].zip
CSDN下載鏈接:http://download.csdn.net/detail/yanzi1225627/7540873
百度雲盤: