使用Camera与SurfaceView实现自定义拍照功能

创建Fragment布局

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <!-- 因为设置的android:layout_width="wrap_content"属性值,Button组件仅用了自己所需的空间,而按照 
         android:layout_width="0dp"的属性值,SurfaceView组件不占用任何空间。不过从剩余空间的角度来说,
           因为使用了layout_height属性,所以SurfaceView组件使用了Button组件以外的全部空间。-->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal" >

        <SurfaceView
            android:id="@+id/crime_camera_surfaceview"
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1" />

        <Button
            android:id="@+id/crime_camera_takePictureButton"
            android:layout_width="wrap_content"
            android:layout_height="fill_parent"
            android:text="@string/take" />
    </LinearLayout>

</FrameLayout>

CrimeCameraActivity

package com.huangfei.criminalintent;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.Window;
import android.view.WindowManager;

public class CrimeCameraActivity extends SingleFragmentActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        /**
         * 为什么必须在activity中实现隐藏呢?在调用Activity.setContentView(...)方法(该方法在CrimeCameraActivity类的
         * onCreate(Bundle)超类版本方法中调用的。)创建activity视图之前,就必须调用requestWindowFeature(...)方法及addFlags(...)
         * 方法。而fragment无法在其托管activity视图创建之前添加,因此。必须在activity里调用隐藏操作栏和状态栏的相关方法。 
         */
        requestWindowFeature(Window.FEATURE_NO_TITLE);//隐藏操作栏
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);//隐藏状态栏
        super.onCreate(savedInstanceState);
    }

    @Override
    protected Fragment createFragment() {
        return new CrimeCameraFragment();
    }

}

注册CrimeCameraActivity并添加拍照权限

uses-permission android:name="android.permission.CAMERA"/>
    <!-- uses-feature元素用来指定应用使用的某项特色设备功能。通过android.hardware.camera特色功能的设置,
                         可以保证只有那些配备相机功能的设备才能看到你发布在Google Play上的应用。 -->
    <uses-feature android:name="android.hardware.camera"/>

......

<activity
            android:name="com.huangfei.criminalintent.CrimeCameraActivity"
            android:screenOrientation="landscape"
            android:label="@string/app_name" >
        </activity>

创建CrimeCameraFragment

package com.huangfei.criminalintent;

import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.UUID;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.Size;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;

public class CrimeCameraFragment extends Fragment {
    private static final String TAG = "CrimeCameraFragment";
    public static final String EXTRA_PHOTO_FILENAME = "com.huangfei.criminalintent.photo_filename";

    /**
     * Camera实例提供了对设备相机硬件级别的调用。相机是一种独占性资源:一次只能有一个activity能够调用相机。
     */
    private Camera mCamera;
    /**
     * SurfaceView实例是相机的取景器。SurfaceView是一种特殊的视图,可直接将要显示的内容渲染输出到设备的屏幕上。
     */
    private SurfaceView mSurfaceView;
    private View mProgressContainer;

    private Camera.ShutterCallback mShutterCallback = new Camera.ShutterCallback() {

        @Override
        public void onShutter() {
            Log.i(TAG, "ShutterCallback");
            mProgressContainer.setVisibility(View.VISIBLE);
        }
    };

