前言

首先需要知道的是该博客只是简单的将摄像头打开并进行预览的一个操作,对于横屏竖屏切换的一个判断处理并没有实现,后续会进行完善,但是不会在这个博客中进行说明。

其次在编写之前应该对整个预览过程用到的一些重要方法或重要的类有一个理解!

技术前瞻

1. CameraManager

摄像头管理器,用于打开和关闭系统摄像头

  • getCameraIdList()
    返回当前设备中可用的相机列表

  • getCameraCharacteristics(String cameraId)
    根据摄像头id返回该摄像头的相关信息

  • openCamera(String cameraId, final CameraDevice.StateCallback callback,Handler handler)
    打开指定cameraId的相机。参数callback为相机打开时的回调,参数handler为callback被调用时所在的线程

2. CameraDevice

描述系统摄像头,一般都是用于创建Capture请求以及创建CaptureSession会话

  • createCaptureRequest(int templateType)
    创建一个新的Capture请求。参数templateType代表了请求类型,请求类型一共分为六种,分别为:
  1. TEMPLATE_PREVIEW : 创建预览的请求
  2. TEMPLATE_STILL_CAPTURE: 创建一个适合于静态图像捕获的请求,图像质量优先于帧速率
  3. TEMPLATE_RECORD : 创建视频录制的请求
  4. TEMPLATE_VIDEO_SNAPSHOT : 创建视视频录制时截屏的请求
  5. TEMPLATE_ZERO_SHUTTER_LAG : 创建一个适用于零快门延迟的请求。在不影响预览帧率的情况下最大化图像质量
  6. TEMPLATE_MANUAL : 创建一个基本捕获请求,这种请求中所有的自动控制都是禁用的(自动曝光,自动白平衡、自动焦点)
  • createCaptureSession(List<Surface> outputs,CameraCaptureSession.StateCallback callback,Handler handler)
    创建CaptureSession会话。第一个参数 outputs 是一个 List 数组,相机会把捕捉到的图片数据传递给该参数中的 Surface 。第二个参数 StateCallback 是创建会话的状态回调。第三个参数描述了 StateCallback 被调用时所在的线程。

3. CameraCharacteristics

描述摄像头的各种特性,类似于Camera1中的CamerInfo。通过CameraManager的getCameraCharacteristics(String cameraId)方法来获取

  • get(Key<T> key)
    通过制定的key获取相应的相机参数。

常用的key值有:

  1. CameraCharacteristics.LENS_FACING :
    获取摄像头方向。前置摄像头(LENS_FACING_FRONT)或 后置摄像头(LENS_FACING_BACK)
  2. CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL:
    获取当前设备支持的相机特性
  3. CameraCharacteristics.SENSOR_ORIENTATION:
    获取摄像头方向
  4. CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP:
    获取StreamConfigurationMap,它是管理摄像头支持的所有输出格式和尺寸
  5. CameraCharacteristics.FLASH_INFO_AVAILABLE:
    是否支持闪光灯
  6. CameraCharacteristics.STATISTICS_INFO_MAX_FACE_COUNT:
    同时检测到人脸的数量
  7. CameraCharacteristics.STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES:
    相机支持的人脸检测模式

4. CaptureRequest

描述了一次操作请求,拍照、预览等操作都需要先传入CaptureRequest参数,具体的参数控制也是通过CameraRequest的成员变量来设置

  • addTarget(Surface outputTarget)
    给此次请求添加一个Surface对象作为图像的输出目标

  • set(Key<T> key, T value)
    设置指定的参数值。

// 自动对焦
captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
// 闪光灯
captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH)
// 根据摄像头方向对保存的照片进行旋转,使其为"自然方向"
captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, mCameraSensorOrientation)
// 人脸检测模式
captureRequestBuilder.set(CaptureRequest.STATISTICS_FACE_DETECT_MODE, CameraCharacteristics.STATISTICS_FACE_DETECT_MODE_SIMPLE)

5. CameraCaptureSession

当需要拍照、预览等功能时,需要先创建该类的实例,然后通过该实例里的方法进行控制(例如:拍照 capture())

  • setRepeatingRequest(CaptureRequest request,
    CaptureCallback listener, Handler handler)

    根据传入的 CaptureRequest 对象开始一个无限循环的捕捉图像的请求。第二个参数 listener 为捕捉图像的回调,在回调中可以拿到捕捉到的图像信息

  • capture( CaptureRequest request,
    CaptureCallback listener, Handler handler)

    拍照。第二个参数为拍照的结果回调

6. CaptureResult

描述拍照完成后的结果

7. ImageReader

用于接收拍照结果和访问拍摄照片的图像数据。
得到一个ImageReader对象的方法为newInstance(int width, int height, int format, int maxImages)。前两个参数是保存图片的宽高,第三个参数为保存图片的格式,第四个参数代表用户可以同时访问到的最大图片数量

注意:
这个参数应该根据具体需业务需求尽可能的小,因为它的数值越大意味着需要消耗的内存就越高

  • acquireNextImage()
    得到ImageReader图像队列中的下一张图片,返回值是一个Image对象

8. Image

一个完整的图片缓存

  • getPlanes()
    获取该图像的像素平面数组。这个数组的大小跟图片的格式有关,如 JPEG格式数组大小为1

9. Plane

图像数据的单色平面

  • getBuffer()
    获取包含帧数据的ByteBuffer。通过这个ByteBuffer我们就可以把图片保存下来

知识前瞻部分的内容,看了一篇帖子写的挺好,就搬运过来,这一部分的顺序很大程度的就是映射着开发中使用他们的顺序。

作者:Smashing丶
链接:https://www.jianshu.com/p/0ea5e201260f
来源:简书

代码实现

