前回のブログでは、単発撮影と連写でのカメラの使い方を紹介しました。誰もが理解する必要があります。しかし、ブログで例を実行したり、説明に基づいてデモを書いたりすると、撮影した写真があまり鮮明ではないことがわかります。大きな理由の1つは、写真でサポートされている最大解像度が1920 * 1080であるためです。毎ターン2000までの解像度で非常に明確ですか?そのため、Camera2はAndroid5.0の後に誕生しました。また、Camera2と連携するディスプレイコントロールもTextureViewになりました。以下、この2つの内容を1つずつ説明します。そして最後に、Camera2製のカスタムカメラの例を示します。
1.テクスチャビューTextureViewの一般的なメソッド
- lockCanvas:キャンバスをロックして取得します。
- UnlockCanvasAndPost:キャンバスのロックを解除して更新します。
- setSurfaceTextureListener:サーフェステクスチャのリスナーを設定します。このメソッドは、サーフェステクスチャの状態変更イベントを監視するために使用されるSurfaceHolderのaddCallbackメソッドと同等です。メソッドパラメータはSurfaceTextureListenerリスナーオブジェクトであり、4つのメソッドを書き直す必要があります。
onSurfaceTextureAvailable |
表面テクスチャが利用可能になるとトリガーされ、カメラを開くなどの操作をここで実行できます。 |
onSurfaceTextureSizeChanged | サーフェステクスチャサイズが変更されたときにトリガーされます。 |
onSurfaceTextureDestroyed | 表面のテクスチャが破壊されたときにトリガーされます。 |
onSurfaceTextureUpdated | 表面テクスチャが更新されるとトリガーされます。 |
- isAvailable:表面テクスチャが使用可能かどうかを判別します。
- getSurfaceTexture:表面テクスチャを取得します。
2.白い背景を洗うSurfaceViewメソッド
//下面两行设置背景为透明,因为SurfaceView默认背景是黑色
setZOrderOnTop(true);
mHolder.setFormat(PixelFormat.TRANSLUCENT);
3.Camera2の新機能
- 30フレーム/秒のフルHD連続撮影をサポートします。
- 各フレーム間で異なる設定を使用することをサポートします。
- ネイティブ形式での画像出力をサポートします。
- ゼロディレイシャッターと動画撮影をサポートします。
- ノイズキャンセリングのレベルの設定など、他の側面でのカメラの手動制御をサポートします。
4.Camera2の新しいアーキテクチャ構造の分割
Camera2は、アーキテクチャが大幅に変更されました。元のCameraクラスは、主に次の部分を含む複数の管理クラスに分割されています。
- カメラマネージャーCameraManager
- カメラデバイスCameraDevice
- CameraCaptureSession
- イメージリーダーImageReader
5.カメラマネージャーCameraManager
<1>カメラマネージャーの役割
カメラマネージャは、使用可能なカメラ、開いているカメラなどのリストを取得するために使用されます。オブジェクトは、システムサービスCAMERA_SERVICEから取得されます。
<2>カメラマネージャーの一般的な方法
- getCameraIdList:カメラリストを取得します。通常、2つのレコードが返されます。1つはリアカメラで、もう1つはフロントカメラです。
- getCameraCharacteristics:カメラのパラメーター情報を取得します。カメラのサポートレベル、写真のサイズなどを含みます。
- openCamera:指定されたカメラを開きます。最初のパラメーターは指定されたカメラのIDで、2番目のパラメーターはデバイスステータスリスナーです。リスナーは、インターフェイスCameraDevice.StateCallbackのonOpendメソッドを実装する必要があります(メソッド内のcreateCaptureRequestメソッドCameraDeviceオブジェクトのが呼び出されます)。
- setTorchMode:カメラを開かずにフラッシュをオンまたはオフにします。Trueはフラッシュをオンにすることを意味し、falseはフラッシュをオフにすることを意味します。
<3>現在の携帯電話がCamera2をサポートしているかどうかを確認します
// 从系统服务中获取相机管理器
CameraManager cm = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
// 获取可用相机设备列表
CameraCharacteristics cc = cm.getCameraCharacteristics(cameraid);
// 检查相机硬件的支持级别
// CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL表示完全支持
// CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED表示有限支持
// CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY表示遗留的
int level = cc.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
if (level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL){
ToastUtil.toastWord(mContext,"完全支持");
}else if (level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED){
ToastUtil.toastWord(mContext,"有限支持");
}else if (level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY){
ToastUtil.toastWord(mContext,"不建议使用");
}
6.カメラデバイスCameraDevice
<1>カメラ機器の役割
カメラデバイスは、写真リクエストの作成、プレビューインターフェイスの追加、写真セッションの作成などに使用されます。
<2>カメラ機器の一般的な方法
- createCaptureRequest:写真リクエストを作成します。2番目のパラメーターはセッション状態リスナーです。リスナーはセッション状態コールバックインターフェイスCamereCaptureSession.StateCallbackのonConfiguredメソッドを実装する必要があります(このメソッドはCameraCaptureSessionオブジェクトのsetRepeatingRequestメソッドを呼び出してプレビュー画像を出力します画面に)。createCaptureRequestメソッドは、CaptureRequestのプレビューオブジェクトを返します。
- 閉じる:カメラの電源を切ります。
7.カメラキャプチャセッションCameraCaptureSession
<1>カメラ撮影会の役割
カメラフォトセッションは、1回の撮影セッション(一度に1枚の写真のみを撮影)、連続撮影セッション(複数の写真の自動連続撮影)などを設定するために使用されます。
<2>カメラ撮影の一般的な方法
- getDevice:セッションのカメラデバイスオブジェクトを取得します。
- キャプチャ:写真を撮り、指定されたターゲットに出力します。出力ターゲットがCaptureRequestオブジェクトの場合は、画面に表示されることを意味します。出力ターゲットがImageReaderオブジェクトの場合は、写真が保存されることを意味します。
- setRepeatingRequest:連写要求を設定し、指定したターゲットに出力します。出力ターゲットがCaptureRequestオブジェクトの場合は、画面に表示されることを意味します。出力ターゲットがImageReaderオブジェクトの場合は、写真が保存されることを意味します。
- stopRepeating:連続撮影を停止します。
8.イメージリーダーImageReader
<1>イメージリーダーの役割
画像リーダーは、写真情報を取得して保存するために使用されます。画像データが生成されると、onImageAvailableメソッドがすぐにトリガーされます。
<2>画像リーダーの一般的な方法
- getSurface:画像が読み取られるサーフェスオブジェクトを取得します。
- setOnImageAvailableListener:画像データに使用できるリスナーを設定します。リスナーは、インターフェイスImageReader.OnImageAvailableListenerのonImageAvailableメソッドを実装する必要があります。
9.Camera2の使用例
Camera2View.java
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
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.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.media.ImageReader.OnImageAvailableListener;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
import android.view.TextureView;
import android.widget.Toast;
import androidx.annotation.RequiresApi;
import androidx.core.app.ActivityCompat;
import com.hao.baselib.utils.PathGetUtil;
import com.hao.baselib.utils.ToastUtil;
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class Camera2View extends TextureView {
private static final String TAG = "Camera2View";
private Context mContext; // 声明一个上下文对象
private Handler mHandler;
private HandlerThread mThreadHandler;
private CaptureRequest.Builder mPreviewBuilder; // 声明一个拍照请求构建器对象
private CameraCaptureSession mCameraSession; // 声明一个相机拍照会话对象
private CameraDevice mCameraDevice; // 声明一个相机设备对象
private ImageReader mImageReader; // 声明一个图像读取器对象
private Size mPreViewSize; // 预览画面的尺寸
private int mCameraType = CameraCharacteristics.LENS_FACING_FRONT; // 摄像头类型
private int mTakeType = 0; // 拍摄类型。0为单拍,1为连拍
public Camera2View(Context context) {
this(context, null);
}
public Camera2View(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
mThreadHandler = new HandlerThread("camera2");
mThreadHandler.start();
mHandler = new Handler(mThreadHandler.getLooper());
}
// 打开指定摄像头的相机视图
public void open(int camera_type) {
mCameraType = camera_type;
// 设置表面纹理变更监听器
setSurfaceTextureListener(mSurfacetextlistener);
}
private String mPhotoPath; // 照片的保存路径
// 获取照片的保存路径
public String getPhotoPath() {
return mPhotoPath;
}
// 执行拍照动作
public void takePicture() {
Log.d(TAG, "正在拍照");
mTakeType = 0;
try {
CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
// 把图像读取器添加到预览目标
builder.addTarget(mImageReader.getSurface());
// 设置自动对焦模式
builder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_AUTO);
// 设置自动曝光模式
builder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
// 开始对焦
builder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CameraMetadata.CONTROL_AF_TRIGGER_START);
// 设置照片的方向
builder.set(CaptureRequest.JPEG_ORIENTATION, (mCameraType == CameraCharacteristics.LENS_FACING_FRONT) ? 90 : 270);
// 拍照会话开始捕获相片
mCameraSession.capture(builder.build(), null, mHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private ArrayList<String> mShootingArray; // 连拍的相片保存路径列表
// 获取连拍的相片保存路径列表
public ArrayList<String> getShootingList() {
Log.d(TAG, "mShootingArray.size()=" + mShootingArray.size());
return mShootingArray;
}
// 开始连拍
public void startShooting(int duration) {
Log.d(TAG, "正在连拍");
mTakeType = 1;
mShootingArray = new ArrayList<String>();
try {
// 停止连拍
mCameraSession.stopRepeating();
// 把图像读取器添加到预览目标
mPreviewBuilder.addTarget(mImageReader.getSurface());
// 设置连拍请求。此时预览画面会同时发给手机屏幕和图像读取器
mCameraSession.setRepeatingRequest(mPreviewBuilder.build(), null, mHandler);
// duration小等于0时,表示持续连拍,此时外部要调用stopShooting方法来结束连拍
if (duration > 0) {
// 延迟若干秒后启动拍摄停止任务
mHandler.postDelayed(mStop, duration);
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
// 停止连拍
public void stopShooting() {
try {
// 停止连拍
mCameraSession.stopRepeating();
// 移除图像读取器的预览目标
mPreviewBuilder.removeTarget(mImageReader.getSurface());
// 设置连拍请求。此时预览画面只会发给手机屏幕
mCameraSession.setRepeatingRequest(mPreviewBuilder.build(), null, mHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
Toast.makeText(mContext, "已完成连拍,按返回键回到上页查看照片。", Toast.LENGTH_SHORT).show();
}
// 定义一个拍摄停止任务
private Runnable mStop = new Runnable() {
@Override
public void run() {
stopShooting();
}
};
// 打开相机
private void openCamera() {
// 从系统服务中获取相机管理器
CameraManager cm = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
String cameraid = mCameraType + "";
try {
// 获取可用相机设备列表
CameraCharacteristics cc = cm.getCameraCharacteristics(cameraid);
// 检查相机硬件的支持级别
// CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL表示完全支持
// CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED表示有限支持
// CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY表示遗留的
int level = cc.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
if (level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL){
ToastUtil.toastWord(mContext,"完全支持");
}else if (level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED){
ToastUtil.toastWord(mContext,"有限支持");
}else if (level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY){
ToastUtil.toastWord(mContext,"不建议使用");
}
StreamConfigurationMap map = cc.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
Size largest = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), new CompareSizeByArea());
// 获取预览画面的尺寸
mPreViewSize = map.getOutputSizes(SurfaceTexture.class)[0];
// 创建一个JPEG格式的图像读取器
mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), ImageFormat.JPEG, 10);
// 设置图像读取器的图像可用监听器,一旦捕捉到图像数据就会触发监听器的onImageAvailable方法
mImageReader.setOnImageAvailableListener(onImageAvaiableListener, mHandler);
if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
// 开启摄像头
cm.openCamera(cameraid, mDeviceStateCallback, mHandler);
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
// 关闭相机
private void closeCamera() {
if (null != mCameraSession) {
mCameraSession.close(); // 关闭相机拍摄会话
mCameraSession = null;
}
if (null != mCameraDevice) {
mCameraDevice.close(); // 关闭相机设备
mCameraDevice = null;
}
if (null != mImageReader) {
mImageReader.close(); // 关闭图像读取器
mImageReader = null;
}
}
// 定义一个表面纹理变更监听器。TextureView准备就绪后,立即开启相机
private SurfaceTextureListener mSurfacetextlistener = new SurfaceTextureListener() {
// 在纹理表面可用时触发
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
openCamera(); // 打开相机
}
// 在纹理表面的尺寸发生改变时触发
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {}
// 在纹理表面销毁时触发
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
closeCamera(); // 关闭相机
return true;
}
// 在纹理表面更新时触发
public void onSurfaceTextureUpdated(SurfaceTexture surface) {}
};
// 创建相机预览会话
private void createCameraPreviewSession() {
// 获取纹理视图的表面纹理
SurfaceTexture texture = getSurfaceTexture();
// 设置表面纹理的默认缓存尺寸
texture.setDefaultBufferSize(mPreViewSize.getWidth(), mPreViewSize.getHeight());
// 创建一个该表面纹理的表面对象
Surface surface = new Surface(texture);
try {
mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
// 把纹理视图添加到预览目标
mPreviewBuilder.addTarget(surface);
// 设置自动对焦模式
mPreviewBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// 设置自动曝光模式
mPreviewBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
// 开始对焦
mPreviewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CameraMetadata.CONTROL_AF_TRIGGER_START);
// 设置照片的方向
mPreviewBuilder.set(CaptureRequest.JPEG_ORIENTATION, (mCameraType == CameraCharacteristics.LENS_FACING_FRONT) ? 90 : 270);
// 创建一个相片捕获会话。此时预览画面显示在纹理视图上
mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
mSessionStateCallback, mHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
// 相机准备就绪后,开启捕捉影像的会话
private CameraDevice.StateCallback mDeviceStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(CameraDevice cameraDevice) {
mCameraDevice = cameraDevice;
createCameraPreviewSession();
}
@Override
public void onDisconnected(CameraDevice cameraDevice) {
cameraDevice.close();
mCameraDevice = null;
}
@Override
public void onError(CameraDevice cameraDevice, int error) {
cameraDevice.close();
mCameraDevice = null;
}
};
// 影像配置就绪后,将预览画面呈现到手机屏幕上
private CameraCaptureSession.StateCallback mSessionStateCallback = new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(CameraCaptureSession session) {
try {
Log.d(TAG, "onConfigured");
mCameraSession = session;
// 设置连拍请求。此时预览画面只会发给手机屏幕
mCameraSession.setRepeatingRequest(mPreviewBuilder.build(), null, mHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(CameraCaptureSession session) {}
};
// 一旦有图像数据生成,立刻触发onImageAvailable事件
private OnImageAvailableListener onImageAvaiableListener = new OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader imageReader) {
Log.d(TAG, "onImageAvailable");
mHandler.post(new ImageSaver(imageReader.acquireNextImage()));
}
};
// 定义一个图像保存任务
private class ImageSaver implements Runnable {
private Image mImage;
public ImageSaver(Image reader) {
mImage = reader;
}
@Override
public void run() {
// 获取本次拍摄的照片保存路径
//获取本次拍摄的照片路径
List<String> listPath = new ArrayList<>();
listPath.add("myCamera");
listPath.add("photos");
String path = PathGetUtil.getLongwayPath(mContext, listPath);
File fileDir = new File(path);
if (!fileDir.exists()) {
fileDir.mkdirs();
}
File filePic = new File(path, "ww" + System.currentTimeMillis() + ".jpg");
if (!filePic.exists()) {
try {
filePic.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
// 保存图片文件
saveImage(filePic.getPath(), mImage.getPlanes()[0].getBuffer());
//BitmapUtil.setPictureDegreeZero(path);
if (mImage != null) {
mImage.close();
}
if (mTakeType == 0) { // 单拍
mPhotoPath = path;
} else { // 连拍
mShootingArray.add(path);
}
Log.d(TAG, "完成保存图片 path=" + path);
}
}
public static void saveImage(String path, ByteBuffer byteBuffer){
try {
File file = new File(path);
boolean append = false;
FileChannel wChannel = new FileOutputStream(file, append).getChannel();
wChannel.write(byteBuffer);
wChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private class CompareSizeByArea implements java.util.Comparator<Size> {
@Override
public int compare(Size lhs, Size rhs) {
return Long.signum((long) lhs.getWidth() * lhs.getHeight()
- (long) rhs.getWidth() * rhs.getHeight());
}
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.cameraself.Camera2View
android:id="@+id/camera2"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_alignParentBottom="true">
<Button
android:id="@+id/one"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="单拍"/>
<Button
android:id="@+id/two"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="连拍"
android:layout_marginLeft="10dp"/>
</LinearLayout>
</RelativeLayout>
MainActivity.java
import android.hardware.camera2.CameraCharacteristics;
import android.os.Build;
import android.os.Handler;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import com.hao.baselib.base.WaterPermissionActivity;
public class MainActivity extends WaterPermissionActivity<MainModel>
implements MainCallback, View.OnClickListener {
private Button one;
private Button two;
private Camera2View camera2;
@Override
protected MainModel getModelImp() {
return new MainModel(this, this);
}
@Override
protected int getContentLayoutId() {
return R.layout.activity_main;
}
@Override
protected void initWidget() {
one = findViewById(R.id.one);
two = findViewById(R.id.two);
camera2 = findViewById(R.id.camera2);
one.setOnClickListener(this);
two.setOnClickListener(this);
requestPermission(READ_EXTERNAL_STORAGE);
}
@Override
protected void doSDRead() {
requestPermission(WRITE_EXTERNAL_STORAGE);
}
@Override
protected void doSDWrite() {
requestPermission(CAMERA);
}
@Override
protected void doCamera() {
// 获取前一个页面传来的摄像头类型
// int camera_type = CameraCharacteristics.LENS_FACING_BACK;
int camera_type = CameraCharacteristics.LENS_FACING_FRONT;
// 设置二代相机视图的摄像头类型
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
camera2.open(camera_type);
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.one:
// 命令二代相机视图执行单拍操作
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
camera2.takePicture();
}
// 拍照需要完成对焦、图像捕获、图片保存等一系列动作,因而要留足时间给系统处理
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, "已完成拍照", Toast.LENGTH_SHORT).show();
}
}, 1500);
break;
case R.id.two:
// 命令二代相机视图执行连拍操作
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
camera2.startShooting(7000);
}
break;
}
}
}
このようにして、Camera2を使用してカスタムカメラを作成するという目的を達成しました。実際のテストでは、Camera2は実際にカメラで撮影した写真よりもはるかに鮮明であり、写真の解像度と最終サイズは改善されていますが、それでもシステムカメラよりも悪く、さらに悪いですWeChatなどの主流アプリケーションのカスタムカメラ。アルゴリズムの最適化、ハードウェアサポート、その他の関連コンテンツが多数あるため、遠く離れています。しかし、それは基本的に私たちの開発ニーズの一部を満たすことができます。したがって、単に写真を撮ってアップロードする必要がある場合は、システムのカメラ機能を呼び出すことをお勧めします。カメラインターフェースをカスタマイズする必要がある場合は、この方法を使用します。また、JetpackでCameraXを勉強し、機会があればブログ記事を書きます。