    private Camera.PictureCallback mJepgCallback = new Camera.PictureCallback() {

        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
            Log.i(TAG, "PictureCallback");
            String fileName = UUID.randomUUID().toString() + ".jpg";
            FileOutputStream fos = null;
            boolean sucess = true;

            try {
                fos = getActivity().openFileOutput(fileName, Context.MODE_PRIVATE);
                fos.write(data);
            } catch (Exception e) {
                e.printStackTrace();
                sucess =false;
            }finally{
                if(fos != null)
                    try {
                        fos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                        sucess =false;
                    }
            }

            if(sucess){
                Intent intent = new Intent();
                intent.putExtra(EXTRA_PHOTO_FILENAME, fileName);
                getActivity().setResult(Activity.RESULT_OK, intent);
            }else {
                getActivity().setResult(Activity.RESULT_CANCELED);
            }

            getActivity().finish();
        }
    };

    @SuppressWarnings("deprecation")
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_crime_camera, container,
                false);

        mProgressContainer = view
                .findViewById(R.id.crime_camera_progressContainer);
        mProgressContainer.setVisibility(View.INVISIBLE);
        Button takePictureButton = (Button) view
                .findViewById(R.id.crime_camera_takePictureButton);
        takePictureButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                if(mCamera != null)
                    /**
                     * ShutterCallback回调方法会在相机捕获图像时调用,但此时,图像数据还未处理完成。
                     * 第一个PictureCallback回调方法是在原始图像数据可用时调用,通常来说,是在加工处理原始图像数据且没有存储之前。
                     * 第二个PictureCallback回调方法是在JPEG版本的图像可用时调用。
                     */
                    mCamera.takePicture(mShutterCallback, null, mJepgCallback);
            }
        });

        mSurfaceView = (SurfaceView) view
                .findViewById(R.id.crime_camera_surfaceview);
        /**
         * SurfaceView实现了SurfaceHolder接口。SurfaceHolder是我们与Surface对象联系的纽带。
         * Surface对象代表着原始像素数据的缓冲区。
         * Surface对象也有生命周期:SurfaceView出现在屏幕上时,会创建Surface;
         * SurfaceView从屏幕上消失时,Surface随即被销毁。Surface 不存在时,必须保证没有任何内容要在她上面绘制。
         * 
         * 不像其他视图对象,SurfaceView及其协同工作对象都不会自我绘制内容。对于任何想将内容绘制到Surface缓冲区的对象,
         * 我们称其为Surface的客户端。 在CrimeCameraFragment类中,Camera实例是Surface的客户端。
         * 
         * Surface创建完成后,需要将Camera连接到SurfaceHolder上;Surface销毁后,
         * 再将Camera从SurfaceHolder上断开。
         * SurfaceHolder提供了一个接口:SurfaceHolder.Callback
         * 。该接口监听Surface生命周期中的事件,这样就可以控制Surface 与其客户端协同工作。
         * 
         */
        SurfaceHolder holder = mSurfaceView.getHolder();
        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);// 为了兼容Honeycomb之前版本的设备。

        holder.addCallback(new Callback() {

            /**
             * SurfaceView从屏幕上移除时,Surface也随即被销毁。通过该方法。可以通知Surface的客户端停止使用Surface
             * 。
             */
            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                if (mCamera != null) {
                    mCamera.stopPreview();
                }
            }

            /**
             * 包含SurfaceView的视图层级结构被放到屏幕上时调用该方法。这里也是Surface与其客户端进行关联的地方。
             */
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                if (mCamera != null) {
                    try {
                        mCamera.setPreviewDisplay(holder);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }

            /**
             * Surface首次显示在屏幕上时调用该方法。通过传入的参数,可以知道Surface的像素格式以及它的宽度和高度。该方法内可以通知
             * Surface的客户端,有多大的绘制区域可以使用。
             */
            @Override
            public void surfaceChanged(SurfaceHolder holder, int format,
                    int width, int height) {
                if (mCamera == null)
                    return;

                Parameters parameters = mCamera.getParameters();
                //设置相机预览尺寸
                Size size = getBestSupportedSize(
                        parameters.getSupportedPreviewSizes(), width, height);
                parameters.setPreviewSize(size.width, size.height);
                //设置图片尺寸
                size = getBestSupportedSize(parameters.getSupportedPictureSizes(), width, height);
                parameters.setPictureSize(size.width, size.height);
                mCamera.setParameters(parameters);
                try {
                    mCamera.startPreview();
                } catch (Exception e) {
                    /**
                     * 启动失败时,我们通过异常控制机制释放了相机资源。任何时候,打开相机并完成任务后,必须记得及时释放它,
                     * 即使是在发生异常时。
                     */
                    e.printStackTrace();
                    mCamera.release();
                    mCamera = null;
                }
            }
        });

        return view;
    }

    /**
     * 找出尺寸列表中合适的尺寸。
     */
    private Size getBestSupportedSize(List<Size> sizes, int width, int height) {
        Size bestSize = null;
        for (Size size : sizes) {
            if(size.width == 640 && size.height == 480){
                bestSize = size;
                break;
            }
            if (bestSize == null) {
                bestSize = sizes.get(sizes.size() / 2);
            }
        }

        return bestSize;
    }

    /**
     * 只有在用户能与fragment视图交互时,相机才可以使用。使用完后即使释放。
     */
    @Override
    public void onResume() {
        super.onResume();
        mCamera = Camera.open(0);
    }

    @Override
    public void onPause() {
        super.onPause();
        if (mCamera != null) {
            mCamera.release();
            mCamera = null;
        }
    }
}

