Camera2中自定义TextureView宽高并实现正常相机预览的方法
解决Camera2框架下TextureView固定400dp高度预览变形问题
1. 先修正布局,固定预览区域尺寸
首先将布局中TextureView的高度设为400dp,保证显示区域的固定:
<FrameLayout android:layout_width="match_parent" android:layout_height="400dp" android:background="@android:color/holo_blue_dark"> <TextureView android:id="@+id/texture" android:layout_width="match_parent" android:layout_height="match_parent"/> </FrameLayout>
2. 选择匹配TextureView比例的相机预览尺寸
相机输出的预览尺寸比例必须和TextureView显示区域比例匹配,否则会出现拉伸变形。添加以下方法筛选最优尺寸:
private Size chooseOptimalSize(Size[] choices, int textureViewWidth, int textureViewHeight) { List<Size> matchedSizes = new ArrayList<>(); // 计算TextureView的显示比例(竖屏场景) float textureRatio = (float) textureViewWidth / textureViewHeight; for (Size option : choices) { // 相机默认输出横屏尺寸,竖屏显示时需交换宽高计算比例 float previewRatio = (float) option.getHeight() / option.getWidth(); // 允许微小比例误差,避免无完全匹配的情况 if (Math.abs(previewRatio - textureRatio) < 0.01) { matchedSizes.add(option); } } // 从匹配尺寸中选分辨率最大的 if (!matchedSizes.isEmpty()) { return Collections.max(matchedSizes, (lhs, rhs) -> Long.signum((long) lhs.getWidth() * lhs.getHeight() - (long) rhs.getWidth() * rhs.getHeight()) ); } // 无匹配尺寸时选最大分辨率的 else { return Collections.max(Arrays.asList(choices), (lhs, rhs) -> Long.signum((long) lhs.getWidth() * lhs.getHeight() - (long) rhs.getWidth() * rhs.getHeight()) ); } }
在打开相机前,需等TextureView布局完成后获取实际尺寸,再筛选预览尺寸:
textureView.post(() -> { int textureWidth = textureView.getWidth(); int textureHeight = textureView.getHeight(); CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); try { String cameraId = manager.getCameraIdList()[0]; // 这里默认选后置相机,可根据需求调整 CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId); StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (map != null) { imageDimension = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), textureWidth, textureHeight); openCamera(); // 打开相机的方法,需自行实现 } } catch (CameraAccessException e) { e.printStackTrace(); } });
3. 修正TextureView的矩阵变换逻辑
之前的矩阵计算混淆了屏幕尺寸和相机预览尺寸,且未考虑相机横屏输出到竖屏显示的旋转需求,替换为以下方法:
private void adjustTextureViewTransform() { if (imageDimension == null || textureView.getWidth() == 0 || textureView.getHeight() == 0) { return; } int textureWidth = textureView.getWidth(); int textureHeight = textureView.getHeight(); Size previewSize = imageDimension; float viewRatio = (float) textureWidth / textureHeight; float previewRatio = (float) previewSize.getHeight() / previewSize.getWidth(); // 竖屏显示的预览比例 Matrix matrix = new Matrix(); if (viewRatio > previewRatio) { // TextureView更宽,缩放高度填满显示区域,宽度裁剪 float scale = (float) textureHeight / previewSize.getWidth(); float scaledWidth = scale * previewSize.getHeight(); float dx = (textureWidth - scaledWidth) / 2f; matrix.setScale(scale, scale); matrix.postTranslate(dx, 0); } else { // TextureView更高,缩放宽度填满显示区域,高度裁剪 float scale = (float) textureWidth / previewSize.getHeight(); float scaledHeight = scale * previewSize.getWidth(); float dy = (textureHeight - scaledHeight) / 2f; matrix.setScale(scale, scale); matrix.postTranslate(0, dy); } // 旋转90度,适配相机横屏输出到竖屏显示的需求 matrix.postRotate(90, textureWidth / 2f, textureHeight / 2f); textureView.setTransform(matrix); }
4. 触发矩阵变换的时机
在TextureView尺寸变化或相机预览创建完成时调用变换方法:
textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { // 打开相机逻辑 } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { adjustTextureViewTransform(); } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { closeCamera(); // 自行实现关闭相机的逻辑 return true; } @Override public void onSurfaceTextureUpdated(SurfaceTexture surface) {} }); // 在createCameraPreview的onConfigured回调中也需要调用 @Override public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { if (null == cameraDevice) return; cameraCaptureSessions = cameraCaptureSession; adjustTextureViewTransform(); // 添加这行 updatePreview(); }
关键修正点说明
- 布局固定了预览区域的400dp高度,避免尺寸不确定导致的适配错误
- 预览尺寸选择时严格匹配TextureView的显示比例,从根源避免拉伸
- 矩阵计算基于相机预览尺寸和TextureView实际尺寸,同时处理了横屏转竖屏的旋转需求,保证预览内容不变形且居中显示
内容的提问来源于stack exchange,提问作者Vijayadhas Chandrasekaran
相关产品推荐
相关产品推荐

