Android-Camera

相机已经成为所有手机中必备的一个程序,在很多地方都必不可少的需要调用Camera,比如拍照,扫描等,我们想在App中使用Camera有两种方式:

  1. 调用系统相机或者是具有相机功能的应用
  2. 在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有什么区别呢?

ViewSurfaceView
主要适用于主动更新的情况主要适用于被动更新,例如频繁地刷新
在主线程中对画面进行刷新通常通过一个子线程来进行页面的刷新
绘图时没有使用双缓冲机制在底层实现机制中就已经实现了双缓冲机制

所以这个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地址:传送门

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值