启动CrimeCameraActivity并接受照片的地址

mPhotoButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                Intent intent = new Intent(getActivity(), CrimeCameraActivity.class);
                startActivity(intent);
            }
        });

        /**
         * 对于不带相机的设备,拍照按钮应该禁用。
         * FEATURE_CAMERA常量代表后置相机
         * FEATURE_CAMERA_FRONT常量代表前置相机
         */
        PackageManager pm = getActivity().getPackageManager();
        boolean hasACamera = pm.hasSystemFeature(PackageManager.FEATURE_CAMERA) ||
                pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT) ||
                Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD ||
                Camera.getNumberOfCameras() > 0;
        if(!hasACamera)
            mPhotoButton.setEnabled(false);
@Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode != Activity.RESULT_OK)
            return;

        if (requestCode == REQUEST_DATE) {
            Date date = (Date) data
                    .getSerializableExtra(DatePickerFragment.EXTRA_DATE);
            mCrime.setDate(date);
            mDateButton.setText(date.toLocaleString());
        } else if (requestCode == REQUEST_PHOTO) {
            String fileName = data
                    .getStringExtra(CrimeCameraFragment.EXTRA_PHOTO_FILENAME);
            if (fileName != null) {
                Photo photo = new Photo(fileName);
                mCrime.setPhoto(photo);
                showPhoto();
            }
        }
    }
private void showPhoto() {
        Photo photo = mCrime.getPhoto();
        BitmapDrawable b = null;

        if (photo != null) {
            String path = getActivity().getFileStreamPath(photo.getFilename())
                    .getAbsolutePath();
            b = PictureUtils.getScaleDrawable(getActivity(), path);
        }
        mPhotoView.setImageDrawable(b);
    }
package com.huangfei.criminalintent;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.graphics.drawable.BitmapDrawable;
import android.view.Display;
import android.widget.ImageView;

public class PictureUtils {

    /**
     * 将图片压缩到设备默认的显示尺寸
     */
    @SuppressWarnings("deprecation")
    public static BitmapDrawable getScaleDrawable(Activity a, String path){
        Display display = a.getWindowManager().getDefaultDisplay();
        float destWidth = display.getWidth();
        float destHeight = display.getHeight();

        Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(path, options);

        float srcWidth = options.outWidth;
        float srcHeight = options.outHeight;

        int inSampleSize = 1;
        if(srcHeight > destHeight || srcWidth > destWidth){
            if(srcHeight > destHeight){
                inSampleSize = Math.round(srcHeight / destHeight);
            }else {
                inSampleSize = Math.round(srcWidth / destWidth);
            }
        }

        options = new BitmapFactory.Options();
        options.inSampleSize = inSampleSize;

        Bitmap bitmap = BitmapFactory.decodeFile(path, options);
        return new BitmapDrawable(a.getResources(), bitmap);
    }

    /**
     * 清理ImageView的BitmapDrawable
     */
    public static void cleanImageView(ImageView imageView){
        if(!(imageView.getDrawable() instanceof BitmapDrawable))
            return;

        BitmapDrawable b = (BitmapDrawable) imageView.getDrawable();
        b.getBitmap().recycle();
        imageView.setImageBitmap(null);
    }
}

代码地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值