本次demo实现的是使用viewpage2进行左右滑动切换拍照与录像功能,对于布局文件及布局代码在这里不是重点,我就把它给码下来不进行详细的讲解。

MainActivity布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><androidx.viewpager2.widget.ViewPager2android:id="@+id/camerapage"android:layout_width="match_parent"android:layout_height="match_parent"/></RelativeLayout>

MainActivity的Java文件

package com.yjs.cameraapplication;import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Lifecycle;
import androidx.viewpager.widget.ViewPager;
import androidx.viewpager2.widget.ViewPager2;import android.Manifest;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.view.TextureView;
import android.view.View;
import android.widget.Adapter;import com.yjs.cameraapplication.FragmentPackage.RecorderVideoFragment;
import com.yjs.cameraapplication.FragmentPackage.TakePhotoFragment;import java.util.ArrayList;
import java.util.List;public class MainActivity extends AppCompatActivity {private String[] permissions={Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.RECORD_AUDIO,Manifest.permission.CAMERA};private ViewPager2 viewPager;private List<Fragment> fragments;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);/*修改各种状态栏状态*/changeWindowStatus();/*初始化各种view*/initView();}private void initView() {fragments=new ArrayList<Fragment>();viewPager = findViewById(R.id.camerapage);TakePhotoFragment takePhotoFragment = new TakePhotoFragment();fragments.add(takePhotoFragment);RecorderVideoFragment recorderVideoFragment = new RecorderVideoFragment();fragments.add(recorderVideoFragment);MyViewAdapter myViewAdapter = new MyViewAdapter(getSupportFragmentManager(), getLifecycle(), fragments);viewPager.setAdapter(myViewAdapter);}private void changeWindowStatus() {//隐藏通知栏状态栏if (Build.VERSION.SDK_INT >= 21) {View decorView=getWindow().getDecorView();//获取当前界面的decorViewint option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN|View.SYSTEM_UI_FLAG_FULLSCREEN//隐藏状态栏|View.SYSTEM_UI_FLAG_LAYOUT_STABLE//保持整个View的稳定,使其不会随着SystemUI的变化而变化;|View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION//让导航栏悬浮在Activity上|View.SYSTEM_UI_FLAG_HIDE_NAVIGATION//隐藏导航栏| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;//沉浸模式且状态栏和导航栏出现片刻后会自动隐藏decorView.setSystemUiVisibility(option);getWindow().setStatusBarColor(Color.TRANSPARENT);//设置透明颜色getWindow().setNavigationBarColor(Color.TRANSPARENT);}ActionBar actionBar=getSupportActionBar();actionBar.hide();}
}

使用PageView需要使用AdaptView对每一页进行一个管理

MyViewAdapter文件

package com.yjs.cameraapplication;import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.lifecycle.Lifecycle;
import androidx.viewpager2.adapter.FragmentStateAdapter;import java.util.List;public class MyViewAdapter extends FragmentStateAdapter {private List<Fragment> fragments;public MyViewAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle, List<Fragment> fragmentList) {super(fragmentManager, lifecycle);this.fragments=fragmentList;}@NonNull@Overridepublic Fragment createFragment(int position) {return fragments.get(position);}@Overridepublic int getItemCount() {return fragments.size();}
}

其实对于上面都是基本操作没有什么好说的,但是有一个点可以注意一下,就是对于手机状态栏、功能栏的一些设置。 我在上面的注释中也是表明的比较明白。接下来就是我们核心功能实现代码。

拍照界面Fragment

我们将着重看向拍照界面的预览。首先一个相机预览的流程大概就是初始化TextureView,然后在TextureView的回调中进行设置相机特性,设置完相机特性后将设备相机设备进行打开操作。具体的实现细节将结合代码进行理解。

首先还是最简单的Fragment的布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".FragmentPackage.TakePhotoFragment"><!-- TODO: Update blank fragment layout --><TextureViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:id="@+id/textureView"/><ImageButtonandroid:id="@+id/takePicture"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentBottom="true"android:layout_centerHorizontal="true"android:layout_marginBottom="50dp"android:background="@drawable/shape_white_ring"android:scaleX="0.4"android:scaleY="0.4"android:src="@drawable/cam" /><ImageViewandroid:id="@+id/image_show"android:layout_width="50dp"android:layout_height="50dp"android:layout_alignParentBottom="true"android:layout_marginLeft="50dp"android:layout_marginBottom="50dp" /><ImageButtonandroid:id="@+id/change"android:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@drawable/changecam"android:scaleY="0.2"android:scaleX="0.2"android:background="@drawable/shape_white_ring"android:layout_alignParentBottom="true"android:layout_marginRight="50dp"android:layout_marginBottom="50dp"android:layout_alignParentRight="true"/></RelativeLayout>

实现预览主要还是对TextureView控件的一个操作。我们首先需要在fragment创建View的回调函数中进行控件的绑定,动态授权的操作,以及初始化TextureView

