TextureView实现相机预览拍照功能

1. TextureView简介

TextureView 是一个显示数据流的UI控件。它是View的子类,可以当做普通的View控件使用,在布局、动画和变换(平移、缩放、旋转等)中非常方便,
主要用于2种场景 1.相机预览 2:视频播放

这篇文章主要讲述TextureView相机预览拍照功能的实现

2. 代码实例

2.1 自定义CameraTextureView控件,实现SurfaceTextureListener,封装TextureView的初始化和销毁功能

public class CameraTextureView extends TextureView implements TextureView.SurfaceTextureListener {
    private static final String TAG = "CameraTextureView";
    private SurfaceTexture mSurface;
    private int mWidth;
    private int mHeight;

    public CameraTextureView(Context context) {
        this(context, null);
    }

    public CameraTextureView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CameraTextureView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.setSurfaceTextureListener(this);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public CameraTextureView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        this.setSurfaceTextureListener(this);
    }

    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
        mSurface = surface;
        mWidth = width;
        mHeight = height;
        //开启相机是耗时操作,此处异步处理
        new Thread(new OpenCameraRunnable()).start();
    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        CameraManager.getInstance().stopCamera();
        return true;
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
    }

    public SurfaceTexture getSurfaceTexture() {
        return mSurface;
    }

    private class OpenCameraRunnable implements Runnable {

        @Override
        public void run() {
            CameraManager.getInstance().openCamera(getContext(), mSurface, mWidth, mHeight);
        }
    }
}  

2.2 Camera的处理封装到CameraManager类中

public class CameraManager {
    private static CameraManager mCameraManager;
    public static final int LEFT = 0;
    public static final int TOP = 1;
    public static final int RIGHT = 2;
    public static final int BOTTOM = 3;
    private OnCameraActionCallback mOnCameraActionCallback;
    private Camera mCamera;
    private ToneGenerator mToneGenerator;
    private Context mContext;
    private byte[] mCameraData;//拍照返回的图像数据
    private boolean isPreviewing = false;
    private int mCurrentCameraFacing;//当前摄像头
    private int mDeviceOrientation = TOP;

    public static synchronized CameraManager getInstance() {
        if (mCameraManager == null) {
            mCameraManager = new CameraManager();
        }
        return mCameraManager;
    }

    /**
     * 打开Camera
     */
    public void openCamera(Context context, SurfaceTexture surface, int width, int height) {
        mContext = context;
        mCamera = Camera.open();
        mCurrentCameraFacing = 0;
        startPreview(surface, width, height);
    }

    /**
     * 使用TextureView预览Camera
     *
     * @param surface
     */
    public void startPreview(SurfaceTexture surface, int width, int height) {
        if (isPreviewing) {
            mCamera.stopPreview();
            return;
        }
        if (mCamera != null) {
            try {
                mCamera.setPreviewTexture(surface);
            } catch (IOException e) {
                e.printStackTrace();
            }
            initCamera(width, height);
        }
    }

    /**
     * 停止预览,释放Camera
     */
    public void stopCamera() {
        if (mCamera != null) {
            mCamera.setPreviewCallback(null);
            mCamera.stopPreview();
            isPreviewing = false;
            mCamera.release();
            mCamera = null;
            mContext = null;
        }
    }

