基于google sample的拍照框架

307 阅读4分钟

项目地址

github.com/peiqingshen…

前言

开发需求,需要自己写一个拍照界面完成相机功能,也看了许多网上写好的拍照框架,多多少少存在一些问题,要么分辨率不能调整,要么机型不能适配完整,要么拍的照片被翻转了,无奈自己写了一个拍照界面,还算不错,能完美运行,许多机型上运行了也未出现bug,后来看到了github 上google的官方案例的拍照,根据之前所写,结合成这个拍照框架,解决了Google samle的一些bug

使用

相机权限和闪光灯权限 读写存储卡权限

<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.FLASHLIGHT"/>
<uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

主要用到两个类

  1. TakePhotoActivity.java 拍照界面类
<activity
    android:name="com.google.android.activity.TakePhotoActivity"
    android:screenOrientation="portrait"
    android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
    android:windowSoftInputMode="stateAlwaysHidden"/>
package com.google.android.activity;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.view.View;
import android.widget.ImageView;

import android.widget.Toast;

import com.google.android.entity.CameraParam;
import com.google.android.view.CameraView;
import com.google.android.view.CaptureLayout;
import com.google.android.view.R;
import com.google.android.listener.CaptureListener;
import com.google.android.listener.TypeListener;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;


/**
 * This demo app saves the taken picture to a constant file.
 * $ adb pull /sdcard/Android/data/com.google.android.cameraview.demo/files/Pictures/picture.jpg
 */
public class TakePhotoActivity extends Activity implements View.OnClickListener {

    private static final int[] FLASH_OPTIONS = {
            CameraView.FLASH_AUTO,
            CameraView.FLASH_OFF,
            CameraView.FLASH_ON,
    };

    private static final int[] FLASH_ICONS = {
            R.drawable.ic_flash_auto,
            R.drawable.ic_flash_off,
            R.drawable.ic_flash_on,
    };
    private int mCurrentFlash;

    private CameraView mCameraView;
    private ImageView iv_photo, iv_back, iv_flash, iv_switch;
    private CaptureLayout mCaptureLayout;

    private Handler mBackgroundHandler;

    private Bitmap mBitmap;
    private CameraParam photoParam;
    private String picPath;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        try {
            setContentView(R.layout.activity_takephoto);
            setParams();
            mCameraView = (CameraView) findViewById(R.id.camera);
            mCameraView.addCallback(mCallback);
            mCameraView.setPictureHeight((int) photoParam.getPicHight());

            iv_photo = (ImageView) findViewById(R.id.iv_photo);
            iv_back = (ImageView) findViewById(R.id.iv_back);
            iv_flash = (ImageView) findViewById(R.id.iv_flash);
            iv_switch = (ImageView) findViewById(R.id.iv_switch);
            mCaptureLayout = (CaptureLayout) findViewById(R.id.capture_layout);
            ArrayList<View> views = new ArrayList<>();
            views.add(iv_flash);
            views.add(iv_switch);
            mCameraView.setViewsRotation(views);

            iv_back.setOnClickListener(this);
            iv_flash.setOnClickListener(this);
            iv_switch.setOnClickListener(this);

            mCaptureLayout.setCaptureLisenter(new CaptureListener() {
                @Override
                public void takePictures() {
                    if (mCameraView != null) {
                        mCameraView.takePicture();
                    }
                }
            });
            //确认 取消
            mCaptureLayout.setTypeLisenter(new TypeListener() {
                @Override
                public void cancel() {
                    mCaptureLayout.resetCaptureLayout();
                    iv_photo.setVisibility(View.INVISIBLE);
                    mBitmap = null;
                }

                @Override
                public void confirm() {
                    if (mBitmap == null) {
                        return;
                    }
                    mCaptureLayout.setVisibility(View.GONE);
                    save();
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
            Toast.makeText(TakePhotoActivity.this, "打开相机失败,请检查设备和权限!" + getError(e), Toast.LENGTH_SHORT).show();
            onBackPressed();
        }
    }

    private void setParams() {
        photoParam = (CameraParam) getIntent().getSerializableExtra("CameraParam");
        if (photoParam == null) {
            //没有设置,缺省值
            photoParam = new CameraParam();
        }
        if (TextUtils.isEmpty(photoParam.getPicPath())) {
            photoParam.setPicPath(getExternalFilesDir(Environment.DIRECTORY_PICTURES).getPath());
        }
    }


    @Override
    protected void onResume() {
        super.onResume();
        try {
            mCameraView.start();
        } catch (Exception e) {
            e.printStackTrace();
            Toast.makeText(TakePhotoActivity.this, "打开相机失败,请检查设备和权限!" + getError(e), Toast.LENGTH_SHORT).show();
            onBackPressed();
        }
    }

    @Override
    protected void onPause() {
        try {
            mCameraView.stop();
        } catch (Exception e) {
            e.printStackTrace();
            Toast.makeText(TakePhotoActivity.this, "打开相机失败,请检查设备和权限!" + getError(e), Toast.LENGTH_SHORT).show();
            onBackPressed();
        }
        super.onPause();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mBackgroundHandler != null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
                mBackgroundHandler.getLooper().quitSafely();
            } else {
                mBackgroundHandler.getLooper().quit();
            }
            mBackgroundHandler = null;
        }
        if (mBitmap != null) {
            mBitmap = null;
        }
    }


