TextureView实现相机预览拍照功能
- 1. TextureView简介
- 2. 代码实例
-
-
- 2.1 自定义CameraTextureView控件,实现SurfaceTextureListener,封装TextureView的初始化和销毁功能
- 2.2 Camera的处理封装到CameraManager类中
- 2.3 创建TakePictureActivity实现拍照页面
- 2.4 AndroidManifest.xml中添加相机权限
-
- 3. 实现过程中遇到的坑
-
-
- 3.1 在非全屏预览页面中出现画面挤压变形
- 3.2 拍照后得到的图片尺寸与预览时不一致
-
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 只最终决定生成的位图文件的大小。