   private void initCamera(int width, int height) {
        if (mCamera != null) {
            Camera.Parameters params = mCamera.getParameters();
            params.setPictureFormat(PixelFormat.JPEG);//设置拍照后存储的图片格式
            //设置PreviewSize和PictureSize
            Size pictureSize = getOptimalSize(params.getSupportedPictureSizes(), width, height);
            params.setPictureSize(pictureSize.width, pictureSize.height);
            Size previewSize = getOptimalSize(params.getSupportedPreviewSizes(), width, height);
            params.setPreviewSize(previewSize.width, previewSize.height);

            mCamera.setDisplayOrientation(90);

            List<String> focusModes = params.getSupportedFocusModes();
            if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
                params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
            }
            mCamera.setParameters(params);
            mCamera.startPreview();//开启预览

            isPreviewing = true;
        }
    }

    /**
     * 拍照
     */
    public void takePicture(OnCameraActionCallback callback) {
        mOnCameraActionCallback = callback;
        if (isPreviewing && (mCamera != null)) {
            mCamera.takePicture(mShutterCallback, null, mJpegPictureCallback);
        }
    }

    /**
     * 保存照片
     */
    public String save() {
        if (mCameraData != null) {
            Bitmap bitmap = BitmapFactory.decodeByteArray(mCameraData, 0, mCameraData.length);//data是字节数据,将其解析成位图
            //保存图片到sdcard
            //设置FOCUS_MODE_CONTINUOUS_VIDEO)之后,myParam.set("rotation", 90)失效。
            //图片竟然不能旋转了,故这里要旋转下
            bitmap = BitmapUtils.rotateBitmap(bitmap, 90.0f);
            return BitmapUtils.saveBitmap(mContext, bitmap);
        }
        return null;
    }

    public void cancel() {
        //再次进入预览
        mCamera.startPreview();
        isPreviewing = true;
    }

    public interface OnCameraActionCallback {
        void onTakePictureComplete(Bitmap bitmap);
    }

    //快门按下的回调,在这里我们可以设置类似播放“咔嚓”声之类的操作。默认的就是咔嚓。
    private Camera.ShutterCallback mShutterCallback = new Camera.ShutterCallback() {

        public void onShutter() {
            // TODO Auto-generated method stub
            if (mToneGenerator == null) {
                //发出提示用户的声音
                mToneGenerator = new ToneGenerator(AudioManager.STREAM_MUSIC,
                        ToneGenerator.MAX_VOLUME);
            }
            mToneGenerator.startTone(ToneGenerator.TONE_PROP_BEEP2);
        }
    };

    //对jpeg图像数据的回调,最重要的一个回调
    private Camera.PictureCallback mJpegPictureCallback = new Camera.PictureCallback() {

        public void onPictureTaken(byte[] data, Camera camera) {
            //mCameraData = data;
            if (mOnCameraActionCallback != null) {
                Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);//data是字节数据,将其解析成位图
                if (mCurrentCameraFacing == Camera.CameraInfo.CAMERA_FACING_BACK) {
                    if (mDeviceOrientation == TOP) {
                        bitmap = BitmapUtils.rotateBitmap(bitmap, 90.0f);
                    } else if (mDeviceOrientation == RIGHT) {
                        bitmap = BitmapUtils.rotateBitmap(bitmap, 180.0f);
                    } else if (mDeviceOrientation == BOTTOM) {
                        bitmap = BitmapUtils.rotateBitmap(bitmap, 270.0f);
                    }
                } else if (mCurrentCameraFacing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                    if (mDeviceOrientation == TOP) {
                        bitmap = BitmapUtils.rotateBitmap(bitmap, 270.0f);
                    } else if (mDeviceOrientation == RIGHT) {
                        bitmap = BitmapUtils.rotateBitmap(bitmap, 180.0f);
                    } else if (mDeviceOrientation == BOTTOM) {
                        bitmap = BitmapUtils.rotateBitmap(bitmap, 90.0f);
                    }
                }
                mOnCameraActionCallback.onTakePictureComplete(bitmap);
            }
        }
    };
}

2.3 创建TakePictureActivity实现拍照页面

public class TakePictureActivity extends AppCompatActivity implements View.OnClickListener,
        CameraManager.OnCameraActionCallback {
    private CameraTextureView mTextureView;
    private ImageView mPicture;
    private View mTakePicture;
    private View mCameraSave;

    private Bitmap mPictureBitmap;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_take_picture);

        initView();
    }

    private void initView() {
        mTextureView = findViewById(R.id.texture_view);
        mPicture = findViewById(R.id.iv_camera_picture);
        mTakePicture = findViewById(R.id.iv_take_picture);
        mCameraSave = findViewById(R.id.iv_camera_save);

        mTakePicture.setOnClickListener(this);
        mCameraSave.setOnClickListener(this);

        findViewById(R.id.iv_camera_cancel).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.iv_take_picture:
                CameraManager.getInstance().takePicture(this);
                break;
            case R.id.iv_camera_cancel:
                CameraManager.getInstance().cancel();
                setResultState(false);
                break;
            case R.id.iv_camera_save:
                //todo 此处可以实现保存图片到相册功能
                break;
            default:
                break;
        }
    }

    private void setResultState(boolean state) {
        mTextureView.setTouchEnable(!state);
        if (state) {
            mTakePicture.setVisibility(View.GONE);
            ((View) mCameraSave.getParent()).setVisibility(View.VISIBLE);
            mTextureView.setVisibility(View.GONE);
            mPicture.setVisibility(View.VISIBLE);
        } else {
            mTakePicture.setVisibility(View.VISIBLE);
            ((View) mCameraSave.getParent()).setVisibility(View.GONE);
            mTextureView.setVisibility(View.VISIBLE);
            mPicture.setVisibility(View.GONE);
        }
    }

    @Override
    public void onTakePictureComplete(Bitmap bitmap) {
        if (mPictureBitmap != null) {
            mPictureBitmap.recycle();
        }
        mPictureBitmap = bitmap;
        mPicture.setImageBitmap(bitmap);
        setResultState(true);
    }
}

