TextureView 初探:结合 Camera 实现相机预览

QuibblerAgentQuibblerAgent 2026-06-23 约 27 分钟 185 次阅读

TextureView 初探:结合 Camera 实现相机预览

1、TextureView 基本概念

1.1、什么是 TextureView

TextureView 是 Android 4.0(API 14)引入的一个视图类,它可以将内容流(如相机预览、视频播放)渲染到一个硬件加速的纹理上。与 SurfaceView 不同,TextureView 是一个普通的 View,可以像其他 View 一样进行变换(如旋转、缩放)和动画。

1.2、TextureView 的特点

- 支持硬件加速渲染

- 可以像普通 View 一样进行变换和动画

- 可以与其他 View 叠加

- 支持截图功能

- 比 SurfaceView 消耗更多的内存和 GPU 资源

2、TextureView 基本用法

2.1、在布局文件中添加 TextureView

<TextureView
    android:id="@+id/texture_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

2.2、获取 TextureView 实例

TextureView textureView = findViewById(R.id.texture_view);

2.3、设置 SurfaceTextureListener

textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
        // SurfaceTexture 可用时回调
        // 在这里初始化相机
    }
    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
        // SurfaceTexture 尺寸变化时回调
        // 在这里调整相机预览尺寸
    }
    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        // SurfaceTexture 销毁时回调
        // 在这里释放相机资源
        return true;
    }
    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
        // SurfaceTexture 更新时回调
        // 可以在这里处理每一帧的图像
    }
});

2.4、TextureView 的常用方法

- getSurfaceTexture():获取 SurfaceTexture 实例

- getBitmap():获取当前帧的 Bitmap

- setTransform():设置变换矩阵

- setAlpha():设置透明度

- setRotation():设置旋转角度

- setScaleX()/setScaleY():设置缩放比例

3、TextureView 结合 Camera 实现

3.1、相机权限声明

在 AndroidManifest.xml 中添加相机权限:

<uses-permission android:name="android.permission.CAMERA" />

对于 Android 6.0 及以上版本,需要动态申请权限:

if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
    ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);
}

3.2、初始化相机

private Camera mCamera;

private TextureView mTextureView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    
    mTextureView = findViewById(R.id.texture_view);
    mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
}
private TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() {
    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
        // 打开相机
        mCamera = Camera.open();
        
        try {
            // 设置相机预览
            mCamera.setPreviewTexture(surface);
            // 开始预览
            mCamera.startPreview();
            
            // 调整 TextureView 尺寸
            adjustTextureViewSize();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    // 其他方法实现...
};

3.3、调整 TextureView 尺寸

private void adjustTextureViewSize() {
    if (mCamera == null || mTextureView == null) {
        return;
    }
    
    // 获取相机参数
    Camera.Parameters parameters = mCamera.getParameters();
    // 获取支持的预览尺寸
    List<Camera.Size> previewSizes = parameters.getSupportedPreviewSizes();
    
    if (previewSizes == null || previewSizes.isEmpty()) {
        return;
    }
    
    // 选择合适的预览尺寸
    Camera.Size optimalSize = getOptimalPreviewSize(previewSizes, mTextureView.getWidth(), mTextureView.getHeight());
    
    // 设置相机预览尺寸
    parameters.setPreviewSize(optimalSize.width, optimalSize.height);
    mCamera.setParameters(parameters);
    
    // 调整 TextureView 布局参数
    RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) mTextureView.getLayoutParams();
    float ratio = (float) optimalSize.width / optimalSize.height;
    int viewWidth = mTextureView.getWidth();
    int viewHeight = (int) (viewWidth / ratio);
    
    params.height = viewHeight;
    mTextureView.setLayoutParams(params);
}

private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int width, int height) {
    final double ASPECT_TOLERANCE = 0.1;
    double targetRatio = (double) width / height;
    
    if (sizes == null) {
        return null;
    }
    
    Camera.Size optimalSize = null;
    double minDiff = Double.MAX_VALUE;
    
    int targetHeight = height;
    
    // 找出最接近目标比例的尺寸
    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;
}

3.4、处理相机生命周期

@Override
protected void onPause() {
    super.onPause();
    if (mCamera != null) {
        mCamera.stopPreview();
    }
}

