概述
Android 下, 使用SurfaceView显示摄像头预览, 通常使用的是一个矩形窗口, 如果, 要使用一个圆形窗口呢?
先上效果图
未打开摄像头
打开摄像头
实现过程
视图布局
实现圆形裁剪的方法有很多, 最简单的, 可以在SurfaceView上方增加一个视图遮罩, 挡住不需要显示的区域即可
遮罩的方法有一个问题:
上图中, 属于ImageView的红色区域也会被遮挡
本文中采用的方法是:
用一个RelativeLayout包含SurfaceView, 通过裁剪RelativeLayout来实现:
@Override protected void dispatchDraw(Canvas canvas) { //裁剪圆型画布,使SurfaceView显示圆形区域图像 canvas.save(); canvas.clipPath(path); super.dispatchDraw(canvas); canvas.restore(); }
增加按键位置的拖动功能, 主要是为了解决验证一个问题:
//设置输入监听, 用于拖动按键位置. btn.setOnTouchListener(new View.OnTouchListener() { float cx, cy, dx, dy; int tx, ty; @Override public boolean onTouch(View v, MotionEvent event) { cx = event.getRawX(); cy = event.getRawY(); switch(event.getAction()){ case MotionEvent.ACTION_DOWN: dx = cx; dy = cy; tx = lpBtn.leftMargin; ty = lpBtn.topMargin; break; case MotionEvent.ACTION_MOVE: lpBtn.leftMargin = (int)(tx + (cx - dx)); lpBtn.topMargin = (int)(ty + (cy - dy)); btn.setLayoutParams(lpBtn); break; case MotionEvent.ACTION_UP: //开始预览 cameraHelper.openCamera(false); cameraHelper.startPreview(null); //必须设置为FALSE //sv.setZOrderOnTop(false); rlRoot.requestLayout(); break; } return false; } });
当写完代码开始在设备上运行调试时, 出现了一个奇怪的问题:
当系统显示导航栏时, 可以正常显示裁剪后的圆形, 在全屏或隐藏导航栏后, 裁剪失效了:
要解决这个问题也简单, 只需要在**XRelativeLayout(rl)**中的SurfaceView上, 覆盖一个控件, 源码中已注释
//MUST add view overlay rl.addView(new View(this));
当然也可以增加到rlRoot中, 从上面的动图可以看到, 裁剪的有效范围与Button的位置有关.
参考代码
import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Path; import android.graphics.PixelFormat; import android.os.Bundle; import android.os.SystemClock; import android.view.MotionEvent; import android.view.SurfaceView; import android.view.View; import android.widget.Button; import android.widget.ImageView; import android.widget.RelativeLayout; import android.app.Activity; public class CameraPreview extends Activity { int cameraId = 1; //根控件 RelativeLayout rlRoot; CameraHelper cameraHelper; SurfaceView sv; RelativeLayoutX rl; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); rlRoot = new RelativeLayout(this); //显示于SurfaceView 下方的ImageView ImageView iv = new ImageView(this); RelativeLayout.LayoutParams lpIv = new RelativeLayout.LayoutParams(UiTools.WRAP_CONTENT, UiTools.WRAP_CONTENT); iv.setScaleType(ImageView.ScaleType.FIT_XY); iv.setImageResource(R.drawable.ic_drawer_product_imgs); iv.setOnTouchListener(new View.OnTouchListener() { float cx, cy, dx, dy; int tx, ty; @Override public boolean onTouch(View v, MotionEvent event) { cx = event.getRawX(); cy = event.getRawY(); switch(event.getAction()){ case MotionEvent.ACTION_DOWN: dx = cx; dy = cy; tx = lpIv.leftMargin; ty = lpIv.topMargin; break; case MotionEvent.ACTION_MOVE: //同样有效 //btn.setTranslationX(tx + (cx - dx)); //btn.setTranslationY(ty + (cy - dy)); lpIv.leftMargin = (int)(tx + (cx - dx)); lpIv.topMargin = (int)(ty + (cy - dy)); iv.setLayoutParams(lpIv); break; } return true; } }); rlRoot.addView(iv, lpIv); //SurfaceView rl = new RelativeLayoutX(this); sv = new SurfaceView(this); //当未打开摄像头开始预览时, 显示透明(不设置则显示黑色) //sv.setZOrderOnTop(true); //sv.getHolder().setFormat(PixelFormat.TRANSLUCENT); rl.addView(sv, new RelativeLayout.LayoutParams(UiTools.MATCH_PARENT, UiTools.MATCH_PARENT)); cameraHelper = new CameraHelper(camBase.getParameters(cameraId), sv, camBase); rlRoot.addView(rl); //MUST add view overlay //rl.addView(new View(this)); RelativeLayout.LayoutParams lpBtn = new RelativeLayout.LayoutParams(UiTools.WRAP_CONTENT, UiTools.WRAP_CONTENT); Button btn = new Button(this); btn.setText("Show"); //设置输入监听, 用于拖动按键位置. btn.setOnTouchListener(new View.OnTouchListener() { float cx, cy, dx, dy; int tx, ty; @Override public boolean onTouch(View v, MotionEvent event) { cx = event.getRawX(); cy = event.getRawY(); switch(event.getAction()){ case MotionEvent.ACTION_DOWN: dx = cx; dy = cy; tx = lpBtn.leftMargin; ty = lpBtn.topMargin; break; case MotionEvent.ACTION_MOVE: lpBtn.leftMargin = (int)(tx + (cx - dx)); lpBtn.topMargin = (int)(ty + (cy - dy)); btn.setLayoutParams(lpBtn); break; case MotionEvent.ACTION_UP: //开始预览 cameraHelper.openCamera(false); cameraHelper.startPreview(null); //必须设置为FALSE //sv.setZOrderOnTop(false); rlRoot.requestLayout(); break; } return false; } }); rlRoot.addView(btn, lpBtn); setContentView(rlRoot); } @Override protected void onDestroy() { super.onDestroy(); cameraHelper.stopPreview(); } static class RelativeLayoutX extends RelativeLayout{ public RelativeLayoutX(Context context) { super(context); } Path path = new Path(); @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); path.reset(); path.addCircle(w/2f, h/2f, Math.min(w, h)/2f, Path.Direction.CW); } @Override protected void dispatchDraw(Canvas canvas) { //裁剪圆型画布,使SurfaceView显示圆形区域图像 canvas.save(); canvas.clipPath(path); super.dispatchDraw(canvas); canvas.restore(); } } //非关键代码, 主要用于返回打开摄像头预览的参数 CameraHelper.CameraBase camBase = new CameraHelper.CameraBaseSimple(){ @Override public CameraHelper.CameraParams getParameters(int camIndex) { return new CameraHelper.CameraParams(cameraId, 640, 480, 30, 0, 0); } @Override public boolean sameParams(CameraHelper.CameraParams cp) { return true; } @Override public void onPreviewStarted(int camIndex) {} @Override public void onCameraError(int error) {} }; }
PS
虽然提出了需求, 也实现了想要的效果, 却也留下了一个问题, 问题虽然解决, 但却找不到具体的原因!
1.浮动窗的情况未测试过
2.Dialog或其它弹出窗口未测试过
3.setZOrderOnTop(true)的情况未有好的解决办法
参考
解决SurfaceView渲染的各种疑难杂症
android 圆形相机预览拍照_Android相机(摄像头)圆形预览窗口,圆形SurfaceView