Android Camera2Video:实现TextureView视频预览无裁剪无拉伸适配
相机预览适配问题:实现无裁剪无拉伸、比例不符时显示黑边
我基于Google的Camera2Video示例修改了一款横屏APP,支持用户选择视频分辨率、帧率及曝光设置。当前布局中,相机视频显示用的AutoFitTextureView受右侧固定宽度的mainPanel(FrameLayout)和底部固定高度的longTexture(FrameLayout)约束,宽高比随设备屏幕比例变化。用户可选择视频预览的宽高比(如4:3、16:9、2:1),期望预览能完整适配TextureView,无裁剪、无拉伸,仅在比例不一致时显示黑边。但目前预览被缩放填充整个TextureView,虽未拉伸却出现裁剪,无法呈现视频原比例。
当前布局XML代码:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <com.example.android.camera2video.AutoFitTextureView android:id="@+id/texture" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@id/longTexture" android:layout_alignLeft="@id/longTexture" android:layout_alignRight="@id/longTexture" android:layout_alignTop="@id/mainPanel" /> <FrameLayout android:id="@+id/longTexture" android:layout_width="match_parent" android:layout_height="75dp" android:layout_alignParentBottom="true" android:layout_toLeftOf="@id/mainPanel" android:background="#0088FF" /> <FrameLayout android:id="@+id/mainPanel" android:layout_width="250dp" android:layout_height="match_parent" android:layout_alignParentTop="true" android:layout_alignParentRight="true" android:layout_alignParentBottom="true" android:background="#222222" android:orientation="horizontal"> <!-- 按钮和下拉选择器 --> </FrameLayout> </RelativeLayout>
解决方案
1. 调整布局结构,用容器包裹TextureView实现居中黑边
原布局中TextureView被强制填充可用区域,导致预览被裁剪。我们可以用一个FrameLayout作为TextureView的父容器,让TextureView在容器内居中,容器填充原本的可用区域并设置黑色背景(作为黑边):
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- 预览容器,填充可用区域,黑色背景作为黑边 --> <FrameLayout android:id="@+id/previewContainer" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_above="@id/longTexture" android:layout_toLeftOf="@id/mainPanel" android:background="#000000"> <!-- 预览TextureView,居中显示 --> <com.example.android.camera2video.AutoFitTextureView android:id="@+id/texture" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" /> </FrameLayout> <FrameLayout android:id="@+id/longTexture" android:layout_width="match_parent" android:layout_height="75dp" android:layout_alignParentBottom="true" android:layout_toLeftOf="@id/mainPanel" android:background="#0088FF" /> <FrameLayout android:id="@+id/mainPanel" android:layout_width="250dp" android:layout_height="match_parent" android:layout_alignParentTop="true" android:layout_alignParentRight="true" android:layout_alignParentBottom="true" android:background="#222222" android:orientation="horizontal"> <!-- 按钮和下拉选择器 --> </FrameLayout> </RelativeLayout>
2. 修改AutoFitTextureView的测量逻辑,保留预览比例避免裁剪
原AutoFitTextureView的onMeasure方法是强制填充父容器,导致预览比例不符时被裁剪。我们需要修改该方法,使其计算出能放入父容器的最大尺寸,同时保持用户选择的预览比例:
public class AutoFitTextureView extends TextureView { private int mRatioWidth = 0; private int mRatioHeight = 0; public AutoFitTextureView(Context context) { this(context, null); } public AutoFitTextureView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } /** * 设置预览宽高比 * @param width 比例宽 * @param height 比例高 */ public void setAspectRatio(int width, int height) { if (width < 0 || height < 0) { throw new IllegalArgumentException("宽高不能为负数"); } mRatioWidth = width; mRatioHeight = height; requestLayout(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int parentWidth = MeasureSpec.getSize(widthMeasureSpec); int parentHeight = MeasureSpec.getSize(heightMeasureSpec); if (mRatioWidth == 0 || mRatioHeight == 0) { // 未设置比例时,填充父容器 setMeasuredDimension(parentWidth, parentHeight); return; } // 计算预览比例和父容器比例 float previewRatio = (float) mRatioWidth / mRatioHeight; float parentRatio = (float) parentWidth / parentHeight; int finalWidth, finalHeight; if (previewRatio > parentRatio) { // 预览更宽,按父容器高度计算最大宽度,若超过父容器宽度则按宽度适配高度 finalHeight = parentHeight; finalWidth = Math.round(parentHeight * previewRatio); if (finalWidth > parentWidth) { finalWidth = parentWidth; finalHeight = Math.round(parentWidth / previewRatio); } } else { // 预览更高,按父容器宽度计算最大高度,若超过父容器高度则按高度适配宽度 finalWidth = parentWidth; finalHeight = Math.round(parentWidth / previewRatio); if (finalHeight > parentHeight) { finalHeight = parentHeight; finalWidth = Math.round(parentHeight * previewRatio); } } // 设置TextureView的最终尺寸,确保完整显示预览内容 setMeasuredDimension(finalWidth, finalHeight); } }
3. 关联用户选择的比例与相机预览尺寸
当用户选择预览比例(如16:9)时,需要从相机支持的预览尺寸中选择最匹配该比例的尺寸,并调用setAspectRatio方法更新TextureView的比例:
// 示例:用户选择16:9比例时 int selectedRatioWidth = 16; int selectedRatioHeight = 9; textureView.setAspectRatio(selectedRatioWidth, selectedRatioHeight); // 从相机支持的尺寸中筛选匹配比例的最优尺寸 Size optimalSize = chooseOptimalSize(cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(SurfaceTexture.class), selectedRatioWidth, selectedRatioHeight); // 后续将该尺寸用于相机预览配置
chooseOptimalSize方法可参考Camera2Video示例中的实现,调整为优先匹配用户选择的比例。
内容的提问来源于stack exchange,提问作者Interstadial
相关产品推荐
相关产品推荐

