使用Camera拍照
使用 Camera 拍照
在 Android 中,使用 Camera
API(旧版)或 Camera2
API(API 21+)可以实现拍照功能。Camera
API 适用于较老设备(API 1-20),但已被标记为弃用;Camera2
API 提供更强大的功能(如手动控制、RAW 格式),是现代应用的首选。本文将重点介绍使用 Camera2 API 拍照,结合 Android 动画合集的背景,展示如何在自定义 View 中集成拍照功能,并添加动画效果(如预览缩放)。同时提供 Camera
API 的简要说明,以便兼容旧设备。内容包括初始化、预览、拍照、保存照片及权限处理。
Camera2 API 的作用与原理
- 作用:
Camera2
API 提供对相机硬件的精细控制,支持预览、拍照、录像及高级功能(如手动对焦、曝光)。它通过CameraManager
访问设备相机,结合SurfaceView
或TextureView
显示预览。 - 原理:
Camera2
使用会话(CaptureSession)管理相机操作,CaptureRequest
定义每次捕捉的参数(如对焦、闪光灯)。拍照结果通过ImageReader
或文件输出。 - 应用场景:
- 自定义相机应用(如美颜相机)。
- 动画效果(如拍照时的缩放反馈)。
- 实时预览(如二维码扫描)。
- 结合 Canvas/Paint 绘制相机预览上的覆盖层。
权限要求
拍照需要以下权限,在 AndroidManifest.xml
中声明:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- API 28 及以下 -->
<uses-feature android:name="android.hardware.camera" android:required="true" />
- 动态权限(API 23+):
Manifest.permission.CAMERA
:访问相机。Manifest.permission.WRITE_EXTERNAL_STORAGE
:保存照片(API 28 及以下)。- 使用
ActivityCompat.requestPermissions
请求权限。
Camera2 API 拍照步骤
- 初始化 CameraManager:获取设备相机列表,选择后置相机。
- 显示预览:使用
TextureView
或SurfaceView
显示相机预览。 - 创建 CaptureSession:配置预览和拍照的输出目标。
- 拍照:发送
CaptureRequest
捕获照片,保存到文件或 Bitmap。 - 释放资源:关闭相机设备和会话。
完整示例(Camera2 拍照 + 动画)
以下是一个完整的 Camera2 拍照示例,结合属性动画在拍照时显示缩放效果。
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.*;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Size;
import android.view.Surface;
import android.view.TextureView;
import android.widget.Button;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.animation.ObjectAnimator;
import java.io.File;
import java.io.FileOutputStream;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Locale;
public class Camera2Activity extends AppCompatActivity {
private static final int REQUEST_CAMERA_PERMISSION = 100;
private TextureView textureView;
private CameraDevice cameraDevice;
private CameraCaptureSession captureSession;
private CaptureRequest.Builder previewRequestBuilder;
private Handler backgroundHandler;
private HandlerThread backgroundThread;
private String cameraId;
private Size previewSize;
private ImageReader imageReader;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_camera2);
textureView = findViewById(R.id.texture_view);
Button captureButton = findViewById(R.id.capture_button);
// 请求相机权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);
} else {
setupCamera();
}
// 拍照按钮
captureButton.setOnClickListener(v -> takePicture());
// TextureView 监听
textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
openCamera();
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {}
});
}
private void setupCamera() {
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
// 选择后置相机
for (String id : manager.getCameraIdList()) {
CameraCharacteristics characteristics = manager.getCameraCharacteristics(id);
Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
if (facing != null && facing == CameraCharacteristics.LENS_FACING_BACK) {
cameraId = id;
// 获取预览尺寸
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map != null) {
previewSize = map.getOutputSizes(SurfaceTexture.class)[0];
}
break;
}
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private void openCamera() {
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
// 启动后台线程
backgroundThread = new HandlerThread("CameraBackground");
backgroundThread.start();
backgroundHandler = new Handler(backgroundThread.getLooper());
// 设置 ImageReader 用于拍照
imageReader = ImageReader.newInstance(previewSize.getWidth(), previewSize.getHeight(), ImageFormat.JPEG, 2);
imageReader.setOnImageAvailableListener(reader -> {
// 保存照片
try (Image image = reader.acquireNextImage()) {
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
saveImage(bytes);
}
}, backgroundHandler);
// 打开相机
manager.openCamera(cameraId, new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
cameraDevice = camera;
startPreview();
}
@Override
public void onDisconnected(@NonNull CameraDevice camera) {
camera.close();
cameraDevice = null;
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {
camera.close();
cameraDevice = null;
}
}, backgroundHandler);
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private void startPreview() {
try {
SurfaceTexture texture = textureView.getSurfaceTexture();
texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
Surface surface = new Surface(texture);
previewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
previewRequestBuilder.addTarget(surface);
cameraDevice.createCaptureSession(Arrays.asList(surface, imageReader.getSurface()), new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
captureSession = session;
try {
previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
captureSession.setRepeatingRequest(previewRequestBuilder.build(), null, backgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {}
}, backgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private void takePicture() {
if (cameraDevice == null) return;
// 拍照动画
ObjectAnimator scaleX = ObjectAnimator.ofFloat(textureView, "scaleX", 1f, 0.9f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(textureView, "scaleY", 1f, 0.9f, 1f);
scaleX.setDuration(200);
scaleY.setDuration(200);
scaleX.start();
scaleY.start();
try {
CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(imageReader.getSurface());
captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
captureSession.capture(captureBuilder.build(), null, backgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private void saveImage(byte[] bytes) {
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
"photo_" + new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date()) + ".jpg");
try (FileOutputStream output = new FileOutputStream(file)) {
output.write(bytes);
runOnUiThread(() -> Toast.makeText(this, "Photo saved: " + file.getAbsolutePath(), Toast.LENGTH_SHORT).show());
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CAMERA_PERMISSION && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
setupCamera();
} else {
Toast.makeText(this, "Camera permission denied", Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onPause() {
super.onPause();
if (captureSession != null) {
captureSession.close();
captureSession = null;
}
if (cameraDevice != null) {
cameraDevice.close();
cameraDevice = null;
}
if (backgroundThread != null) {
backgroundThread.quitSafely();
backgroundThread = null;
backgroundHandler = null;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (imageReader != null) {
imageReader.close();
imageReader = null;
}
}
}
- 布局 XML(res/layout/activity_camera2.xml):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextureView
android:id="@+id/texture_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<Button
android:id="@+id/capture_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Capture" />
</LinearLayout>
- 说明:
- 权限:动态请求相机权限,保存照片需存储权限(API 28 及以下)。
- 预览:
TextureView
显示相机预览,SurfaceTexture
设置分辨率。 - 拍照:点击按钮触发拍照,
ImageReader
获取 JPEG 数据,保存到 Pictures 目录。 - 动画:拍照时添加缩放动画(
scaleX
、scaleY
),增强交互反馈。 - 生命周期:在
onPause
关闭会话和相机,onDestroy
释放ImageReader
。
旧版 Camera API(简要说明)
Camera
API(API 1-20,弃用)使用更简单,但功能有限。以下是基本步骤:
- 初始化:
Camera camera = Camera.open();
- 预览:绑定到
SurfaceView
,设置camera.setPreviewDisplay(holder)
。 - 拍照:
camera.takePicture(null, null, (data, camera) -> saveImage(data));
- 释放:
camera.release();
- 代码片段:
Camera camera = Camera.open();
SurfaceView surfaceView = findViewById(R.id.surface_view);
SurfaceHolder holder = surfaceView.getHolder();
holder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
try {
camera.setPreviewDisplay(holder);
camera.startPreview();
} catch (Exception e) {
e.printStackTrace();
}
}
// ...
});
findViewById(R.id.capture_button).setOnClickListener(v -> camera.takePicture(null, null, (data, cam) -> saveImage(data)));
- 注意:
Camera
API 已弃用,建议使用Camera2
或 CameraX(更现代的封装库)。
优缺点
- 优点:
Camera2
提供精细控制(对焦、曝光、闪光灯等)。- 支持多种分辨率和格式(JPEG、RAW)。
- 适合自定义相机应用。
- 缺点:
Camera2
复杂,状态管理繁琐。- 旧版
Camera
API 已弃用,兼容性问题多。 - 权限处理和设备差异需特别注意。
- 替代方案:
- CameraX:Jetpack 库,简化相机开发,推荐现代应用。
- Third-party Libraries:如 Fotoapparat。
- OpenGL/Canvas:结合自定义渲染效果。
注意事项
- 权限:API 23+ 需动态请求权限,处理拒绝情况。
- 设备兼容性:检查设备相机特性(如
CameraCharacteristics
),处理无相机设备的情况。 - 性能:异步操作(
prepareAsync
、capture
)避免阻塞主线程;预览分辨率影响性能。 - 生命周期:在
onPause
和onDestroy
释放资源,避免泄漏。 - 存储:API 29+ 使用
MediaStore
替代直接文件保存。 - 调试:通过 Log 检查相机状态,验证预览和拍照结果。
扩展:Jetpack Compose + CameraX
CameraX
是推荐的现代相机 API,结合 Compose 实现更简洁。以下是示例:
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCaptureException
import androidx.camera.view.PreviewView
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat
import androidx.camera.core.CameraSelector
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import java.io.File
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.Executor
@Composable
fun CameraXScreen() {
val context = LocalContext.current
val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(context) }
var imageCapture by remember { mutableStateOf<ImageCapture?>(null) }
Column {
AndroidView(
factory = { ctx ->
PreviewView(ctx).apply {
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder().build().also {
it.setSurfaceProvider(this.surfaceProvider)
}
imageCapture = ImageCapture.Builder().build()
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
ctx as ComponentActivity,
CameraSelector.DEFAULT_BACK_CAMERA,
preview,
imageCapture
)
}, ContextCompat.getMainExecutor(ctx))
}
},
modifier = Modifier
.fillMaxWidth()
.height(400.dp)
)
Button(
onClick = {
val file = File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES),
"IMG_${SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())}.jpg")
val outputOptions = ImageCapture.OutputFileOptions.Builder(file).build()
imageCapture?.takePicture(
outputOptions,
ContextCompat.getMainExecutor(context),
object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
// 动画效果
}
override fun onError(exc: ImageCaptureException) {
exc.printStackTrace()
}
}
)
},
modifier = Modifier.padding(16.dp)
) {
Text("Capture")
}
}
}
- 说明:
- 使用
CameraX
简化相机逻辑,PreviewView
显示预览。 ImageCapture
处理拍照,保存到文件。- 可结合
animate*AsState
添加动画(如缩放)。
示例:拍照动画
为拍照添加闪光动画:
captureButton.setOnClickListener(v -> {
ObjectAnimator flash = ObjectAnimator.ofFloat(textureView, "alpha", 1f, 0.5f, 1f);
flash.setDuration(100);
flash.start();
takePicture();
});
如果需要更复杂功能(如对焦控制、视频录制、CameraX 高级用法)、与 Canvas/Paint 的结合、或旧版 Camera API 详细实现,请告诉我!后续可探讨 Android 动画合集的其他部分(如过渡动画、Lottie)。