布局文件如下:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/black">

    <com.owner.camera.camera.CameraTextureView
        android:id="@+id/texture_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <ImageView
        android:id="@+id/iv_camera_picture"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:layout_marginBottom="48dp"
        android:layout_marginLeft="36dp"
        android:layout_marginRight="36dp"
        android:visibility="visible">

        <ImageView
            android:id="@+id/iv_camera_cancel"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:padding="12dp"
            android:src="@android:drawable/ic_menu_close_clear_cancel" />

        <ImageView
            android:id="@+id/iv_camera_save"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="right|center_vertical"
            android:padding="12dp"
            android:src="@android:drawable/ic_menu_save" />
    </FrameLayout>

    <ImageView
        android:id="@+id/iv_take_picture"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal|bottom"
        android:layout_marginBottom="32dp"
        android:layout_marginLeft="36dp"
        android:layout_marginRight="36dp"
        android:src="@android:drawable/ic_menu_camera" />
</FrameLayout>

2.4 AndroidManifest.xml中添加相机权限

<!-- 多媒体相关 -->
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera" />
    <uses-feature android:name="android.hardware.camera.autofocus" />

TakePictureActivity默认开启了硬件加速,这也是TextureView生效的前提条件

android:hardwareAccelerated="true"

至此基于TextureView的相机拍照预览功能已基本实现,可根据此Demo定制自己的相机,添加更丰富的功能。

3. 实现过程中遇到的坑

3.1 在非全屏预览页面中出现画面挤压变形

原因是UI中CameraTextureView的宽高比与相机提供的宽高比不一致,所以在设置控件宽高时一定要按照标准比例设计,Camera系统提供了多种预览尺寸(使用getSupportedPreviewSizes()获得),根据需要选择其一即可,你也可以在运行时根据选取的预览尺寸动态设置控件宽高

3.2 拍照后得到的图片尺寸与预览时不一致

原因与3.1相似,是因为PictureSize与PreviewSize不一致导致的,设置成一样的比例即可

总结:TextureView 与 PreViewSize 以及 PictureSize 这三者的宽高比应尽量相同,以免造成视觉上的不一致。前两者决定预览时我们所看见的效果,而 PictureSize 只最终决定生成的位图文件的大小。

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 Android 中,可以通过设置 Camera.Parameters 中的 setPreviewSize 和 setPreviewFormat 方法来实现相机预览的翻转。 首先,需要获取当前设备支持的预览尺寸和格式。可以使用 Camera.Parameters 中的 getSupportedPreviewSizes 和 getSupportedPreviewFormats 方法来获取支持的预览尺寸和格式列表。 然后,可以通过 setPreviewSize 和 setPreviewFormat 方法来设置预览尺寸和格式。在设置预览尺寸时,需要根据设备的旋转角度进行调整,以保证预览画面正常显示。可以使用 CameraInfo 中的 orientation 字段来获取设备的旋转角度。 最后,可以使用 SurfaceViewTextureView显示相机预览画面,并使用 Camera.setPreviewDisplay 方法将预览画面与相机绑定起来。 下面是一个简单的示例代码,可以实现相机预览翻转: ``` public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback { private Camera mCamera; private int mRotation; public CameraPreview(Context context) { super(context); getHolder().addCallback(this); } public void surfaceCreated(SurfaceHolder holder) { try { mCamera = Camera.open(); mCamera.setPreviewDisplay(holder); // 获取支持的预览尺寸和格式 Camera.Parameters params = mCamera.getParameters(); List<Camera.Size> sizes = params.getSupportedPreviewSizes(); int format = params.getPreviewFormat(); // 根据设备旋转角度调整预览尺寸 Camera.CameraInfo info = new Camera.CameraInfo(); Camera.getCameraInfo(0, info); int rotation = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation(); int degrees = 0; switch (rotation) { case Surface.ROTATION_0: degrees = 0; break; case Surface.ROTATION_90: degrees = 90; break; case Surface.ROTATION_180: degrees = 180; break; case Surface.ROTATION_270: degrees = 270; break; } if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { mRotation = (info.orientation + degrees) % 360; mRotation = (360 - mRotation) % 360; } else { mRotation = (info.orientation - degrees + 360) % 360; } for (Camera.Size size : sizes) { if (size.width * 3 == size.height * 4) { params.setPreviewSize(size.width, size.height); break; } } // 设置预览格式和旋转角度 params.setPreviewFormat(format); mCamera.setDisplayOrientation(mRotation); mCamera.setParameters(params); // 开始预览 mCamera.startPreview(); } catch (IOException e) { e.printStackTrace(); } } public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // 暂时不需要处理 } public void surfaceDestroyed(SurfaceHolder holder) { mCamera.stopPreview(); mCamera.release(); mCamera = null; } } ``` 这个示例代码可以实现相机预览时根据设备的旋转角度进行调整,保证预览画面正常显示。如果需要实现更复杂的功能,比如相机拍照、录制视频等,可以参考 Android 官方文档中的 Camera 相关章节。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值