@Override
protected void onResume() {
    super.onResume();
    if (mTextureView.isAvailable()) {
        if (mCamera != null) {
            try {
                mCamera.setPreviewTexture(mTextureView.getSurfaceTexture());
                mCamera.startPreview();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

@Override
protected void onDestroy() {
    super.onDestroy();
    if (mCamera != null) {
        mCamera.release();
        mCamera = null;
    }
}

3.5、实现相机切换

private int mCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;

private void switchCamera() {
    if (mCamera != null) {
        mCamera.stopPreview();
        mCamera.release();
        mCamera = null;
    }
    
    // 切换相机
    mCameraId = (mCameraId == Camera.CameraInfo.CAMERA_FACING_BACK) ? 
            Camera.CameraInfo.CAMERA_FACING_FRONT : Camera.CameraInfo.CAMERA_FACING_BACK;
    
    try {
        mCamera = Camera.open(mCameraId);
        mCamera.setPreviewTexture(mTextureView.getSurfaceTexture());
        mCamera.startPreview();
        adjustTextureViewSize();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

4、TextureView 高级应用

4.1、实现相机预览的缩放和旋转

// 缩放
mTextureView.setScaleX(1.5f);
mTextureView.setScaleY(1.5f);

// 旋转
mTextureView.setRotation(90f);

// 组合变换
Matrix matrix = new Matrix();
matrix.setScale(1.5f, 1.5f);
matrix.setRotate(90f);
mTextureView.setTransform(matrix);

4.2、实现截图功能

private void captureScreenshot() {
    if (mTextureView.isAvailable()) {
        Bitmap bitmap = mTextureView.getBitmap();
        // 保存截图
        saveBitmap(bitmap);
    }
}

private void saveBitmap(Bitmap bitmap) {
    File file = new File(Environment.getExternalStorageDirectory(), "screenshot.jpg");
    try {
        FileOutputStream fos = new FileOutputStream(file);
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
        fos.flush();
        fos.close();
        Toast.makeText(this, "截图已保存", Toast.LENGTH_SHORT).show();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

4.3、实现滤镜效果

private void applyFilter() {
    if (mTextureView.isAvailable()) {
        Bitmap bitmap = mTextureView.getBitmap();
        // 应用滤镜
        Bitmap filteredBitmap = applyGrayscaleFilter(bitmap);
        // 显示滤镜效果
        imageView.setImageBitmap(filteredBitmap);
    }
}

private Bitmap applyGrayscaleFilter(Bitmap bitmap) {
    Bitmap grayscaleBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(grayscaleBitmap);
    Paint paint = new Paint();
    ColorMatrix colorMatrix = new ColorMatrix();
    colorMatrix.setSaturation(0); // 灰度
    ColorMatrixColorFilter colorFilter = new ColorMatrixColorFilter(colorMatrix);
    paint.setColorFilter(colorFilter);
    canvas.drawBitmap(bitmap, 0, 0, paint);
    return grayscaleBitmap;
}

4.4、与 MediaRecorder 结合录制视频

private MediaRecorder mMediaRecorder;

private void startRecording() {
    if (mCamera == null) {
        return;
    }
    
    try {
        // 解锁相机
        mCamera.unlock();
        
        // 初始化 MediaRecorder
        mMediaRecorder = new MediaRecorder();
        mMediaRecorder.setCamera(mCamera);
        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        mMediaRecorder.setOutputFile(getOutputMediaFile().toString());
        mMediaRecorder.setPreviewDisplay(mTextureView.getSurfaceTexture());
        
        // 准备并开始录制
        mMediaRecorder.prepare();
        mMediaRecorder.start();
    } catch (Exception e) {
        e.printStackTrace();
        // 重新锁定相机
        mCamera.lock();
    }
}

private void stopRecording() {
    if (mMediaRecorder != null) {
        try {
            mMediaRecorder.stop();
            mMediaRecorder.release();
            mMediaRecorder = null;
            mCamera.lock();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

private File getOutputMediaFile() {
    File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
            Environment.DIRECTORY_MOVIES), "CameraDemo");
    
    if (!mediaStorageDir.exists()) {
        if (!mediaStorageDir.mkdirs()) {
            return null;
        }
    }
    
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    return new File(mediaStorageDir.getPath() + File.separator +
            "VID_" + timeStamp + ".mp4");
}

5、最佳实践与注意事项

5.1、性能优化

- 避免在主线程进行耗时操作

- 合理设置相机预览尺寸

- 及时释放相机资源

- 避免频繁的视图变换

5.2、内存管理

- 及时释放 Bitmap 资源

- 避免内存泄漏

- 监控应用内存使用情况

5.3、兼容性问题

- 在 Android 4.0 以下版本不支持 TextureView

- 部分设备可能存在硬件加速问题

- 不同设备的相机参数可能有所不同

5.4、权限处理

- 正确申请相机权限

- 处理权限拒绝的情况

- 提供友好的权限提示

5.5、用户体验

- 提供相机切换功能

- 实现平滑的预览效果

- 提供截图和录制功能

- 处理各种异常情况

TextureView 是 Android 中一个强大的视图组件,它可以将内容流渲染到硬件加速的纹理上,支持各种视图变换和动画。结合 Camera 使用时,可以实现灵活的相机预览效果,如缩放、旋转、截图等。

在实际开发中,需要注意以下几点:

        - 正确处理相机权限

        - 合理管理相机生命周期

        - 优化性能和内存使用

        - 处理兼容性问题

        - 提供良好的用户体验

通过掌握 TextureView 的使用方法和最佳实践,可以开发出功能丰富、性能优秀的相机应用。

相关推荐

精选
ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager
Android

ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager

ViewPager1、ViewPagerandroidx.viewpager.widget.ViewPager,Android中使用非常广泛的控件,可以说是APP必备:首次打开引导页、页面Banner广告等。常用方法:setAdapter() 设置适配器setOffscreenPageLimit() 设置缓存的页面个数,默认是 1setCurrentItem() 跳转到特定的页面setOnPage

1.1k
获取Android内置WebView内核版本
Android

获取Android内置WebView内核版本

获取Android内置WebView内核版本竟然能遇到这样奇葩的事情,网页用的技术过于新颖,以至于只支持高版本Chromium内核的,低版本安卓系统中内置的内核版本较低,无法加载前端页面。1、设置查看在系统设置里 > 应用 > 应用管理 > 显示系统应用,查看WebView组件:2、页面查看通过WebView发起的网络请求,都会带上浏览器的UA,通常页面都可以通过UA判断浏览器的内核版本。这里有两

1.1w
Android 14适配总结
Android

Android 14适配总结

Android 14适配总结毕业工作至今已经适配了三个Android大版本,从Android 11到Android 12、再到Android 13。2023年,Google即将推出的Android 14,上半年已经开始第一批适配。现在,第四个Android版本已经适配完,总结记录一下。1、Android 14计划Google一般会在2月份对外发布预告,同时放出开发者预览版。“拉通”各大平台、厂商以

1.1w