    private Handler getBackgroundHandler() {
        if (mBackgroundHandler == null) {
            HandlerThread thread = new HandlerThread("background");
            thread.start();
            mBackgroundHandler = new Handler(thread.getLooper());
        }
        return mBackgroundHandler;
    }

    private CameraView.Callback mCallback = new CameraView.Callback() {

        @Override
        public void onCameraOpened(CameraView cameraView) {
        }

        @Override
        public void onCameraClosed(CameraView cameraView) {
        }

        @Override
        public void onPictureTaken(CameraView cameraView, final byte[] data) {
            System.gc();
            getBackgroundHandler().post(new Runnable() {
                @Override
                public void run() {
                    try {
                        BitmapFactory.Options options = new BitmapFactory.Options();
                        options.inPreferredConfig = Bitmap.Config.RGB_565;
                        mBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);
                        mBitmap = crop(mBitmap);
                        if (!TextUtils.isEmpty(photoParam.getWatermark())) {
                            mBitmap = addWatermark(mBitmap, photoParam.getWatermark());
                        }
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                iv_photo.setImageBitmap(mBitmap);
                                iv_photo.setVisibility(View.VISIBLE);
                                mCaptureLayout.startAlphaAnimation();
                                mCaptureLayout.startTypeBtnAnimator();
                            }
                        });
                    } catch (final Exception e) {
                        e.printStackTrace();
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                Toast.makeText(TakePhotoActivity.this, "拍照失败,请检查设备和权限!" + getError(e), Toast.LENGTH_SHORT).show();
                                onBackPressed();
                            }
                        });
                    }
                }
            });

        }
    };

    private Bitmap crop(Bitmap bitmap) {
        Matrix matrix = new Matrix();
        float scaleW = photoParam.getPicHight() / bitmap.getWidth();
        float scaleH = photoParam.getPicHight() / bitmap.getHeight();
        float scale = scaleW > scaleH ? scaleH : scaleW;
        //对于尺寸和所给一样,也未旋转的,不能进行添加水印,变大一点点
        scale = scale == 1 ? 1.0001f : scale;
        matrix.postScale(scale, scale);
        float degrees = 0f;
        // 根据拍摄的方向旋转图像(纵向拍摄时要需要将图像旋转90度)
        if (mCameraView.getFacing() == CameraView.FACING_BACK) {
            degrees = (mCameraView.getCurrentOrientation()) * 90;
            //符合16:9的照片尺寸在9:16的情况下顺时针旋转90
            if (bitmap.getWidth() > bitmap.getHeight()) {
                degrees += 90;
            }
        }
        //前置摄像头为镜面显示,如果显示为原生就要相反
        if (mCameraView.getFacing() == CameraView.FACING_FRONT) {
            degrees = -(mCameraView.getCurrentOrientation()) * 90;
            //符合16:9的照片尺寸在9:16的情况下顺时针旋转90
            if (bitmap.getWidth() > bitmap.getHeight()) {
                degrees -= 90;
            }
        }

        matrix.postRotate(degrees);
        return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
    }

    /**
     * 添加水印
     */
    private Bitmap addWatermark(Bitmap cameraBitmap, String watermark) {
        int w = cameraBitmap.getWidth();
        int h = cameraBitmap.getHeight();
        Canvas mCanvas = new Canvas(cameraBitmap);
        TextPaint textPaint = new TextPaint();
        textPaint.setColor(Color.YELLOW);
        textPaint.setTextSize((w > h ? w : h) / 40);
        StaticLayout layout = new StaticLayout(watermark, textPaint, w, Layout.Alignment.ALIGN_NORMAL, 1.0F, 0.0F, true);
        mCanvas.translate(0, h - layout.getHeight());
        layout.draw(mCanvas);
        mCanvas.save(Canvas.ALL_SAVE_FLAG);
        mCanvas.restore();
        return cameraBitmap;
    }

    private void save() {
        getBackgroundHandler().post(new Runnable() {
            @Override
            public void run() {
                File file = new File(photoParam.getPicPath(), photoParam.getPicPre() + System.currentTimeMillis() + ".jpg");
                try {
                    FileOutputStream out = new FileOutputStream(file);
                    mBitmap.compress(Bitmap.CompressFormat.JPEG, 80, out);
                    out.flush();
                    out.close();
                    picPath = file.getAbsolutePath();
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            onBackPressed();
                        }
                    });
                } catch (FileNotFoundException e) {
                    Toast.makeText(TakePhotoActivity.this, "保存照片失败,请检查设备和权限!" + getError(e), Toast.LENGTH_SHORT).show();
                    e.printStackTrace();
                } catch (IOException e) {
                    Toast.makeText(TakePhotoActivity.this, "保存照片失败,请检查设备和权限!" + getError(e), Toast.LENGTH_SHORT).show();
                    e.printStackTrace();
                }
            }
        });
    }

    @Override
    public void onClick(View v) {
        try {
            int i = v.getId();
            if (i == R.id.iv_flash) {
                if (mCameraView != null) {
                    mCurrentFlash = (mCurrentFlash + 1) % FLASH_OPTIONS.length;
                    iv_flash.setImageResource(FLASH_ICONS[mCurrentFlash]);
                    mCameraView.setFlash(FLASH_OPTIONS[mCurrentFlash]);
                }
            } else if (i == R.id.iv_switch) {
                if (mCameraView != null) {
                    int facing = mCameraView.getFacing();
                    mCameraView.setFacing(facing == CameraView.FACING_FRONT ?
                            CameraView.FACING_BACK : CameraView.FACING_FRONT);
                }
            } else if (i == R.id.iv_back) {
                onBackPressed();
            }
        } catch (Exception e) {
            e.printStackTrace();
            Toast.makeText(TakePhotoActivity.this, "打开相机失败,请检查设备和权限!" + getError(e), Toast.LENGTH_SHORT).show();
            onBackPressed();
        }
    }

    @Override
    public void onBackPressed() {
        getIntent().putExtra("result", picPath);
        setResult(RESULT_OK, getIntent());
        super.onBackPressed();
    }


    private String getError(Exception e) {
        return "(" + e.getMessage() + ")";
    }
}

