赞
踩
一、资料参考
二、代码
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <uses-feature android:name="android.hardware.camera" android:required="false" /> <!--CAMER相机权限 下面是写出权限--> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission> <application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.CameraDemo" tools:targetApi="31"> <activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.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:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <!--TextureView: 把摄像头捕获到的数据显示到界面上--> <TextureView android:id="@+id/textureView" android:layout_width="match_parent" android:layout_height="match_parent" /> <Button android:layout_width="70dp" android:layout_height="70dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.532" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.948" android:text="TAKE" android:onClick="Capture" ></Button> </androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.java
package com.example.camerademo; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import android.Manifest; import android.content.Context; import android.content.pm.PackageManager; import android.graphics.ImageFormat; import android.graphics.SurfaceTexture; 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.CameraMetadata; 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.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.util.Size; import android.util.SparseArray; import android.view.Surface; import android.view.TextureView; import android.view.View; import android.widget.Toast; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.List; public class MainActivity extends AppCompatActivity { TextureView mPreviewview; //预览窗口 //Handler: 异步消息处理机制,可用于主线程与子线程之间传递消息 //nadlerThread:带有消息处理机制的线程 HandlerThread mHandlerThread; Handler mCameraHandler; CameraManager manager; Size mPreviewSize; //最佳的预览尺寸 Size mCaptureSize; //最佳的拍照尺寸 String mCameraId; //要打开的摄像头的id CameraDevice mCameraDevice; //回调函数中的对象 CaptureRequest.Builder mCaptureRequestBuilder; CaptureRequest mCaptureRequest; CameraCaptureSession mCameraCaptureSession; ImageReader mimageReader; //根据摄像头夹角确定旋转角度 private static final SparseArray ORIENTATION =new SparseArray(); static{ ORIENTATION.append(Surface.ROTATION_0,90); ORIENTATION.append(Surface.ROTATION_90,0); ORIENTATION.append(Surface.ROTATION_180,270); ORIENTATION.append(Surface.ROTATION_270,180); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mPreviewview = findViewById(R.id.textureView); // onResume(); } /*********** Cameramanager: 管理摄像头信息 CameraDevice: 摄像头对象,管理session会话, CameraCaptureSession: 会话,android device向camera device发送请求,以及camera device反馈结果都在会话中进行 CaptureRequest: 请求 CameraMatedata: 反馈结果 *******************/ /* 当打开摄像头或者屏幕旋转的时候需要去检查当前摄像头的状态 这个工作在onResume中进行 onResume方法在activity启动后执行 */ protected void onResume() { super.onResume(); startCameraThread(); //打开摄像头线程 /*摄像头没有正常打开的时候预览界面是不能预览内容的 所以这里设置一个监听 */ if (!mPreviewview.isActivated()) { //添加监听 mPreviewview.setSurfaceTextureListener(mTextureListener); }else { startPreview(); //开始预览 } } //这里是创建一个监听器的全局变量,里边的四个函数是自动出现的 TextureView.SurfaceTextureListener mTextureListener = new TextureView.SurfaceTextureListener() { @Override //摄像头控件准备好 public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) { //当 surfaceTexture(摄像头)可用时,设置相机参数并打开摄像头 try { setupCamera(width, height); //设置摄像头参数(预览界面的宽和高) } catch (CameraAccessException e) { throw new RuntimeException(e); } openCamera(); //打开摄像头 } @Override //尺寸改变 public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) { } @Override //销毁 public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) { return false; } @Override //更新 public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) { } }; //开启摄像头线程 private void startCameraThread() { mHandlerThread = new HandlerThread("CameraThread"); //name:"CameraThread" mHandlerThread.start(); mCameraHandler = new Handler(mHandlerThread.getLooper()); } //设置摄像头参数(预览界面的宽和高) private void setupCamera(int width, int height) throws CameraAccessException { manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); //启动一个系统服务 //遍历手机系统中的摄像头,拿到摄像头ID for (String cameraID : manager.getCameraIdList()) { //拿到当前摄像头的一些参数 CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraID); //获得当前摄像头的朝向 Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING); //如果当前摄像头朝前,那就这个就不要了,直接换下一个 if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) { continue; } //获得当前摄像头的分辨率 StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (map != null) { //找到摄像头能够输出的。最符合我们当前显示器界面分辨率的最小值 mPreviewSize = getOptimalSize(map.getOutputSizes(SurfaceTexture.class), width, height); //括号里是预览界面的尺寸 mCaptureSize=Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), new Comparator<Size>() { @Override public int compare(Size o1, Size o2) { return Long.signum(o1.getWidth()*o1.getHeight()-o2.getWidth()*o2.getHeight()); } }); } //建立缓冲区,准备存储照片 setupImageReader(); mCameraId = cameraID; //给全局变量id赋值,以便打开相机时使用 break; } } private Size getOptimalSize(Size[] sizeMap, int width, int height) { List<Size> sizeList = new ArrayList<Size>(); //一个存放分辨率的列表 for (Size option : sizeMap) { if (width > height) { //横屏 if (option.getWidth() > width && option.getHeight() > height) { sizeList.add(option); } } else { //竖屏 if (option.getWidth() > height && option.getHeight() > width) { sizeList.add(option); } } } if (sizeList.size() > 1) { return Collections.min(sizeList, new Comparator<Size>() { @Override public int compare(Size o1, Size o2) { return Long.signum(o1.getWidth() * o1.getHeight() - o2.getWidth() * o2.getHeight()); } }); } return sizeMap[0]; } //打开摄像头 private void openCamera() { //动态获取权限 String [] permissions={Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE}; //遍历权限 int i=0; for(String permission:permissions) { if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { this.requestPermissions(permissions, i++); //弹框 return; } } try { manager.openCamera(mCameraId, mStateCallback, mCameraHandler); } catch (CameraAccessException e) { throw new RuntimeException(e); } } //打开相机的回调函数 CameraDevice.StateCallback mStateCallback=new CameraDevice.StateCallback() { //摄像头打开, @Override public void onOpened(@NonNull CameraDevice camera) { mCameraDevice=camera; startPreview(); } //摄像头关闭 @Override public void onDisconnected(@NonNull CameraDevice camera) { mCameraDevice.close(); mCameraDevice=null; } //摄像头发生错误 @Override public void onError(@NonNull CameraDevice camera, int error) { mCameraDevice.close(); mCameraDevice=null; } }; //开始预览 private void startPreview(){ //建立图像缓冲区 SurfaceTexture mSurfaceTexture=mPreviewview.getSurfaceTexture(); //缓冲区 mSurfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(),mPreviewSize.getHeight()); //缓冲区尺寸 //得到界面显示对象 Surface previewSurface = new Surface(mSurfaceTexture); try { mCaptureRequestBuilder=mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); //预览模板 mCaptureRequestBuilder.addTarget(previewSurface); //把对象放到界面里 //建立通道(CaptureRequest 和 CaptureSession会话) //asList两个参数:是将绘画的数据流交给界面和保存图片这两个部分进行处理,要将绘画跟要处理的缓冲区建立联系 mCameraDevice.createCaptureSession(Arrays.asList(previewSurface,mimageReader.getSurface()), new CameraCaptureSession.StateCallback() { @Override //配置成功 public void onConfigured(@NonNull CameraCaptureSession session) { mCaptureRequest=mCaptureRequestBuilder.build(); mCameraCaptureSession=session; //不停发送请求 try { mCameraCaptureSession.setRepeatingRequest(mCaptureRequest,null,mCameraHandler); } catch (CameraAccessException e) { throw new RuntimeException(e); } } @Override //配置失败 public void onConfigureFailed(@NonNull CameraCaptureSession session) { } }, mCameraHandler); } catch (CameraAccessException e) { throw new RuntimeException(e); } } //开始拍照,这个是拍照按钮的响应函数 public void Capture(View view) throws CameraAccessException { //获取摄像头的请求 CaptureRequest.Builder mCameraBuilder=mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); mCameraBuilder.addTarget(mimageReader.getSurface()); //获取照相缓冲区的数据流 //获取摄像头方向 int rotaion=getWindowManager().getDefaultDisplay().getRotation(); //获取拍照方向 mCameraBuilder.set(CaptureRequest.JPEG_ORIENTATION,(Integer)ORIENTATION.get(rotaion)); //拍照的回调函数 CameraCaptureSession.CaptureCallback mCaptureCallBack=new CameraCaptureSession.CaptureCallback() { @Override //拍照完成(onCaptureCompleted当图像捕捉完成,并且结果可用时调用此函数) public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { Toast.makeText(getApplicationContext(),"拍照结束,相片已保存",Toast.LENGTH_LONG).show(); unLockFouces(); //关闭摄像头 super.onCaptureCompleted(session, request, result); } }; mCameraCaptureSession.stopRepeating(); //停止绘画请求 mCameraCaptureSession.capture(mCameraBuilder.build(),mCaptureCallBack,mCameraHandler); //capture拍摄单张照片 //获取图像的缓冲区 //获取文件的存储权限及操作 } private void unLockFouces(){ try { mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL); mCameraCaptureSession.setRepeatingRequest(mCaptureRequestBuilder.build(),null,mCameraHandler); } catch (CameraAccessException e) { throw new RuntimeException(e); } } //建立缓冲区,准备保存图片,参数2表示最多存2张 private void setupImageReader(){ mimageReader=ImageReader.newInstance(mCaptureSize.getWidth(),mCaptureSize.getHeight(),ImageFormat.JPEG,2); //当图片已经准备好了 mimageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { // Image image=reader.acquireLatestImage(); //这样就可以拿到这张图片了 mCameraHandler.post(new ImageSaver(reader.acquireLatestImage())); //acquire..表示图片准备好了,这是就可以调用ImageSaver这个存储过程存储图片 } },mCameraHandler); } private class ImageSaver implements Runnable{ Image mImage; public ImageSaver(Image image){ mImage=image; } public void run(){ ByteBuffer buffer=mImage.getPlanes()[0].getBuffer(); byte[] data=new byte[buffer.remaining()]; buffer.get(data); String path= Environment.getExternalStorageDirectory()+"/DCIM/CameraV2/"; File mImageFile=new File(path); if(!mImageFile.exists()){ mImageFile.mkdir(); //如果路径不存在就建一个 } //文件命名方式:年月日_时分秒 String timeStamp=new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); String fileName=path+"IMG_"+timeStamp+".jpg"; try { FileOutputStream fos=new FileOutputStream(fileName); fos.write(data,0,data.length); //写出图片 } catch (FileNotFoundException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } } } }
三、结果
四、补充一些样式设置
colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="colorPrimary">#55BBFF</color>
<color name="colorPrimaryDark">#55BBFF</color>
<color name="colorAccent">#D81B60</color>
</resources>
styles.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <!--style 是作为主题还是控件的样式取决于parent 本程序的主题设置在themes.xml里面了--> <style name="ButtonFont" parent="TextAppearance.AppCompat.Button"> <item name="android:layout_height">wrap_content</item> <item name="android:layout_width">wrap_content</item> <item name="android:textSize">20sp</item> </style> <style name="loading_dialog" parent="android:style/Theme.Dialog"> <item name="android:windowFrame">@null</item> <item name="android:windowNoTitle">true</item> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowIsFloating">true</item> <item name="android:backgroundDimEnabled">false</item> <item name="android:windowContentOverlay">@null</item> </style> </resources>
themes.xml
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.CameraDemo" parent="Theme.Material3.DayNight">
<!-- Customize your light theme here. -->
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="Theme.CameraDemo" parent="Base.Theme.CameraDemo" />
</resources>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。