如何在Fragment中显示相机实时画面?适配低版本安卓的图文对比需求
在Android Fragment中实现相机实时预览(适配5.0-6.0,无需系统相机APP)
我正好做过类似的相机预览+图片对比的应用,结合你的需求(适配Marshmallow/Lollipop,不支持Nougat分屏),给你梳理下核心方案:
首先明确:你完全不需要调用系统默认相机APP,SurfaceView 或者 TextureView 就是用来直接渲染相机实时画面的核心控件——条码阅读器、快速图片编辑器这类应用,都是基于这两个组件实现相机预览的。
1. 核心方案选择
- SurfaceView:性能优先,它的渲染逻辑在独立线程运行,不会阻塞UI线程,适合相机这种高帧率实时预览场景,是大多数相机类APP的首选。
- TextureView:灵活性更高,支持对预览画面做缩放、旋转、裁剪等自定义变换,如果你需要和图库图片做精准叠加对比,这个控件会更合适。
2. 完整实现步骤(以SurfaceView为例)
2.1 权限处理(适配Marshmallow动态权限)
Android 6.0开始相机权限属于危险权限,必须动态申请。在Fragment中先检查权限,未授权则发起申请:
@Override public void onResume() { super.onResume(); if (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { initCamera(); } else { requestPermissions(new String[]{Manifest.permission.CAMERA}, 100); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == 100) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { initCamera(); } else { Toast.makeText(getContext(), "需要相机权限才能使用预览功能", Toast.LENGTH_SHORT).show(); } } }
2.2 Fragment布局(叠加相机预览+图库图片)
布局里同时放SurfaceView(相机预览)和ImageView(图库图片),通过透明度实现半透明叠加对比:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- 相机实时预览 --> <SurfaceView android:id="@+id/camera_surface" android:layout_width="match_parent" android:layout_height="match_parent" /> <!-- 图库图片(半透明叠加) --> <ImageView android:id="@+id/gallery_img" android:layout_width="match_parent" android:layout_height="match_parent" android:alpha="0.5" android:scaleType="fitCenter" /> </RelativeLayout>
2.3 Fragment核心代码(相机预览逻辑)
public class CameraCompareFragment extends Fragment implements SurfaceHolder.Callback { private Camera mCamera; private SurfaceView mSurfaceView; private SurfaceHolder mSurfaceHolder; private ImageView mGalleryImg; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_camera_compare, container, false); mSurfaceView = view.findViewById(R.id.camera_surface); mGalleryImg = view.findViewById(R.id.gallery_img); mSurfaceHolder = mSurfaceView.getHolder(); mSurfaceHolder.addCallback(this); // 这里可以添加图库图片选择逻辑(比如点击按钮打开图库) return view; } private void initCamera() { try { mCamera = Camera.open(); // 默认打开后置摄像头,需要前置则传1 setupCameraParams(); } catch (Exception e) { Toast.makeText(getContext(), "相机初始化失败", Toast.LENGTH_SHORT).show(); } } private void setupCameraParams() { Camera.Parameters params = mCamera.getParameters(); // 设置最优预览尺寸(和SurfaceView尺寸匹配,避免拉伸) List<Camera.Size> previewSizes = params.getSupportedPreviewSizes(); Camera.Size optimalSize = getOptimalPreviewSize(previewSizes, mSurfaceView.getWidth(), mSurfaceView.getHeight()); params.setPreviewSize(optimalSize.width, optimalSize.height); params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); // 自动对焦 mCamera.setParameters(params); } // 计算和控件尺寸匹配的最优预览尺寸 private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int targetWidth, int targetHeight) { final double ASPECT_TOLERANCE = 0.1; double targetRatio = (double) targetWidth / targetHeight; Camera.Size optimalSize = null; double minDiff = Double.MAX_VALUE; for (Camera.Size size : sizes) { double ratio = (double) size.width / size.height; if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue; if (Math.abs(size.height - targetHeight) < minDiff) { optimalSize = size; minDiff = Math.abs(size.height - targetHeight); } } if (optimalSize == null) { minDiff = Double.MAX_VALUE; for (Camera.Size size : sizes) { if (Math.abs(size.height - targetHeight) < minDiff) { optimalSize = size; minDiff = Math.abs(size.height - targetHeight); } } } return optimalSize; } @Override public void surfaceCreated(SurfaceHolder holder) { if (mCamera != null) { try { mCamera.setPreviewDisplay(holder); mCamera.startPreview(); } catch (IOException e) { e.printStackTrace(); } } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { if (mSurfaceHolder.getSurface() == null) return; // 画面尺寸变化时重新设置相机参数 mCamera.stopPreview(); setupCameraParams(); try { mCamera.setPreviewDisplay(mSurfaceHolder); mCamera.startPreview(); } catch (IOException e) { e.printStackTrace(); } } @Override public void surfaceDestroyed(SurfaceHolder holder) { // 相机资源在onPause中统一释放 } @Override public void onPause() { super.onPause(); if (mCamera != null) { mCamera.stopPreview(); mCamera.release(); mCamera = null; } } }
3. 适配注意事项
- 禁止Nougat分屏:在Manifest的
<activity>标签中添加android:resizeableActivity="false",这样Nougat及以上系统会禁止该应用进入分屏模式。 - Lollipop适配:Android 5.0不需要动态权限,直接在Manifest声明
<uses-permission android:name="android.permission.CAMERA" />即可。 - 多摄像头支持:如果需要切换前后摄像头,调用
Camera.open(0)(后置)或Camera.open(1)(前置)即可,注意切换前要释放旧的相机实例。
内容的提问来源于stack exchange,提问作者Wesos de Queso
相关产品推荐
相关产品推荐

