相机已经成为所有手机中必备的一个程序,在很多地方都必不可少的需要调用Camera,比如拍照,扫描等,我们想在App中使用Camera有两种方式:
- 调用系统相机或者是具有相机功能的应用
- 在App中自定义相机
调用系统相机
第一种方法就特别简单了,我们直接新增一个按钮,然后监听点击事件,点击事件中只要new一个Intent,然后开启Activity就好了。
package com.gin.xjh.camera;
import android.content.Intent;
import android.graphics.YuvImage;
import android.provider.MediaStore;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void StartCamera(View view) {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivity(intent);
}
}
我们现在就可以使用系统相机进行拍照了,但是拍完了我们怎么在APP中查看照片呢,我们总不可能为了看一张照片有到系统图库里面看吧,所以接下来我们就来说下怎么在APP中看到照片。
这里我们就要用到startActivityForResult()了,因为通过startActivityForResult()启动的,就会自动调用onActivityResult()方法,然后我们重写onActivityResult()方法就可以让图片进行显示了。
case R.id.StartCamera1:
Intent intent1 = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent1, REQ_1);
break;
我们传入了REQ_1这个标记,然后我们在onActivityResult()中判断是不是这个就好了。
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {//判断是返回的事数据
if (requestCode == REQ_1) {//判断是我们之前的intent返回的数据
Bundle bundle = data.getExtras();
Bitmap bitmap = (Bitmap) bundle.get("data");//从data中获取数据
mImageView.setImageBitmap(bitmap);
}
}
}
这样我们就可以把图片显示出来了,有人会问在Bundle中的data对应的数据是什么意思,其实data是bundle中封装一个数据的字段,直接获取该属性就可以得到图片的数据,可以这样理解就好了。然后获取了数据了以后转成Bitmap展示出来就好了。
然后其实我们会发现现在展示出来的图片其实是缩略图(数据中传回来的本身就是缩略图),并不是那么清楚,所以我们想怎么才能显示原图呢,我们可以发现,如果我们在ImageView中直接展示一个文件中的图片,这样就是原图了,那样我们怎么指定拍摄的存储路径呢,我们先获取到SD卡的路径,然后新建一个temp.jpg文件,然后生成Uri,然后用Intent中的一个属性,我们把MediaStore.EXTRA_OUTPUT对应的数据附带Uri进去就好了,这样我们就可以把拍到的图片存储到我们之前指定的路径下面去,但是因为是Android7.0的环境之下,已经不允许在app中把file://Uri暴露给其他app,包括但不局限于通过Intent或ClipData 等方法。 所以我们就不能直接用Uri.fromFile(File)方法了,我们要通过FileProvider来获取Uri,关于这部分可以看我另一篇博客:传送门,之后和前面一样就好了。
case R.id.StartCamera2:
Uri uri;
mFilePath = Environment.getExternalStorageDirectory().getPath();//获得SD卡路径
mFilePath = mFilePath + "/temp.jpg";
Intent intent2 = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (Build.VERSION.SDK_INT >= 24) {
uri = FileProvider.getUriForFile(this,"com.gin.xjh.camera.provider", new File(mFilePath));
} else {
uri = Uri.fromFile(new File(mFilePath));
}
intent2.putExtra(MediaStore.EXTRA_OUTPUT, uri);
startActivityForResult(intent2, REQ_2);
break;
我们既然已经知道了路径,然后通过数据流读取出图片就好了(记得用完以后把流关闭)。
else if (requestCode == REQ_2) {
FileInputStream fis = null;
try {
fis = new FileInputStream(mFilePath);
Bitmap bitmap = BitmapFactory.decodeStream(fis);
mImageView.setImageBitmap(Bitmap.createScaledBitmap(bitmap, 800, 800, true));//压缩图片
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
因为图片可能过大,所以我们要在新建Bitmap的时候对他的大小进行限制就好了。这样我们就很好的显示了原图。
自定义相机
我们想的是需要一个Button来控制拍照,那用什么东西来展示Camera现在所拍到的东西呢,我们肯定不能用普通的View,因为Camera是要实时更新的,如果我们每次都重新绘制的话,那样就会阻塞UI线程,因为一般的View想要更新的话一定是要在UI线程中更新的,所以这里我们就要使用SurfaceView这个控件了,那什么是SurfaceView呢?
Surface意为表层、表面,顾名思义SurfaceView就是指一个在表层的View对象。为什么说是在表层呢,这是因为它有点特殊跟其他View不一样,其他View是绘制在“表层”的上面,而它就是充当“表层”本身。SDK的文档说到:SurfaceView就是在窗口上挖一个洞,它就是显示在这个洞里,其他的View是显示在窗口上,所以View可以显式在SurfaceView之上,你也可以添加一些层在SurfaceView之上。(摘自博客:传送门)
其实说View和SurfaceView有什么区别呢?
View | SurfaceView |
---|---|
主要适用于主动更新的情况 | 主要适用于被动更新,例如频繁地刷新 |
在主线程中对画面进行刷新 | 通常通过一个子线程来进行页面的刷新 |
绘图时没有使用双缓冲机制 | 在底层实现机制中就已经实现了双缓冲机制 |
所以这个SurfaceView控件就是在Android中多用于游戏开发,图片和视频的播放等。所以我们用它来展现Camera所实时拍摄到的东西,这里我就不细说它的使用了,可以看这篇博客:传送门
然后我们进行布局问价的编写:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/Capture"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Capture"/>
<SurfaceView
android:id="@+id/preview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
实现取景效果:
我们先实现几个方法:获取Camera对象,开始预览相机内容,释放相机资源
开始浏览相机内容我们先不用去了解,因为一部手机就只有一个Camera,所以我们对Camera的占用是一定要绑定,以及释放的,因为不释放的话,以后的关于Camera的APP就都没有用了。
/**
* 获取Camera对象
* @return
*/
private Camera getCamera() {
Camera camera;
try {
camera = Camera.open();
} catch (Exception e){
camera=null;
e.printStackTrace();
}
return camera;
}
/**
* 释放相机资源
*/
private void releaseCamera(){
if(mCamera!=null){
mCamera.setPreviewCallback(null);//将相机的回调置空,取消绑定
mCamera.stopPreview();//取消相机的取景功能
mCamera.release();//释放相机所占有的系统资源
mCamera=null;
}
}
然后我们就要把这些方法与Activity的生命周期进行绑定,在Activity启动的时候进行绑定,在暂停的时候对Camera进行释放
@Override
protected void onResume() {
super.onResume();
if(mCamera==null){
mCamera=getCamera();
if(mHolder!=null){
setStartPreview(mCamera,mHolder);
}
}
}
@Override
protected void onPause() {
super.onPause();
releaseCamera();
}
当然mHolder是什么呢,看过SurfaceView使用的可以知道,我们需要实现SurfaceHolder.Callback接口,然后我们就会要重写下面几个方法:
@Override
public void surfaceCreated(SurfaceHolder holder) {
setStartPreview(mCamera,mHolder);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
mCamera.stopPreview();
setStartPreview(mCamera,mHolder);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
releaseCamera();
}
就是关于SurfaceView的创建,改变,销毁的过程,我们只要在这个生命周期中也加上相对应的对Camera的使用以及释放的操作就好了。
然后就开始我们的重中之重,开始预览相机内容:
/**
* 开始预览相机内容
*/
private void setStartPreview(Camera camera,SurfaceHolder holder){
try {
camera.setPreviewDisplay(holder);//绑定操作
camera.setDisplayOrientation(90);//最初系统的Camera的预览角度是横屏状态,把他旋转90度就是竖屏状态
camera.startPreview();//开始预览相机
} catch (IOException e) {
e.printStackTrace();
}
}
这里我们就要知道一个关于Camera显示的东西,因为Camera的图像是默认横屏的,所以我们要让它是竖屏的就要把Camera旋转90度,只要就行了。然后我们实现取景的效果了。
实现拍照功能
这里我们就要搞清楚一些参数以及回调方法了,因为拍照我们就是点击拍照按钮就好了,但是拍摄照片要清晰说一定要对焦的,所以我们要实现自动对焦,这里就涉及到了参数的设置了。我们来看一下点击事件:
public void onClick(View v) {
Camera.Parameters parameters = mCamera.getParameters();
parameters.setPictureFormat(ImageFormat.JPEG);//图片格式
parameters.setPictureSize(800,400);//图片大小
parameters.setFocusMode(Camera.Parameters.FLASH_MODE_AUTO);//设置相机对焦(自动对焦要相机是支持自动对焦的)
mCamera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
if(success){//success对焦是否准确
mCamera.takePicture(null,null,mPictureCallback);
}
}
});
}
这里面我们设定了一些参数,比如图片格式,图片大小,设置相机对焦,然后判断释放自动对焦完成,如果完成了,就直接调用Camera的takePicture()方法,这里我们怎么报错照片呢,我们看传入参数,是有一个回调的,我们new一个PictureCallback,在里面重写onPictureTaken()方法:
private Camera.PictureCallback mPictureCallback=new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {//data是完整的图片数据,并非是缩略图
File tempFile = new File("/sdcard/temp.png");
try {
FileOutputStream fos = new FileOutputStream(tempFile);
fos.write(data);//把data数据写入输出流
fos.close();//关闭输出流
Intent intent = new Intent(CustomCamera.this,ResultAty.class);
intent.putExtra("picPath",tempFile.getAbsolutePath());//把文件的绝对路径写入intent的picPath字段中
startActivity(intent);
CustomCamera.this.finish();//关闭
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
};
这里面传入的参数data就是图片的数据,我们直接通过输出流,把byte[]中的数据传入我们new的文件中就好了,然后把绝对路径传入Intent,然后在另一个页面显示就好了。
实现照片显示
在里面我们就只要从Intent中获得路径,然后把路径中的图片显示到ImageView就好了,
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.result);
String path = getIntent().getStringExtra("picPath");
imageView = findViewById(R.id.pic);
Bitmap bitmap = BitmapFactory.decodeFile(path);
imageView.setImageBitmap(bitmap);
}
但是我们会发现,这个图片显示是有问题的,变成横向的了,所以我们要对图片进行旋转以后再处理,我们把下面这段代码
Bitmap bitmap = BitmapFactory.decodeFile(path);
imageView.setImageBitmap(bitmap);
替换成后面那个就好了
try {
FileInputStream fis = new FileInputStream(path);
Bitmap bitmap = BitmapFactory.decodeStream(fis);
Matrix matrix = new Matrix();
matrix.setRotate(90);
bitmap = Bitmap.createBitmap(bitmap,0,0,bitmap.getWidth(),bitmap.getHeight(),matrix,true);
imageView.setImageBitmap(bitmap);
fis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
我们通过新建一个Bitmap,然后通过矩阵让他旋转一下就好了,记得关闭输入流。
最后的效果就是这样了(提醒:Android7.0及以上一定要注意权限问题,对于这个还不了解的可以看我的博客:传送门):
Github地址:传送门