Intent intent = new Intent();
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
//判断版本
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //如果在Android7.0以上,使用FileProvider获取Uri
intent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
Uri contentUri = FileProvider.getUriForFile(Main2Activity.this, "demo.scroll.com.cameraproject", tempFile);
intent.putExtra(MediaStore.EXTRA_OUTPUT, contentUri);
} else { //否则使用Uri.fromFile(file)方法获取Uri
tempFile = new File(Environment.getExternalStorageDirectory().getPath(), System.currentTimeMillis() + ".jpg");
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(tempFile));
}
if(intent.resolveActivity(getPackageManager())!=null){
startActivityForResult(intent,SYSTEM_CAMERA_CODE);
}
为啥要用两种方式判断SDK 的版本问题呢,这是由于7.0之后调用系统相机会出现问题,崩溃。FileUriExposedException,原因是file:// 不被允许作为一个附加的 Uri 的意图 这样的Uri不在被调用系统相机的intent允许。具体的使用FileProvider的逻辑可以自百度(笔者没有7.0的手机测试)。还有一个坑是
if(data !=null){ //可能尚未指定intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
//返回有缩略图
if(data.hasExtra("data")){
Bitmap thumbnail = data.getParcelableExtra("data");
//得到bitmap后的操作,如压缩处理...
}
}
大部分人通过这种方式去判断调用系统相机回调之后的图片,其实这样是可以判断是否拍照成功的,但是我们只能通过这个方式获取到我们需要的图片的缩略图,去过data去获取Uri是为空的。所以这样是行不通的。
还有一个比较蛋疼的问题,有可能我们通过调用系统相机拍照所得到的照片,在点击拍照后预览是正常的(角度正常没有旋转)但是,Android就是一个开源系统,现在这么多厂商而且那么多手机需要去适配,难免在有些手机上预览正常,但是设置到你的ImageView上之后角度旋转了,是不是觉得很蛋疼呢。网上有很多说法是,可以通过ExifInterface 这个类来获取所拍照片的各种属性值。例如照片的方向。
/**
* 获取图片的旋转角度
* @param imagePath 图片的绝对路径
*/
private int getBitmapDegree(String imagePath) {
int degree = 0;
try {
// 从指定路径下读取图片,并获取其EXIF信息
ExifInterface exifInterface = new ExifInterface(imagePath);
// 获取图片的旋转信息
int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL);
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
degree = 90;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
degree = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
degree = 270;
break;
}
} catch (IOException e) {
e.printStackTrace();
}
return degree;
}
用这个方法获得照片要旋转的角度。在用下面的方法去旋转Bimap的角度设置到我们的ImageView上面。
/**
* 旋转图片
* @param angle 被旋转角度
* @param bitmap 图片对象
* @return 旋转后的图片
*/
public static Bitmap rotaingImageView(int angle, Bitmap bitmap) {
Bitmap returnBm = null;
// 根据旋转角度,生成旋转矩阵
Matrix matrix = new Matrix();
matrix.postRotate(angle%360);
try {
// 将原始图片按照旋转矩阵进行旋转,并得到新的图片
returnBm = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
} catch (OutOfMemoryError e) {
}
if (returnBm == null) {
returnBm = bitmap;
}
if (bitmap != returnBm) {
bitmap.recycle();
}
return returnBm;
}
是不是感觉很完美呢,但是事实可能并不能如我们所愿,在我用这种方法尝试的时候上面获取到要旋转的角度一直为0,并且我的测试手机就是照片旋转的。是不是很无语,因此我只能选择自定义相机的方式去实现了。
先直接上代码吧。定义一个属于自己的CameraView
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
private final String TAG = CameraPreview.class.getSimpleName();
private SurfaceHolder mHolder;
private Camera mCamera;
public CameraPreview(Context context, Camera camera) {
super(context);
mCamera = camera;
mHolder = getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
try {
if(mCamera!=null){
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
}
} catch (IOException e) {
Log.d(TAG, "Error setting camera preview: " + e.getMessage());
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if (mHolder.getSurface() == null){
return;
}
try {
mCamera.stopPreview();
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
mCamera.autoFocus(null);
} catch (Exception e){
Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
}
基本的相机的定义需要的就是最原始的这一些代码。
mCamera.setPreviewDisplay(mHolder);
其实就是将相机所预览的对象放在SurfaceHolder中进行预览,这些其实都可以在Android的开发文档里面看到,里面有具体的实例https://developer.android.google.cn/guide/topics/media/camera,最主要的我觉得是相机的一些参数的处理或者设置。
一般我们将我们CameraView所在的Activity设置位
android:screenOrientation="portrait"
一个具体的朝向,横向或者纵向,这样防止我们在使用自定义相机的时候,在预览的时候,横屏或者竖屏拍照的切换,导致activity重建,给人一种不好的体验。例如上面我们设置的activity一直位竖屏,这个牵扯到我们后面手机所拍出的照片的角度调整。在代码中我们可以使用
getWindowManager().getDefaultDisplay().getRotation()
来获取我们相机的出事旋转角度。贴出代码然后结合代码讲解吧。
public class MainActivity extends Activity {
private static final String TAG = MainActivity.class.getSimpleName();
private static final int MY_PERMISSIONS_REQUEST_CAMERA =3 ;
private Camera mCamera;
private CameraPreview mPreview;
private View confirm_cancle_layout;
private Button captureButton,cancel_btn,confirm_btn;
private String mFilePath = "";
private OrientationEventListener mOrientationEventListener;
private Camera.CameraInfo mCameraInfo = new Camera.CameraInfo();
private static final int LANDSCAPE_90 = 90;
private static final int LANDSCAPE_270 = 270;
private static final int FACING_BACK = 0;
private static final int FACING_FRONT = 1;
private static final int INVALID_CAMERA_ID = -1;
private Camera.Parameters mParameters;
private int mCameraId;
private Display display;
private int mLastKnownRotation = -1;
static final SparseIntArray DISPLAY_ORIENTATIONS = new SparseIntArray();
private int screenOretation = 0;
private int onClickOrtation =0;
private boolean isHasInited = false;
static {
DISPLAY_ORIENTATIONS.put(Surface.ROTATION_0, 0);
DISPLAY_ORIENTATIONS.put(Surface.ROTATION_90, 90);
DISPLAY_ORIENTATIONS.put(Surface.ROTATION_180, 180);
DISPLAY_ORIENTATIONS.put(Surface.ROTATION_270, 270);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getWindowManager().getDefaultDisplay();
display = getWindowManager().getDefaultDisplay();
}
private Camera.PictureCallback mPicture = new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
File pictureFile = SDcardUtil.getOutputMediaFile(SDcardUtil.MEDIA_TYPE_IMAGE);
if (pictureFile == null){
Log.d(TAG, "Error creating media file, check storage permissions: ");
return;
}
mFilePath = pictureFile.getAbsolutePath();
try {
FileOutputStream fos = new FileOutputStream(pictureFile);
fos.write(data);
fos.close();
} catch (FileNotFoundException e) {
Log.d(TAG, "File not found: " + e.getMessage());
} catch (IOException e) {
Log.d(TAG, "Error accessing file: " + e.getMessage());
}
}
};
//检测是否有相机的硬件
private boolean checkCameraFeature(){
return getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
}
@Override
protected void onResume() {
super.onResume();
if(checkCameraFeature()){
if(PERMISSION_GRANTED != ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA)){
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA},
MY_PERMISSIONS_REQUEST_CAMERA);
return;
}
if(!isHasInited){
initCamera();
initView();
isHasInited = true;
}else{
mCamera.stopPreview();
}
}
}
public void initCamera(){
try {
chooseCamera();
/* if (mCamera != null) {
mCamera.stopPreview();//停止预览
mCamera.release();//释放相机资源
mCamera = null;
}*/
mCamera = Camera.open(mCameraId);
mParameters = mCamera.getParameters();
int screenWidth = display.getWidth();
int screenHeight = display.getHeight();
mParameters.setPictureSize(screenWidth, screenHeight);
mParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
}
catch (Exception e){
e.printStackTrace();
}
if(mOrientationEventListener==null){
mOrientationEventListener = new OrientationEventListener(this, SensorManager.SENSOR_DELAY_NORMAL) {
@Override
public void onOrientationChanged(int orientation) {
//只检测是否有四个角度的改变
if (orientation > 350 || orientation < 10) { //0度
screenOretation = 0;
} else if (orientation > 80 && orientation < 100) { //90度
screenOretation = 90;
} else if (orientation > 170 && orientation < 190) { //180度
screenOretation = 180;
} else if (orientation > 260 && orientation < 280) { //270度
screenOretation = 270;
}
if(mCamera!=null && mParameters!=null){
setCameraDisplayOrientation(orientation);
}
}
};
if (mOrientationEventListener.canDetectOrientation()) {
Log.v(TAG, "Can detect orientation");
mOrientationEventListener.enable();
} else {
Log.v(TAG, "Cannot detect orientation");
mOrientationEventListener.disable();
}
}
}
/* @Override
protected void onDestroy() {
super.onDestroy();
}*/
@Override
public void finish() {
mCamera.stopPreview();//停止预览
mCamera.release();//释放相机资源
mCamera = null;
super.finish();
}
private void initView(){
mPreview = new CameraPreview(this, mCamera);
FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
preview.addView(mPreview);
captureButton = (Button) findViewById(R.id.button_capture);
captureButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
onClickOrtation = screenOretation+mCameraInfo.orientation;
Log.i("*********","onClickOrtation==="+onClickOrtation+" result ======="+mCameraInfo.orientation);
if(mCamera!=null){
mCamera.takePicture(null, null, mPicture);
captureButton.setVisibility(View.GONE);
confirm_cancle_layout.setVisibility(View.VISIBLE);
}
}
}
);
confirm_cancle_layout = findViewById(R.id.confirm_cancle_layout);
cancel_btn = (Button) findViewById(R.id.cancel_btn);
confirm_btn = (Button) findViewById(R.id.confirm_btn);
cancel_btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mCamera.stopPreview();
mCamera.startPreview();
confirm_cancle_layout.setVisibility(View.GONE);
captureButton.setVisibility(View.VISIBLE);
}
});
confirm_btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, Main2Activity.class);
intent.putExtra("file_path",mFilePath);
intent.putExtra("rotation",onClickOrtation);
setResult(RESULT_OK,intent);
MainActivity.this.finish();
}
});
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_CAMERA:
initCamera();
break;
}
}
public void setCameraDisplayOrientation(int orientation) {
if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN ||
display == null) {
return;
}
int rotation = display.getRotation();
if(mLastKnownRotation != rotation){
mLastKnownRotation = rotation;
}
Log.i(TAG,"rotation==="+rotation+" mCameraInfo.orientation ======="+mCameraInfo.orientation);
int result = calcDisplayOrientation(DISPLAY_ORIENTATIONS.get(rotation));
Log.i(TAG,"rotation==="+rotation+" result ======="+result);
if(mCamera!=null){
mCamera.setDisplayOrientation(result);
}
}
private int calcDisplayOrientation(int screenOrientationDegrees) {
if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
return (360 - (mCameraInfo.orientation + screenOrientationDegrees) % 360) % 360;
} else { // back-facing
return (mCameraInfo.orientation - screenOrientationDegrees + 360) % 360;
}
}
private void chooseCamera() {
for (int i = 0, count = Camera.getNumberOfCameras(); i < count; i++) {
Camera.getCameraInfo(i, mCameraInfo);
if (mCameraInfo.facing == FACING_BACK) {
mCameraId = i;
return;
}
}
mCameraId = INVALID_CAMERA_ID;
}
挑几个重要的变量讲解一下
private OrientationEventListener mOrientationEventListener; //一个监听屏旋转变化角度的变量
if(mOrientationEventListener==null){
mOrientationEventListener = new OrientationEventListener(this, SensorManager.SENSOR_DELAY_NORMAL) {
@Override
public void onOrientationChanged(int orientation) {
//只检测是否有四个角度的改变
if (orientation > 350 || orientation < 10) { //0度
screenOretation = 0;
} else if (orientation > 80 && orientation < 100) { //90度
screenOretation = 90;
} else if (orientation > 170 && orientation < 190) { //180度
screenOretation = 180;
} else if (orientation > 260 && orientation < 280) { //270度
screenOretation = 270;
}
if(mCamera!=null && mParameters!=null){
setCameraDisplayOrientation(orientation);
}
}
};
if (mOrientationEventListener.canDetectOrientation()) {
Log.v(TAG, "Can detect orientation");
mOrientationEventListener.enable();
} else {
Log.v(TAG, "Cannot detect orientation");
mOrientationEventListener.disable();
}
}
代码段中根据范围,将屏幕角度分为0,90,180,270,其实还有360也就是与0度重合而已,因为拍照的时候,无论我们拍照的角度是怎么样,他只可能在正确的角度的基础上旋转这几个角度中的一个,不可能只出现旋转60度(除非自己特殊处理)这也就是,我们其实在最开始定义的
static {
DISPLAY_ORIENTATIONS.put(Surface.ROTATION_0, 0);
DISPLAY_ORIENTATIONS.put(Surface.ROTATION_90, 90);
DISPLAY_ORIENTATIONS.put(Surface.ROTATION_180, 180);
DISPLAY_ORIENTATIONS.put(Surface.ROTATION_270, 270);
}
屏幕方向只有这四个值其中的一个一样类似的意思。
public void setCameraDisplayOrientation(int orientation){ } //这个是根据屏幕旋转角度和本身自己的角度设置相机照片的预览角度
在最后点击拍照按钮的时候我们记录下的角度是
onClickOrtation = screenOretation+mCameraInfo.orientation;
这个OnClickOrtation其实是最后我们自定义相机拍出来的图片需要我们自己处理之后,才能显示正常朝向的图片所需要旋转的角度额。
图片旋转的处理方法
/**
* 旋转图片
* @param angle 被旋转角度
* @param bitmap 图片对象
* @return 旋转后的图片
*/
public static Bitmap rotaingImageView(int angle, Bitmap bitmap) {
Bitmap returnBm = null;
// 根据旋转角度,生成旋转矩阵
Matrix matrix = new Matrix();
matrix.postRotate(angle%360);
try {
// 将原始图片按照旋转矩阵进行旋转,并得到新的图片
returnBm = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
} catch (OutOfMemoryError e) {
}
if (returnBm == null) {
returnBm = bitmap;
}
if (bitmap != returnBm) {
bitmap.recycle();
}
return returnBm;
}
在这里其实我最想记录的就是得到正确朝向图片我们所需要处理的问题,关键是怎么样正确得到图片所需要旋转的角度,当然如果能够直接通过上面的
getBitmapDegree(String path)
方法获取所需要旋转的角度更好。
当然如果有什么不懂或者觉得不好实现的,完全可以去借鉴google/CameraView来去设置相机的参数和属性,比如预览的大小,自动聚焦,相机的质量问题,也可以借鉴其中的代码自己扩展,还实现了Camera,Camera2比如说点击某点聚焦等等,相信这里面都有很好的答案。代码传送门