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 隻最終決定生成的位圖檔案的大小。