SurfaceView继承自View,主要用来展示视频流的绘制,典型的应用场景是相机,视频播放器,游戏界面绘制等。它独立于UI线程进行绘制,所以不会阻塞UI线程。本文将结合一个简单的相机demo介绍SurfaceView的使用。
我们实现的相机功能很简单,可以进行相机预览,点击拍照按钮拍照,并展示拍摄的照片,点击确定返回相机预览界面。
布局如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
...
>
<SurfaceView
android:id="@+id/preview_surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<ImageView
android:id="@+id/image_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"/>
<Button
android:id="@+id/take_photo_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:text="拍照"/>
<Button
android:id="@+id/confirm_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:text="确定"/>
</RelativeLayout>
SurfaceView的初始化如下,首先根据SurfaceView获取SurfaceHolder,SurfaceHolder是一个接口,提供了控制SurfaceView界面的函数,比如控制界面的尺寸、格式,编辑界面像素,监控界面的变化等。然后给SurfaceHolder添加CallBack回调函数,三个回调函数对应了界面的三个状态,创建,修改和销毁。在这里我们在界面创界后开始进行相机预览。
public class MainActivity extends AppCompatActivity {
...
private Camera camera;
private SurfaceHolder surfaceHolder;
private byte[] pictureDataBytes;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
surfaceHolder = surfaceView.getHolder();
surfaceHolder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
startPreview();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
});
}
相机的初始化在onResume里进行,销毁在onPause里进行:
@Override
protected void onResume() {
super.onResume();
initCamera();
}
@Override
protected void onPause() {
super.onPause();
releaseCamera();
}
private void initCamera() {
if (camera == null) {
camera = Camera.open();
}
}
private void releaseCamera() {
if (camera != null) {
camera.release();
camera = null;
}
}
启动相机预览的函数实现如下,通过setPreviewDisplay指定显示预览的view:
private void startPreview() {
if (camera != null) {
try {
camera.setPreviewDisplay(surfaceHolder);
if (isCapturing) {
camera.startPreview();
}
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(this, "Unable to open camera.", Toast.LENGTH_SHORT) .show();
}
}
}
点击拍照按钮,进行拍照并展示。这里为了防止相机拍摄的照片太大导致OOM错误,要对图片进行压缩,压缩包括两个方式,尺寸压缩和像素格式压缩,可以参考博客从Oppo手机拍照无法展示谈图片压缩:
@OnClick(R.id.take_photo_btn)
protected void onTakePhotoClicked(View v) {
camera.takePicture(null, null, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
pictureDataBytes = data;
showPicture();
}
});
}
private void showPicture() {
Bitmap bitmap = BitmapFactory.decodeByteArray(pictureDataBytes, 0, pictureDataBytes.length);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = calculateSampleSize(bitmap.getHeight(), bitmap.getWidth());
options.inPreferredConfig = Bitmap.Config.RGB_565;
InputStream inputStream = bitmapToStream(bitmap);
imageView.setImageBitmap(BitmapFactory.decodeStream(inputStream, null, options));
...
}
计算采样比例采用下面的函数:
// 获取采样比例
public int calculateSampleSize(int outWidth, int outHeight) {
int scale = 1;
if (outHeight > MAX_HEIGHT || outWidth > MAX_WIDTH) {
int maxSize = MAX_WIDTH > MAX_HEIGHT ? MAX_WIDTH : MAX_HEIGHT;
scale = (int) Math.pow(2, (int) Math.round(Math.log(maxSize /(double) Math.max(outHeight, outWidth)) / Math.log(0.5)));
}
return scale;
}
最后记得在Manifest里添加相机权限:
<uses-permission android:name="android.permission.CAMERA"/>
参考:
https://developer.android.com/reference/android/view/SurfaceView.html
http://archive.oreilly.com/oreillyschool/courses/android2/CameraAdvanced.html
网友评论