@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {// Inflate the layout for this fragmentView view = inflater.inflate(R.layout.fragment_take_photo, container, false);//控件绑定initView(view);//初始化TextureViewtextureView.setSurfaceTextureListener(textureListener);//动态授权getPermission();return view;}

控件的绑定、动态授权我这边就不在具体的去讲。下面我将动态授权的代码码出来。

private void getPermission() {Log.d(TAG, "getPermission: success");if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {for (String permission : permissions) {if (ContextCompat.checkSelfPermission(getContext(), permission) != PackageManager.PERMISSION_GRANTED) {permissionList.add(permission);}}if (!permissionList.isEmpty()) {//进行授权requestPermissions(permissionList.toArray(new String[permissionList.size()]), 1);} else {//表示全部已经授权//这时候回调一个预览view的回调函数textureView.setSurfaceTextureListener(textureListener);}}}

接下来我们来看看TextureView的初始化代码运行。

//初始化TextureView
 textureView.setSurfaceTextureListener(textureListener);

初始化其实就是设置一个SurfaceTexture的监听者,当其可用的时候进行回调函数

/*SurfaceView状态回调*/TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {@Overridepublic void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {Log.d(TAG, "onSurfaceTextureAvailable: success");//首先就需要设置相机,然后再打开相机setupCamera(width,height);openCamera();}//下面的方法可以先不看,我们先实现相机预览@Overridepublic void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {}@Overridepublic boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {return false;}@Overridepublic void onSurfaceTextureUpdated(SurfaceTexture surface) {}};

可以看到回调函数中其实就做了两件事, 在SufaceTexture可用的时候,设置相机的特性,设置完之后便打开相机相关操作,首先我们先看一下setupCamera方法进行设置相机

private void setupCamera(int width,int height) {Log.d(TAG, "setupCamera: success");CameraManager cameraManager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE);try {String[] cameraIdList = cameraManager.getCameraIdList();for (String cameraId : cameraIdList) {CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId);if (cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) {continue;}StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);//相机支持的所有分辨率,下一步就是获取最合适的分辨率Size[] outputSizes = map.getOutputSizes(SurfaceTexture.class);Size size = getOptimalSize(outputSizes, width, height);previewSize = size;mCameraId = cameraId;break;}} catch (CameraAccessException e) {e.printStackTrace();}}
//选择sizeMap中大于并且接近width和height的sizeprivate Size getOptimalSize(Size[] outputSizes, int width, int height) {Size tempSize=new Size(width,height);List<Size> sizes = new ArrayList<>();for (Size outputSize : outputSizes) {if (width > height) {//横屏的时候if (outputSize.getHeight() > height && outputSize.getWidth() > width) {sizes.add(outputSize);}} else {//竖屏的时候if (outputSize.getWidth() > height && outputSize.getHeight() > width) {sizes.add(outputSize);}}}if (sizes.size() > 0) {//如果有多个符合条件找到一个差距最小的,最接近预览分辨率的tempSize= sizes.get(0);int minnum = 999999;for (Size size : sizes) {int num = size.getHeight() * size.getHeight() - width * height;if (num < minnum) {minnum = num;tempSize = size;}}}return tempSize;/*if (sizes.size() > 0) {return Collections.min(sizes, new Comparator<Size>() {@Overridepublic int compare(Size size, Size t1) {return Long.signum(size.getWidth() * size.getHeight() - t1.getWidth() * t1.getHeight());}});}return outputSizes[0];*/}

看着上面的设置相机部分代码,我们可以知道, 我们通过getActivity().getSystemService(Context.CAMERA_SERVICE)去获得CameraManager去对相机特性进行设置。其中cameraCharacteristics是获取相机的所有特性,例如分辨率、相机前后摄像头等特性的设置。设置相机代码主要设置了后摄的最佳分辨率的一个选择初始化。

设置好相机之后,我们便可以进行打开相机的操作,在打开相机操作的过程中再初始化一下ImageReader为后面拍照保存图片做好准备。

//打开摄像头private void openCamera() {Log.d(TAG, "openCamera: success");CameraManager cameraManager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE);try {if (ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {// TODO: Consider calling//    ActivityCompat#requestPermissions// here to request the missing permissions, and then overriding//   public void onRequestPermissionsResult(int requestCode, String[] permissions,//                                          int[] grantResults)// to handle the case where the user grants the permission. See the documentation// for ActivityCompat#requestPermissions for more details.return;}cameraManager.openCamera(mCameraId, stateCallback, null);} catch (CameraAccessException e) {e.printStackTrace();}}

其实打开相机函数,就是通过CameraManager进行openCamera方法,这个方法有三个参数去进行完善,首先第一个参数是相机的Id,因为一个设备上可能有多个相机设备,刚刚的设置相机中已经提及,第二个参数则是监控相机设备状态的回调,第三个参数是设置该方法在哪个线程进行操作,如果是null则代表是在当前线程下进行opencamera。接下来我们来看一下第二个参数监控相机设备的回调函数。

//摄像头状态回调private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {@Overridepublic void onOpened(@NonNull CameraDevice camera) {Log.d(TAG, "onOpened: success");mCameraDevice = camera;//开启预览startPreview();}@Overridepublic void onDisconnected(@NonNull CameraDevice camera) {Toast.makeText(getContext(), "摄像头设备连接失败", Toast.LENGTH_SHORT).show();}@Overridepublic void onError(@NonNull CameraDevice camera, int error) {Toast.makeText(getContext(), "摄像头设备连接出错", Toast.LENGTH_SHORT).show();}};

当检测到相机设备打开时回调onOpened函数,首先在这时我们可以获得对应的cameradevice对象,再其后开启预览界面startPreview()。

//预览功能private void startPreview() {Log.d(TAG, "startPreview: success");//设置图片阅读器setImageReader();//注意这里:sufacetexture跟surfaceview是两个东西,需要注意!//sufacetexture是textureview的重要属性SurfaceTexture surfaceTexture = textureView.getSurfaceTexture();//设置textureview的缓存区大小surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());//设置surface进行预览图像数据mPreviewSurface = new Surface(surfaceTexture);//创建CaptureRequestsetCaptureRequest();//创建capturesession/*Surface表示有多个输出流,我们有几个显示载体,就需要几个输出流。对于拍照而言,有两个输出流:一个用于预览、一个用于拍照。对于录制视频而言,有两个输出流:一个用于预览、一个用于录制视频。*/// previewSurface 用于预览, mImageReader.getSurface() 用于拍照try {mCameraDevice.createCaptureSession(Arrays.asList(mPreviewSurface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(@NonNull CameraCaptureSession session) {//当回调创建成功就会调用这个回调mCaptureSession = session;setRepeatCapture();}@Overridepublic void onConfigureFailed(@NonNull CameraCaptureSession session) {}}, null);} catch (CameraAccessException e) {e.printStackTrace();}}

在打开预览界面之前,我们先对ImageReader进行一个初始化,为后续拍照做准备。ImageReader主要时实现对图片的保存,以及通知广播对图库数据库进行一个更新。过程还是比较直白,就直接贴一下代码

//设置图片阅读器private void setImageReader() {Log.d(TAG, "setImageReader: success");//创建ImageReader实例,接下来应该是设置一些属性参数mImageReader = ImageReader.newInstance(previewSize.getWidth(), previewSize.getHeight(), ImageFormat.JPEG, 1);//果然跟我想的一样,接下来是设置监听当图片流可用的时候的监听器,即为拍照之后产生照片流mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {@Overridepublic void onImageAvailable(ImageReader reader) {Image image = reader.acquireLatestImage();//进行保存图片,开一个线程进行保存ImageSaver imageSaver = new ImageSaver(getContext(), image);new Thread(imageSaver).start();Toast.makeText(getContext(), "保存图片成功", Toast.LENGTH_SHORT).show();}}, null);}
//创建一个图片保存类public class ImageSaver implements Runnable {private Context context;private Image image;public ImageSaver(Context context, Image image) {Log.d(TAG, "ImageSaver: success");this.context = context;this.image = image;}@Overridepublic void run() {Image.Plane[] planes = image.getPlanes();ByteBuffer buffer = planes[0].getBuffer();byte[] bytes = new byte[buffer.remaining()];buffer.get(bytes);System.out.println(planes);String filname = Environment.getExternalStorageDirectory() + "/DCIM/Camera/"  + System.currentTimeMillis() + ".jpg";File file = new File(filname);FileOutputStream fileOutputStream=null;try {//保存图片fileOutputStream = new FileOutputStream(file);fileOutputStream.write(bytes,0,bytes.length);} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}finally {if (fileOutputStream!=null){try {fileOutputStream.close();} catch (IOException e) {e.printStackTrace();}}//最后还要广播通知相册更新数据库notiBroadcast();//保存操作结束后,需要用handle进行主线程数据的更新Message message = new Message();message.what=0;Bundle bundle = new Bundle();bundle.putString("path",filname);message.setData(bundle);handler.sendMessage(message);//image也算是个流也需要进行关闭,否则可能拍照的时候会报错image.close();}}}
private void notiBroadcast(){String path = Environment.getExternalStorageDirectory() + "/DCIM/";Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);Uri uri = Uri.fromFile(new File(path));intent.setData(uri);getContext().sendBroadcast(intent);}
private void setLastImagePath(String path){Log.d(TAG, "setLastImagePath: success");//拿到最后一张拍照照片Bitmap bitmap = BitmapFactory.decodeFile(path);mImageView.setImageBitmap(bitmap);}
private Handler handler=new Handler(Looper.myLooper()){@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);if (msg.what==0){Bundle pathdata = msg.getData();String path = (String) pathdata.get("path");imageList.add(path);//设置拍照界面显示的一个图片(左下角的图片预览)setLastImagePath(path);}}};

设置好ImageReader之后,以及对Surface进行设置尺寸大小之后,便需要setCaptureRequest();还没讲解setCaptureRequest()之前,可能会疑问Surface是拿来干什么的,接下来,我把setCaptureRequest()码出来就好理解很多。

//无论是预览还是拍照都需要设置capturerequestprivate void setCaptureRequest(){try {mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);mPreviewRequestBuilder.addTarget(mPreviewSurface);//手动对焦,还不会/*TODO*/// 自动对焦mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);// 闪光灯mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);// 人脸检测模式mPreviewRequestBuilder.set(CaptureRequest.STATISTICS_FACE_DETECT_MODE, CameraCharacteristics.STATISTICS_FACE_DETECT_MODE_SIMPLE);} catch (CameraAccessException e) {e.printStackTrace();}}

可以看到我们设计的相机怎么知道预览界面的,便是通过 mPreviewRequestBuilder.addTarget(mPreviewSurface)进行一个设置,然后builder还可以对自动对焦、闪光灯等进行一个设置,当builder都设置好之后,便可以调用build进行captureRequire的生成。创建好captureRequire之后我们便需要capturesession去调用预览方法,预览方法中需要captureRequire,其实整个过程都是迭迭相加的。

private void setRepeatCapture(){Log.d(TAG, "setRepeatCapture: success");mPreviewRequestBuilder.setTag(TAG);//首先要知道整个调用顺序 devices创建出capturebuilder,当builder设置好各种参数之后,就可以build出capturerequiremPreviewRequest=mPreviewRequestBuilder.build();//session中需要用到capturerequiretry {mCaptureSession.setRepeatingRequest(mPreviewRequest, new CameraCaptureSession.CaptureCallback() {@Overridepublic void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {super.onCaptureCompleted(session, request, result);}},null);} catch (CameraAccessException e) {e.printStackTrace();}}

致辞预览就简单实现了,但是我个人文字逻辑并不清晰,估计这个就我自己能看懂,我就把拍照的Fragement的所有代码进行码。大家一步一步慢慢看,总能看懂。

package com.yjs.cameraapplication.FragmentPackage;import android.Manifest;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.SurfaceTexture;
import android.graphics.drawable.Drawable;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.util.Size;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.Toast;import com.yjs.cameraapplication.MainActivity;
import com.yjs.cameraapplication.R;
import com.yjs.cameraapplication.SelfView.MyImageView;import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.sql.Time;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.TimeUnit;/*** A simple {@link Fragment} subclass.* Use the {@link TakePhotoFragment#newInstance} factory method to* create an instance of this fragment.*/
public class TakePhotoFragment extends Fragment implements View.OnClickListener {private static final String TAG = "TakePhotoFragment";private static final String ARG_PARAM1 = "param1";private static final String ARG_PARAM2 = "param2";private String mParam1;private String mParam2;/*fragment空间声明*/private TextureView textureView;private ImageButton takePhotoBtn;private ImageView mImageView;/*private MyImageView mImageView;*/private ImageButton changeCamBtn;/*除此之外,还需要一些参数*/private String mCameraId; //摄像头IDprivate Size previewSize; //预览分辨率private ImageReader mImageReader; //图片阅读器private static CameraDevice mCameraDevice;   //摄像头设备private static CameraCaptureSession mCaptureSession;   //获取会话private CaptureRequest mPreviewRequest;      //获取预览请求private CaptureRequest.Builder mPreviewRequestBuilder;   //获取到预览请求的Builder通过它创建预览请求private Surface mPreviewSurface;  //预览显示图//新建一个权限链private String[] permissions = {Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO};private List<String> permissionList = new ArrayList();//添加一个图片集合List<String> imageList = new ArrayList<>();private Boolean isCreated=false;private Boolean isLeave=false;public TakePhotoFragment() {// Required empty public constructor}/*** Use this factory method to create a new instance of* this fragment using the provided parameters.** @param param1 Parameter 1.* @param param2 Parameter 2.* @return A new instance of fragment TakePhotoFragment.*/// TODO: Rename and change types and number of parameterspublic static TakePhotoFragment newInstance(String param1, String param2) {TakePhotoFragment fragment = new TakePhotoFragment();Bundle args = new Bundle();args.putString(ARG_PARAM1, param1);args.putString(ARG_PARAM2, param2);fragment.setArguments(args);return fragment;}@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);if (getArguments() != null) {mParam1 = getArguments().getString(ARG_PARAM1);mParam2 = getArguments().getString(ARG_PARAM2);}}@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {Log.d(TAG, "onCreateView: success");// Inflate the layout for this fragmentView view = inflater.inflate(R.layout.fragment_take_photo, container, false);initView(view);textureView.setSurfaceTextureListener(textureListener);//动态授权getPermission();isCreated=true;return view;}//看看viewpage对fragment的生命周期的影响@Overridepublic void onPause() {super.onPause();Log.d(TAG, "onPause: success");closeCamera();System.out.println(textureView.isAvailable());if (textureView.isAvailable()){//如果可用,就是除了进行切换pageview之外的所有操作isLeave=true;}else {//不可用说明就是直接切换了pageviewcloseCamera();}}@Overridepublic void onStop() {super.onStop();Log.d(TAG, "onStop: success");}@Overridepublic void onDestroyView() {super.onDestroyView();Log.d(TAG, "onDestroyView: success");}@Overridepublic void onDestroy() {super.onDestroy();Log.d(TAG, "onDestroy: success");}@Overridepublic void onResume() {super.onResume();Log.d(TAG, "onResume: success");System.out.println("++++++++++");System.out.println(textureView.isAvailable());if (textureView.isAvailable()){if (isLeave){//息屏等操作会自动关闭camera,所以就得手动再打开一次openCamera();isLeave=false;}}}@Overridepublic void onStart() {super.onStart();Log.d(TAG, "onStart: success");/*if (textureView.isAvailable()){openCamera();}*/}@Overridepublic void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);Log.d(TAG, "onViewCreated: success");}private void closeCamera(){Log.d(TAG, "closeCamera: success");//首先要关闭sessionif (mCaptureSession!=null){mCaptureSession.close();}if (mCameraDevice!=null){mCameraDevice.close();}}//绑定控件private void initView(View view) {textureView = view.findViewById(R.id.textureView);takePhotoBtn = view.findViewById(R.id.takePicture);mImageView = view.findViewById(R.id.image_show);changeCamBtn = view.findViewById(R.id.change);changeCamBtn.setOnClickListener(this);}private void getPermission() {Log.d(TAG, "getPermission: success");if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {for (String permission : permissions) {if (ContextCompat.checkSelfPermission(getContext(), permission) != PackageManager.PERMISSION_GRANTED) {permissionList.add(permission);}}if (!permissionList.isEmpty()) {//进行授权ActivityCompat.requestPermissions(getActivity(),permissionList.toArray(new String[permissionList.size()]),1);} else {//表示全部已经授权//这时候回调一个预览view的回调函数textureView.setSurfaceTextureListener(textureListener);}}}//只能写在Activity中,下次把授权写到activity中,减少麻烦/*@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {Log.d(TAG, "onRequestPermissionsResult: success");super.onRequestPermissionsResult(requestCode, permissions, grantResults);if (requestCode==1){if (grantResults.length!=0){//表示有权限没有授权getPermission();}else {//表示都授权openCamera();}}}*//*SurfaceView状态回调*/TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {@Overridepublic void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {Log.d(TAG, "onSurfaceTextureAvailable: success");//首先就需要设置相机,然后再打开相机setLastImagePath();setupCamera(width,height);openCamera();}@Overridepublic void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {}@Overridepublic boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {return false;}@Overridepublic void onSurfaceTextureUpdated(SurfaceTexture surface) {}};private void setupCamera(int width,int height) {Log.d(TAG, "setupCamera: success");CameraManager cameraManager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE);try {String[] cameraIdList = cameraManager.getCameraIdList();for (String cameraId : cameraIdList) {CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId);if (cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) {continue;}StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);//相机支持的所有分辨率,下一步就是获取最合适的分辨率Size[] outputSizes = map.getOutputSizes(SurfaceTexture.class);Size size = getOptimalSize(outputSizes, width, height);previewSize = size;mCameraId = cameraId;break;}} catch (CameraAccessException e) {e.printStackTrace();}}//打开摄像头private void openCamera() {Log.d(TAG, "openCamera: success");CameraManager cameraManager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE);try {if (ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {// TODO: Consider calling//    ActivityCompat#requestPermissions// here to request the missing permissions, and then overriding//   public void onRequestPermissionsResult(int requestCode, String[] permissions,//                                          int[] grantResults)// to handle the case where the user grants the permission. See the documentation// for ActivityCompat#requestPermissions for more details.AlertDialog.Builder builder = new AlertDialog.Builder(getContext());builder.setMessage("该应用需要相机授权,点击授权按钮跳转到设置进行设置");builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {getActivity().finish();}});builder.setPositiveButton("设置", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {Intent intent = new Intent("android.settings.APPLICATION_DETAILS_SETTINGS");intent.setData(Uri.fromParts("package", getActivity().getPackageName(), null));startActivity(intent);}}).create().show();return;}cameraManager.openCamera(mCameraId, stateCallback, null);} catch (CameraAccessException e) {e.printStackTrace();}}//摄像头状态回调private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {@Overridepublic void onOpened(@NonNull CameraDevice camera) {Log.d(TAG, "onOpened: success");mCameraDevice = camera;//开启预览startPreview();}@Overridepublic void onDisconnected(@NonNull CameraDevice camera) {Toast.makeText(getContext(), "摄像头设备连接失败", Toast.LENGTH_SHORT).show();}@Overridepublic void onError(@NonNull CameraDevice camera, int error) {Toast.makeText(getContext(), "摄像头设备连接出错", Toast.LENGTH_SHORT).show();}};//预览功能private void startPreview() {Log.d(TAG, "startPreview: success");//设置图片阅读器setImageReader();//注意这里:sufacetexture跟surfaceview是两个东西,需要注意!//sufacetexture是textureview的重要属性SurfaceTexture surfaceTexture = textureView.getSurfaceTexture();//设置textureview的缓存区大小surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());//设置surface进行预览图像数据mPreviewSurface = new Surface(surfaceTexture);//创建CaptureRequestsetCaptureRequest();//创建capturesession/*Surface表示有多个输出流,我们有几个显示载体,就需要几个输出流。对于拍照而言,有两个输出流:一个用于预览、一个用于拍照。对于录制视频而言,有两个输出流:一个用于预览、一个用于录制视频。*/// previewSurface 用于预览, mImageReader.getSurface() 用于拍照try {mCameraDevice.createCaptureSession(Arrays.asList(mPreviewSurface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(@NonNull CameraCaptureSession session) {//当回调创建成功就会调用这个回调mCaptureSession = session;setRepeatCapture();}@Overridepublic void onConfigureFailed(@NonNull CameraCaptureSession session) {}}, null);} catch (CameraAccessException e) {e.printStackTrace();}}//设置图片阅读器private void setImageReader() {Log.d(TAG, "setImageReader: success");//创建ImageReader实例,接下来应该是设置一些属性参数mImageReader = ImageReader.newInstance(previewSize.getWidth(), previewSize.getHeight(), ImageFormat.JPEG, 1);//果然跟我想的一样,接下来是设置监听当图片流可用的时候的监听器,即为拍照之后产生照片流mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {@Overridepublic void onImageAvailable(ImageReader reader) {Image image = reader.acquireLatestImage();//进行保存图片,开一个线程进行保存ImageSaver imageSaver = new ImageSaver(getContext(), image);new Thread(imageSaver).start();Toast.makeText(getContext(), "保存图片成功", Toast.LENGTH_SHORT).show();}}, null);}//选择sizeMap中大于并且接近width和height的sizeprivate Size getOptimalSize(Size[] outputSizes, int width, int height) {Size tempSize=new Size(width,height);List<Size> sizes = new ArrayList<>();for (Size outputSize : outputSizes) {if (width > height) {//横屏的时候if (outputSize.getHeight() > height && outputSize.getWidth() > width) {sizes.add(outputSize);}} else {//竖屏的时候if (outputSize.getWidth() > height && outputSize.getHeight() > width) {sizes.add(outputSize);}}}if (sizes.size() > 0) {//如果有多个符合条件找到一个差距最小的,最接近预览分辨率的tempSize= sizes.get(0);int minnum = 999999;for (Size size : sizes) {int num = size.getHeight() * size.getHeight() - width * height;if (num < minnum) {minnum = num;tempSize = size;}}}return tempSize;/*if (sizes.size() > 0) {return Collections.min(sizes, new Comparator<Size>() {@Overridepublic int compare(Size size, Size t1) {return Long.signum(size.getWidth() * size.getHeight() - t1.getWidth() * t1.getHeight());}});}return outputSizes[0];*/}/*Fragment控件点击事件*/@Overridepublic void onClick(View v) {switch (v.getId()){case R.id.change:changeLens();break;}}private void changeLens(){Log.d(TAG, "changeLens: success");if (mCameraId.equals(String.valueOf(CameraCharacteristics.LENS_FACING_BACK))){mCameraId=String.valueOf(CameraCharacteristics.LENS_FACING_FRONT);}else {if (mCameraId.equals(String.valueOf(CameraCharacteristics.LENS_FACING_FRONT))){mCameraId=String.valueOf(CameraCharacteristics.LENS_FACING_BACK);}}CameraManager cameraManager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE);try {CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(mCameraId);StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);Size[] outputSizes = map.getOutputSizes(SurfaceTexture.class);Size optimalSize = getOptimalSize(outputSizes, textureView.getWidth(), textureView.getHeight());previewSize=optimalSize;} catch (CameraAccessException e) {e.printStackTrace();}/*这里不能直接使用关闭camera这个方法*//*closeCamera();*/closeCamera();openCamera();}//创建一个图片保存类public class ImageSaver implements Runnable {private Context context;private Image image;public ImageSaver(Context context, Image image) {Log.d(TAG, "ImageSaver: success");this.context = context;this.image = image;}@Overridepublic void run() {Image.Plane[] planes = image.getPlanes();ByteBuffer buffer = planes[0].getBuffer();byte[] bytes = new byte[buffer.remaining()];buffer.get(bytes);System.out.println(planes);String filname = Environment.getExternalStorageDirectory() + "/DCIM/Camera/"  + System.currentTimeMillis() + ".jpg";File file = new File(filname);FileOutputStream fileOutputStream=null;try {//保存图片fileOutputStream = new FileOutputStream(file);fileOutputStream.write(bytes,0,bytes.length);} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}finally {if (fileOutputStream!=null){try {fileOutputStream.close();} catch (IOException e) {e.printStackTrace();}}//最后还要广播通知相册更新数据库notiBroadcast();//保存操作结束后,需要用handle进行主线程数据的更新Message message = new Message();message.what=0;Bundle bundle = new Bundle();bundle.putString("path",filname);message.setData(bundle);handler.sendMessage(message);//image也算是个流也需要进行关闭,否则可能下一次拍照的时候会报错image.close();}}}private Handler handler=new Handler(Looper.myLooper()){@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);if (msg.what==0){Bundle pathdata = msg.getData();String path = (String) pathdata.get("path");imageList.add(path);//设置拍照界面显示的一个图片(左下角的图片预览)setLastImagePath();}}};private void notiBroadcast(){String path = Environment.getExternalStorageDirectory() + "/DCIM/";Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);Uri uri = Uri.fromFile(new File(path));intent.setData(uri);getContext().sendBroadcast(intent);}private void setLastImagePath(){Log.d(TAG, "setLastImagePath: success");//先判断一下手机有没有权限if (ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){return;}//拿到最后一张拍照照片,遍历所有相册照片String DCIMPath=Environment.getExternalStorageDirectory()+"/DCIM/Camera";File file = new File(DCIMPath);//对该文件夹进行遍历String[] jpgs = file.list(new FilenameFilter() {@Overridepublic boolean accept(File dir, String name) {if (name.contains("jpg")) {return true;} else {return false;}}});String finalImagePath=Environment.getExternalStorageDirectory()+"/DCIM/Camera/"+jpgs[jpgs.length-1];Bitmap bitmap = BitmapFactory.decodeFile(finalImagePath);/*Canvas canvas = new Canvas();canvas.drawBitmap(bitmap,0,0,new Paint());canvas.drawCircle(bitmap.getWidth()/2,bitmap.getHeight()/2,bitmap.getWidth()/2,new Paint());mImageView.draw(canvas);*/RoundedBitmapDrawable roundedBitmapDrawable = RoundedBitmapDrawableFactory.create(getResources(), bitmap);roundedBitmapDrawable.setCircular(true);/*mImageView.setImageBitmap(bitmap);*/mImageView.setImageDrawable(roundedBitmapDrawable);}//无论是预览还是拍照都需要设置capturerequestprivate void setCaptureRequest(){try {mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);mPreviewRequestBuilder.addTarget(mPreviewSurface);//手动对焦,还不会/*TODO*/// 自动对焦mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);// 闪光灯mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);// 人脸检测模式mPreviewRequestBuilder.set(CaptureRequest.STATISTICS_FACE_DETECT_MODE, CameraCharacteristics.STATISTICS_FACE_DETECT_MODE_SIMPLE);} catch (CameraAccessException e) {e.printStackTrace();}}private void setRepeatCapture(){Log.d(TAG, "setRepeatCapture: success");mPreviewRequestBuilder.setTag(TAG);//首先要知道整个调用顺序 devices创建出capturebuilder,当builder设置好各种参数之后,就可以build出capturerequiremPreviewRequest=mPreviewRequestBuilder.build();//session中需要用到capturerequiretry {mCaptureSession.setRepeatingRequest(mPreviewRequest, new CameraCaptureSession.CaptureCallback() {@Overridepublic void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {super.onCaptureCompleted(session, request, result);}},null);} catch (CameraAccessException e) {e.printStackTrace();}}
}

后续补充

这是写完这篇文章的一个补充,上面的所有代码示例,我也更新到完善Bug之后的代码。首先说一下这次预览主要遇到的问题:因为我设计的这个demo是搭载在Fragment上,所以出现的问题都是在当Fragment不可见的时候出现的问题,例如当切换ViewPage时,切换回相机预览页面,CameraDevice的回调会调disconnnect方法表示连接出错;以及当息屏时,亮屏预览只显示息屏前一帧;以及当在该Activity跳转到其他Activity时,返回的时候,预览也停留在离开该Activity的前一帧。

对于这几个bug的解决之路也是非常艰辛,一开始的时候就发现了第一个情况的bug,没当回事直接找到onPause调用closeCamera方法,然后再resume的时候再重新打开,但是随着调试的不断进行,这个方法只能将第一种情况的bug解决。后续又尝试了getUserVisibleHint回调方法,但是发现程序并不会回调这个方法。

后来没办法,只能从fragment的生命周期出发,发现viewpage切换的时候,textureview会自动变为不可用,滑动回预览界面的时候,textureview在加载完之后会回调自己的监听函数进行重新打开相机的操作,也就是说我们在onpause的时候记得将camera关闭就行,而对于锁屏或是跳到其他Activity返回的时候,可以发现textureview时一直可用的,也就是说上面两个情况重新回到页面,是不会自动调用textureview的加载完的回调函数,所以我们这时候就可以设置一个标识符,然后在resume函数中进行判断,如果符合这个标识符就进行重新打开相机。(onpause的时候也要关闭camera,否则后续会报错)。具体看代码实现

//回调这个函数的时候,无论什么情况都要关闭相机
//若是已经离开该活动的相关操作,只需要将isLeave置为true就可
@Overridepublic void onPause() {super.onPause();Log.d(TAG, "onPause: success");closeCamera();System.out.println(textureView.isAvailable());if (textureView.isAvailable()){//如果可用,就是除了进行切换pageview之外的所有操作isLeave=true;}/*else {//不可用说明就是直接切换了pageviewcloseCamera();}*/}//这个方法是无论什么操作,回到预览界面都会被回调的,这时候只要稍加区别
//就可以很好的解决预览的各种极端情况下的问题
@Overridepublic void onResume() {super.onResume();Log.d(TAG, "onResume: success");System.out.println("++++++++++");System.out.println(textureView.isAvailable());if (textureView.isAvailable()){if (isLeave){//息屏等操作会自动关闭camera,所以就得手动再打开一次openCamera();isLeave=false;}}

使用Camera2实现预览功能相关推荐

  1. 从零开发一款相机APP 第七篇: Camera2相机 预览功能实现

    本课程内容由 @小驰笔记 出品,欢迎关注,获取更多交流信息~ 欢迎访问个人博客:www.xiaochibiji.com 显示需要借助surface,一般采用surfaceview或者texturevi ...

  2. Android 音视频开发(三) -- Camera2 实现预览、拍照功能

    音视频 系列文章 Android 音视频开发(一) – 使用AudioRecord 录制PCM(录音):AudioTrack播放音频 Android 音视频开发(二) – Camera1 实现预览.拍 ...

  3. Android OpenGL+Camera2渲染(2) —— OpenGL实现Camera2图像预览

    Android OpenGL+Camera2渲染(1) -- OpenGL简单介绍 Android OpenGL+Camera2渲染(2) -- OpenGL实现Camera2图像预览 Android ...

  4. Android Camera2 相机预览、获取数据

    Camera2简要说明 在Google 推出Android 5.0的时候, Android Camera API 版本升级到了API2(android.hardware.camera2),大幅提高了A ...

  5. alert()的功能_前端实现简单的图片上传小图预览功能

    <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8" ...

  6. 智能媒体管理产品文档转换/预览功能介绍(4)--快速搭建

    摘要: 智能媒体管理提供了 Cloud Native 架构的文档转换/预览服务,本文介绍快速搭建的示例,让您用 DIY 的体验方式实现文档预览功能. 一.导语 智能媒体管理 提供了 Cloud Nat ...

  7. window下实现在线预览功能

    window下实现在线预览功能 最近用到文档在线预览功能,之前没接触过,一切从零开始,整了一段时间终于实现,现在把方法分享给大家! 一.主要思路 先将其他格式的文档(office文档.txt.图片等等 ...

  8. vue+vant使用图片预览功能ImagePreview的问题

    如果您搜到这篇文章的话,那员外估计您遇到跟我一样的问题了,即在打开图片预览功能后,如果不关闭预览的图片,同时改变路由的话,会发现即使路由改变了,预览的图片还在文档的最顶层显示,如图: 着实让员外百思不 ...

  9. Java实现图片裁剪预览功能

    Java实现图片裁剪预览功能 在项目中,我们需要做些类似头像上传,图片裁剪的功能,ok看下面文章! 需要插件:jQuery Jcrop 后端代码: package org.csg.upload;imp ...

最新文章

  1. Hadoop自学笔记(二)HDFS简单介绍
  2. android Merger 代替 FrameLayout:布局优化
  3. pyinstaller打包py文件生成的exe出现闪退问题
  4. Citrix Xendesktop中VDA注册DDC的流程
  5. 腾讯十周年,看看你的QQ是什么时候注册的?
  6. extjs4:代码实现comboBox选中事件
  7. Kettle 系列随笔
  8. flash中Stage,stage,root和this的区别用法
  9. CH - 6801 棋盘覆盖(二分图最大匹配+奇偶拆点)
  10. oracle path函数,自定义类似 sys_connect_by_path 功能的函数
  11. 某盘视频网页播放视频修改播放速度代码
  12. 公共代码之密码加解密
  13. 【定制开发】【M3】基于Python+pygame实现的人机AI对战五子棋游戏(保姆级入门讲解)
  14. 2018/03/28更新 日记
  15. 赛马问题--最全面的解析
  16. BATCH/批处理命令
  17. linux下Dnw配置(mini2440)
  18. 深度学习(四十八)InfoGAN学习笔记
  19. 编程中的数学理论——排列数组合数
  20. Introduction to 3D Object Detection with Lidar

热门文章

  1. 【IoT毕设】esp8266+机智云AIoT+热水器远程智能控制
  2. 基于沙猫群优化算法的线性规划求解matlab程序
  3. Python求电压电流相位差
  4. uniapp、微信小程序--自定义table
  5. 《火星救援》为什么像真的?作者、导演和NASA专家告诉你
  6. Spark- 之不同Source产生RDD的分区数与数据分配
  7. 中策大数据:在建工程是指什么?在建工程项目包含哪些类型?
  8. AMD新旗舰APU A12-9800 主频可超频至4.8GHz
  9. 突破16%“生死线”!央视点名鸿蒙,华为再次“改口”
  10. 无人机图像处理、视频与高性能单屏和多屏便携工作站