对于所有的异常捕获 以Toast形式告知客户,可以自行修改,拍完保存在本地文件夹,,可后续对拍照文件处理。
2. CameraParam.java


import java.io.Serializable;

/**
 * Created by pei on 2016/7/15.
 */
public class CameraParam implements Serializable {
    private float picHight;//接近于这个图片高度的尺寸
    private int picQuality;//图片质量
    private String picPath;//图片路径
    private String picPre;//前缀名
    private String watermark;// 水印内容 默认时间

    public enum Quantity {Low, Medium, High, VeryHigh}

    public CameraParam() {
        this(Quantity.Medium);//默认中画质
    }

    public CameraParam(Quantity quantity) {
        picPre = "";
        switch (quantity) {
            case Low:
                picHight = 800;
                picQuality = 40;
                break;
            case Medium:
                picHight = 1280;
                picQuality = 60;
                break;
            case High:
                picHight = 1920;
                picQuality = 80;
                break;
            case VeryHigh:
                picHight = 3840;
                picQuality = 100;
                break;
        }
    }

    public float getPicHight() {
        return picHight;
    }

    public void setPicHight(float picHight) {
        this.picHight = picHight;
    }

    public int getPicQuality() {
        return picQuality;
    }

    public void setPicQuality(int picQuality) {
        this.picQuality = picQuality;
    }

    public String getPicPath() {
        return picPath;
    }

    public void setPicPath(String picPath) {
        this.picPath = picPath;
    }

    public String getPicPre() {
        return picPre;
    }

    public void setPicPre(String picPre) {
        this.picPre = picPre;
    }

    public String getWatermark() {
        return watermark;
    }

    public void setWatermark(String watermark) {
        this.watermark = watermark;
    }
}

拍照设置类,作为intent传入具体使用查看github

总结

Google的代码在有些机型上也是有bug的,已做修改 比如SizeMap这个类

        SortedSet<Size> sizes = mRatios.get(ratio);
        //原来是没有null判断的,在某些机型上会报空指针
        if (sizes == null) {
            return mRatios.valueAt(mRatios.size() - 1);
        }
        return sizes;
    }

还有一些bug,测试了许多机型,稳定性还是不错的