这篇文章主要为大家详细介绍了机器人开发之一个类实现照相机2预览和拍照效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
简介
网上对于照相机2的介绍有很多,在开源代码库上也有很多关于照相机2的封装库,但是对于那些库,封装性太强,有时候我们仅仅是需要个简简单单的拍照功能而已,因此,自定义一个照相机使之变得轻量级那是非常重要的了。(本文并非重复造轮子,而是在于学习Camera2API的基本功能,笔记之。)
学习要点:
使用Android Camera2 API的基本功能。
迭代连接到设备的所有相机的特征。
显示相机预览和拍摄照片。
照相机2 API为连接到机器人设备的各个相机设备提供了一个界面。它替代了已弃用的照相机类。
使用getCameraIdList获取所有可用摄像机的列表。然后,您可以使用getCameraCharacteristics,并找到适合您需要的最佳相机(前/后面,分辨率等)。创建一个摄像设备。状态回调的实例并打开相机。当相机打开时,准备开始相机预览。使用TextureView显示相机预览。创建一个照相机拍摄并设置一个重复的捕获请求。静像拍摄需要几个步骤。首先,需要通过更新相机预览的捕获请求来锁定相机的焦点。然后,以类似的方式,需要运行一个预捕获序列。之后,它准备拍摄一张照片。创建一个新的捕获请求并调用[捕获].
完成后,别忘了解锁焦点。
实现效果
环境
SDK21
Camera2 类图
代码实现
CameraPreview.java
/**
*神华创作于2017-10-20-0020。
*给shenhuanet@126.com发电子邮件
*/
公共类照相机预览扩展TextureView {
private static final String TAG=' camera preview ';
private static final SparseIntArray ORIENTATIONS=new SparseIntArray();//从屏幕旋转转换为联合图像专家组方向
private static final int MAX _ PREVIEW _ WIDTH=1920;//Camera2 API保证的最大预览宽高
private static final int MAX _ PREVIEW _ HEIGHT=1080;
private static final int STATE _ PREVIEW=0;//显示相机预览
private static final int STATE _ WAITING _ LOCK=1;//焦点锁定中
私有静态最终int STATE _ WAITING _ PRE _ CAPTURE=2;//拍照中
private static final int STATE _ WAITING _ NON _ PRE _ CAPTURE=3;//其它状态
private static final int STATE _ PICTURE _ TAKEN=4;//拍照完毕
private int mState=STATE _ PREVIEW;
private int mRatioWidth=0,mration height=0;
私有int mSensorOrientation
私有布尔mflash支持
私有信号量mCameraOpenCloseLock=新信号量(1);//使用信号量旗语进行多线程任务调度
私人活动活动;
私有文件mFile
私有处理程序线程mBackgroundThread
私有处理程序mBackgroundHandler
私有大小mPreviewSize
私有字符串麦卡梅雷德
私人摄像设备;
私有捕获请求构建器mPreviewRequestBuilder
private CaptureRequest mPreviewRequest;
私人摄像师;
私有ImageReader mImageReader
静态{
ORIENTATIONS.append(表面.旋转_0,90);
ORIENTATIONS.append(表面.旋转_90,0);
ORIENTATIONS.append(表面.旋转_180,270);
ORIENTATIONS.append(表面.旋转_270,180);
}
公共摄像机预览(上下文上下文){
这(上下文,空);
}
公共相机预览(上下文上下文,属性集属性){
this(context,attrs,0);
}
公共摄像机预览(Context Context,AttributeSet attrs,int defStyleAttr) {
super(context,attrs,defStyleAttr);
mFile=新文件(getContext().getExternalFilesDir(null),' pic。jpg’);
}
@覆盖
受保护的测量时无效(int width measurespec,int heightMeasureSpec) {
超级棒。on measure(widthMeasureSpec,heightsmeasurespec);
int width=测量规格。getsize(widthMeasureSpec);
int height=测量规格。getsize(heightsmeasurespec);
if(0==mrationwidth | | 0==mrationheight){
setMeasuredDimension(宽度,高度);
}否则{
如果(宽度高度* mrationwidth/mrationheight){
setMeasuredDimension(width,width * mrationheight/mrationwidth);
}否则{
setMeasuredDimension(height * m ration width/m ration height,height);
}
}
}
公共简历无效(活动活动){
this.activity=活动
startBackgroundThread();
//当活动或简历上的片段()时,可以冲洗打开一个相机并开始预览,否则,这个表面已经准备就绪
if (this.isAvailable()) {
openCamera(this.getWidth()、this。getheight());
}否则{
这个。setsurfaceetexturelistener(msurfaceetexturelistener);
}
}
public void onPause() {
关闭摄像头();
stopBackgroundThread();
}
public void setOutPutDir(File File){
this.mFile=file
}
public void setaspectation(int width,int height) {
如果(宽度0 ||高度0) {
抛出新的IllegalArgumentException(' Size不能为负');
}
比率宽度=宽度;
mRatioHeight=高度;
请求布局();
}
public void setAutoFlash(捕获请求.构建器请求构建器){
if (mFlashSupported) {
请求生成器。设置(捕获请求.控制_ AE _模式,
捕获请求. CONTROL _ AE _ MODE _ ON _ AUTO _ FLASH);
}
}
公共空的拍照(){
锁定焦点();
}
private void startBackgroundThread(){
mBackgroundThread=新的处理程序线程('相机背景');
mbackgroundthread。start();
mBackgroundHandler=新处理程序(mbackgroundthread。get looper());
}
private void stopBackgroundThread(){
mbackgroundthread。安全退出();
尝试{
mbackgroundthread。join();
mBackgroundThread=null
mBackgroundHandler=null
} catch (InterruptedException e) {
e。printstacktrace();
}
}
/**
* 处理生命周期内的回调事件
*/
私有最终纹理视图表面纹理监听器msurfaceetexture监听器=新纹理视图.SurfaceTextureListener() {
@覆盖
public void onsurfaceetextureavailable(表面纹理纹理,整数宽度,整数高度){
开放式摄像机(宽度、高度);
}
@覆盖
public void onSurfaceTextureSizeChanged(表面纹理纹理,整数宽度,整数高度){
配置转换(宽度、高度);
}
@覆盖
public boolean onSurfaceTextureDestroyed(表面纹理纹理){
返回真实的
}
@覆盖
公共void onSurfaceTextureUpdated(表面纹理纹理){
}
};
/**
* 相机状态改变回调
*/
私人最终摄像设备状态回调mStateCallback=新的摄像机设备。StateCallback() {
@覆盖
打开的(@非空相机设备相机设备)上的公共void {
mcameraopencloselock。发布();
Log.d(标签,'相机已打开');
mCameraDevice=cameraDevice
createCameraPreviewSession();
}
@覆盖
不连贯的上的公共void(@非空相机设备相机设备){
mcameraopencloselock。发布();
摄像设备。close();
mCameraDevice=null
}
@覆盖
public void on error(@ NonNull相机设备相机设备,int error) {
mcameraopencloselock。发布();
摄像设备。close();
mCameraDevice=null
if (null!=活动){
活动。finish();
}
}
};
/**
* 处理与照片捕获相关的事件
*/
私人摄影师CaptureCallback mCaptureCallback=新摄像机捕获会话.CaptureCallback() {
私有空的流程(捕获结果结果){
开关(状态){
案例状态_预览:{
打破;
}
案例状态_等待_锁定:{
整数af状态=结果。获取(捕获结果.控制_ AF _状态);
if (afState==null) {
captureStillPicture();
} else if (CaptureResult .控制_自动对焦_状态_聚焦_锁定==自动对焦状态| |
捕获结果. CONTROL _ AF _ STATE _ NOT _ FOCUSED _ LOCKED==AF状态){
整数AE状态=结果。获取(捕获结果.CONTROL _ AE _ STATE);
if(ea state==null | | ea state==捕获结果.控制_ AE _状态_收敛){
mState=STATE _ PICTURE _ TAKEN
captureStillPicture();
}否则{
runPreCaptureSequence();
}
}
打破;
}
案例状态_等待_预捕获:{
整数AE状态=结果。获取(捕获结果.CONTROL _ AE _ STATE);
if (aeState==null ||
aeState==CaptureResult .控制_ AE _状态_预先捕获||
aeState==CaptureRequest .控制_ AE _状态_闪存_必需){
mState=STATE _ WAITING _ NON _ PRE _ CAPTURE;
}
打破;
}
案例状态_等待_非_预捕获:{
整数AE状态=结果。获取(捕获结果.CONTROL _ AE _ STATE);
if (aeState==null || aeState!=捕获结果。控制_ AE _状态_预捕获){
mState=STATE _ PICTURE _ TAKEN
captureStillPicture();
}
打破;
}
}
}
@覆盖
public void on captureprogressed(@ NonNull camera capturesession会话,
@NonNull CaptureRequest请求,
@非空捕获结果部分结果){
过程(部分结果);
}
@覆盖
捕获时的公共void已完成(@ NonNull CameraCaptureSession会话,
@NonNull CaptureRequest请求,
@NonNull TotalCaptureResult结果){
过程(结果);
}
};
/**
* 在确定相机预览大小后应调用此方法
*
* @param viewWidth宽
* @param viewHeight高
*/
私有void配置转换(int view width,int viewHeight) {
if(null==mPreviewSize | | null==activity){
返回;
}
int旋转=活动。getwindowmanager().getDefaultDisplay().get rotation();
Matrix Matrix=new Matrix();
RectF viewRect=new RectF(0,0,viewWidth,view height);
RectF bufferRect=new RectF(0,0,mPreviewSize.getHeight()、mPreviewSize。getwidth());
float centex=view rect。centex();
float centey=view rect。centerY();
如果(表面10 .旋转_90==旋转||曲面. ROTATION_270==rotation) {
缓冲矩形。偏移量(百分比-缓冲矩形。centex()、centey-缓冲区矩形。centerY());
matrix.setRectToRect(viewRect,bufferRect,matrix .ScaleToFit。填充);
float scale=Math.max(
(浮点型)视图高度/mpreviewsize。获取高度(),
(浮点型)视图宽度/mpreviewsize。getwidth());
matrix.postScale(Scale,Scale,centerX,centerY);
matrix.postRotate(90 *(旋转- 2)、centex、centey);
} else if(表面. ROTATION_180==rotation) {
matrix.postRotate(180,centex,centey);
}
this.setTransform(矩阵);
}
/**
* 根据麦卡梅雷德打开相机
*/
private void openCamera(int width,int height) {
设置照相机输出(宽度、高度);
配置转换(宽度、高度);
camera manager manager=(camera manager)获取上下文().获取系统服务(上下文。相机_服务);
尝试{
如果(!mcameraopencloselock。尝试获取(2500,时间单位。毫秒)){
抛出新的RuntimeException('等待锁定相机打开超时');
}
如果(活动兼容。checkselpermission(activity,Manifest.permission.CAMERA)!=包管理器.PERMISSION_GRANTED) {
//TODO:考虑调用
//活动比较#请求权限
//此处请求缺少的权限,然后重写
//public void onrequestpermissions结果(int请求代码,String[]权限,
//int[] grantResults)
//处理用户授予权限的情况。参见文档
//对于活动compat #请求权限了解更多详细信息。
返回;
}
manager.openCamera(mCameraId,mStateCallback,mBackgroundHandler);
} catch(CameraAccessException e){
e。printstacktrace();
} catch (InterruptedException e) {
抛出新的RuntimeException('在试图锁定摄像机打开时被中断. e);
}
}
/**
* 关闭相机
*/
私有void closeCamera() {
尝试{
mcameraopencloselock。获取();
if (null!=mCaptureSession) {
mcapturesession。close();
mCaptureSession=null
}
if (null!=mCameraDevice) {
mcameradevice。close();
mCameraDevice=null
}
if (null!=mImageReader) {
mimage阅读器。close();
mImageReader=null
}
} catch (InterruptedException e) {
抛出新的RuntimeException('在尝试锁定摄像机关闭时被中断. e);
}最后{
mcameraopencloselock。发布();
}
}
/**
* 设置相机相关的属性或变量
*
* @param宽度相机预览的可用尺寸的宽度
* @param高度相机预览的可用尺寸的高度
*/
@隐藏警告('可疑的组合')
private void setUpCameraOutputs(int width,int height) {
camera manager manager=(camera manager)获取上下文().获取系统服务(上下文。相机_服务);
尝试{
for(字符串相机id:manager。getcameraidlist()){
camera characteristics characters=经理。getcameracharacteristics(cameraId);
//在这个例子中不使用前置摄像头
integer facing=字符。获取(CameraCharacteristics .镜头_朝向);
如果(面对!=零朝向==摄像机特性.LENS_FACING_FRONT) {
继续;
}
StreamConfigurationMap map=characters。获取(CameraCharacteristics .缩放器_流_配置_映射);
if (map==null) {
继续;
}
最大尺寸=集合。最大(数组。作为列表(地图。获取输出尺寸(图像格式.JPEG)),
new CompareSizesByArea());
mimage reader=imagereader。新实例(最大。getwidth()、largest.getHeight(),
图像格式。JPEG,/* max images */2);
mimagereader。setonimageavailablelistener(
mOnImageAvailableListener,mBackgroundHandler);
int显示旋转=活动。getwindowmanager().getDefaultDisplay().get rotation();
//无检查常数条件
m传感器方向=字符。获取(CameraCharacteristics .传感器_方位);
布尔交换尺寸=假;
开关(显示旋转){
表壳表面。旋转_0:
表壳表面。旋转_180:
如果(m传感器方向==90 | | m传感器方向==270){
swappedDimensions=true
}
打破;
表壳表面。旋转_90度:
表壳表面。旋转_270:
如果(m传感器方向==0 | | m传感器方向==180){
swappedDimensions=true
}
打破;
默认值:
Log.e(标签,'显示旋转无效: '显示旋转');
}
点显示大小=新点();
activity.getWindowManager().getDefaultDisplay().getSize(显示大小);
int rotatedPreviewWidth=宽度
int rotatedPreviewHeight=height;
int maxPreviewWidth=displaysize。x;
int maxPreviewHeight=displaysize。y;
if (swappedDimensions) {
rotatedPreviewWidth=height
rotatedPreviewHeight=宽度
maxPreviewWidth=displaySize.y
maxPreviewHeight=displaysize。x;
}
if(maxPreviewWidth MAX _ PREVIEW _ WIDTH){
MAX PREVIEW WIDTH=MAX _ PREVIEW _ WIDTH;
}
if(maxPreviewHeight MAX _ PREVIEW _ HEIGHT){
MAX PREVIEW HEIGHT=MAX _ PREVIEW _ HEIGHT;
}
mPreviewSize=chooseOptimalSize(map。获取输出尺寸(表面纹理。类),
rotatedPreviewWidth,rotatedPreviewHeight,maxPreviewWidth,
maxPreviewHeight,最大);
int orientation=getResources().获取配置().定向;
如果(方向==配置。方向_横向){
setAspectRatio(mpreviewsize。getwidth()、mpreviewsize。getheight());
}否则{
setAspectRatio(mpreviewsize。getheight()、mpreviewsize。getwidth());
}
布尔可用=字符。获取(CameraCharacteristics .FLASH _ INFO _ AVAILABLE);
mFlashSupported=available==null?假:可用;
mCameraId=cameraId
返回;
}
} catch(CameraAccessException e){
e。printstacktrace();
} catch (NullPointerException e) {
Log.e(标签,'设备不支持摄像机2’);
}
}
/**
* 获取一个合适的相机预览尺寸
*
* @param选择支持的预览尺寸列表
* @param textureViewWidth相对宽度
* @param textureViewHeight相对高度
* @param maxWidth可以选择的最大宽度
* @param maxHeight可以选择的最大高度
* @ param aspectRatio宽高比
* @返回最佳预览尺寸
*/
私有静态大小chooseOptimalSize(Size[]choices,int textureViewWidth,int textureViewHeight,
int maxWidth,int maxHeight,Size as spec ratio){
列表大小足够=new ArrayList();
列表大小不大足够=new ArrayList();
int w=aspectratio。getwidth();
int h=aspectratio。获取height();
对于(尺寸选项:选择){
if(选项。getwidth()=maxWidth选项。getheight()=maxHeight
选项。getheight()==选项。getwidth()*高/宽){
if(选项。getwidth()=纹理视图宽度
选项。getheight()=纹理视图高度){
足够大. add(选项);
}否则{
notBigEnough.add(选项);
}
}
}
如果(bigovernment。size()0){
返回收藏。min(big ough,new CompareSizesByArea());
} else if (notBigEnough.size() 0) {
返回收藏。max(不够大,new CompareSizesByArea());
}否则{
Log.e(标签,'找不到任何合适的预览大小');
返回选择[0];
}
}
/**
* 为相机预览创建新的照相机拍摄
*/
私有void createCameraPreviewSession(){
尝试{
表面纹理纹理=这个。get surface texture();
断言纹理!=空
//将默认缓冲区的大小配置为想要的相机预览的大小
质感。setdefaultbuffersize(mpreviewsize。getwidth()、mpreviewsize。getheight());
表面=新表面(纹理);
mPreviewRequestBuilder=mcameradevice。createcapturerequest(摄像机设备.模板_预览);
mpreviewrequestbuilder。添加目标(表面);
//我们创建一个照相机拍摄来进行相机预览
mcameradevice。createcapturesession(数组。as list(surface,mImageReader.getSurface()),
新的照相机拍摄.StateCallback() {
@覆盖
已配置的公共void(@ NonNull CameraCaptureSession CameraCaptureSession){
if (null==mCameraDevice) {
返回;
}
//会话准备好后,我们开始显示预览
mCaptureSession=camera capture session;
尝试{
mpreviewrequestbuilder。set(CaptureRequest .控制_自动对焦_模式,
捕获请求。控制_ AF _模式_连续_图片);
setAutoFlash(mPreviewRequestBuilder);
mPreviewRequest=mpreviewrequestbuilder。build();
mcapturesession。set repeating request(mPreviewRequest,mCaptureCallback,mBackgroundHandler);
} catch(CameraAccessException e){
e。printstacktrace();
}
}
@覆盖
配置时公共void失败(@ NonNull CameraCaptureSession CameraCaptureSession){
}
},null);
} catch(CameraAccessException e){
e。printstacktrace();
}
}
/**
* 从指定的屏幕旋转中检索照片方向
*
* @param旋转屏幕方向
* @返回照片方向(0,90,270,360)
*/
私有int get orientation(int rotation){
返回(方向。get(旋转)m传感器方位270)% 360;
}
/**
* 锁定焦点
*/
私有void lockFocus() {
尝试{
//如何通知相机锁定焦点
mpreviewrequestbuilder。set(CaptureRequest .控制_自动对焦_触发器,照相机元数据.控制_自动对焦_触发_启动);
//通知mCaptureCallback等待锁定
mState=状态_等待_锁定
mcapturesession。捕获(mpreviewrequestbuilder。build()、mCaptureCallback、mBackgroundHandler);
} catch(CameraAccessException e){
e。printstacktrace();
}
}
/**
* 解锁焦点
*/
私有void unlockFocus() {
尝试{
mpreviewrequestbuilder。set(CaptureRequest .控制_自动对焦_触发,
照相机数据.控制_ AF _触发_取消);
setAutoFlash(mPreviewRequestBuilder);
mcapturesession。捕获(mpreviewrequestbuilder。build()、mCaptureCallback、
mBackgroundHandler);
mState=STATE _ PREVIEW
mcapturesession。set repeating request(mPreviewRequest,mCaptureCallback,
mBackgroundHandler);
} catch(CameraAccessException e){
e。printstacktrace();
}
}
/**
* 拍摄静态图片
*/
私有void捕捉静态图片(){
尝试{
if(null==activity | | null==mCameraDevice){
返回;
}
最终捕获请求。生成器捕获生成器=
mCameraDevice.createCapture
Request(CameraDevice.TEMPLATE_STILL_CAPTURE); captureBuilder.addTarget(mImageReader.getSurface()); captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); setAutoFlash(captureBuilder); // 方向 int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation)); CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { Toast.makeText(getContext(), "Saved: " + mFile, Toast.LENGTH_SHORT).show(); Log.d(TAG, mFile.toString()); unlockFocus(); } }; mCaptureSession.stopRepeating(); mCaptureSession.abortCaptures(); mCaptureSession.capture(captureBuilder.build(), captureCallback, null); } catch (CameraAccessException e) { e.printStackTrace(); } } /** * 运行preCapture序列来捕获静止图像 */ private void runPreCaptureSequence() { try { // 设置拍照参数请求 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START); mState = STATE_WAITING_PRE_CAPTURE; mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } /** * 比较两者大小 */ private static class CompareSizesByArea implements Comparator<Size> { @Override public int compare(Size lhs, Size rhs) { return Long.signum((long) lhs.getWidth() * lhs.getHeight() - (long) rhs.getWidth() * rhs.getHeight()); } } /** * ImageReader的回调对象 */ private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile)); } }; /** * 将捕获到的图像保存到指定的文件中 */ private static class ImageSaver implements Runnable { private final Image mImage; private final File mFile; ImageSaver(Image image, File file) { mImage = image; mFile = file; } @Override public void run() { ByteBuffer buffer = mImage.getPlanes()[0].getBuffer(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); FileOutputStream output = null; try { output = new FileOutputStream(mFile); output.write(bytes); } catch (IOException e) { e.printStackTrace(); } finally { mImage.close(); if (null != output) { try { output.close(); } catch (IOException e) { e.printStackTrace(); } } } } } }MainActivity.java
public class MainActivity extends AppCompatActivity { CameraPreview cameraView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); cameraView = (CameraPreview) findViewById(R.id.cameraView); } @Override protected void onResume() { super.onResume(); cameraView.onResume(this); } @Override protected void onPause() { cameraView.onPause(); super.onPause(); } public void takePic(View view) { cameraView.takePicture(); } }activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout 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:id="@+id/constraintLayout" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/black" tools:context="com.shenhua.ocr.activity.Main2Activity"> <com.shenhua.ocr.widget.CameraPreview android:id="@+id/cameraView" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:layout_width="70dp" android:layout_height="70dp" android:layout_gravity="center" android:background="@drawable/ic_capture_200px" android:onClick="takePic" android:text="TAKE" app:layout_constraintBottom_toBottomOf="@id/constraintLayout" app:layout_constraintEnd_toEndOf="@id/constraintLayout" app:layout_constraintStart_toStartOf="@id/constraintLayout" app:layout_constraintTop_toTopOf="@id/cameraView" app:layout_constraintVertical_bias="0.97" /> </android.support.constraint.ConstraintLayout>资源文件 ic_capture_200px.xml
<vector android:height="24dp" android:viewportHeight="1024.0" android:viewportWidth="1024.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> <path android:fillColor="#03A9F4" android:pathData="M512,512m-393.8,0a393.8,393.8 0,1 0,787.7 0,393.8 393.8,0 1,0 -787.7,0Z"/> <path android:fillColor="#03A9F4" android:pathData="M512,1024C229.2,1024 0,794.8 0,512S229.2,0 512,0s512,229.2 512,512 -229.2,512 -512,512zM512,984.6c261,0 472.6,-211.6 472.6,-472.6S773,39.4 512,39.4 39.4,251 39.4,512s211.6,472.6 472.6,472.6z"/> </vector>其它
Manifest 权限:
<uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" />Android6.0 运行时权限未贴出。(注意:为了方便读者手机端阅读,本文代码部分的成员变量使用了行尾注释,在正常编程习惯中,请使用 /* / 注释。)
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。