Android 裁剪摄像头预览窗口-SurfaceView

简介: Android 裁剪摄像头预览窗口-SurfaceView

概述


Android 下, 使用SurfaceView显示摄像头预览, 通常使用的是一个矩形窗口, 如果, 要使用一个圆形窗口呢?

先上效果图

未打开摄像头

image.png

打开摄像头

image.png


实现过程


视图布局

image.png

实现圆形裁剪的方法有很多, 最简单的, 可以在SurfaceView上方增加一个视图遮罩, 挡住不需要显示的区域即可

遮罩的方法有一个问题:

image.png

上图中, 属于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;
    }
  });


当写完代码开始在设备上运行调试时, 出现了一个奇怪的问题:

当系统显示导航栏时, 可以正常显示裁剪后的圆形, 在全屏或隐藏导航栏后, 裁剪失效了:

image.png

要解决这个问题也简单, 只需要在**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


相关文章
|
编解码 开发工具 Android开发
Android平台RTSP轻量级服务|RTMP推送摄像头或屏幕之音频接口设计
好多开发者在做Android平台录像或者RTSP轻量级服务、RTMP推送相关模块时,对需要设计哪些常用接口会心存疑惑,本文主要以大牛直播SDK(官方)为例,简单介绍下Android平台直播推送SDK所有音频相关的接口,感兴趣的开发者可以看看。
|
前端开发 开发工具 Android开发
Android播放器之SurfaceView与GLSurfaceView
Surface的官方介绍:Handle onto a raw buffer that is being managed by the screen compositor,Surface是一个raw buffer的句柄,通过它在raw buffer上进行绘制,可以通过Surface获得一个Canvas。
273 0
|
3月前
|
Android开发
Android Stadio Build 窗口字符串乱码问题
在使用Android Studio过程中,如果遇到Build窗口字符串乱码问题,可以通过编辑`studio.vmoptions`文件添加`-Dfile.encoding=UTF-8`配置并重启Android Studio来解决。
169 1
Android Stadio Build 窗口字符串乱码问题
|
2月前
|
安全 Android开发 Kotlin
Android经典实战之SurfaceView原理和实践
本文介绍了 `SurfaceView` 这一强大的 UI 组件,尤其适合高性能绘制任务,如视频播放和游戏。文章详细讲解了 `SurfaceView` 的原理、与 `Surface` 类的关系及其实现示例,并强调了使用时需注意的线程安全、生命周期管理和性能优化等问题。
152 8
|
2月前
|
Android开发
Android中SurfaceView的双缓冲机制和普通View叠加问题解决办法
本文介绍了 Android 平台上的 SurfaceView,这是一种高效的图形渲染控件,尤其适用于视频播放、游戏和图形动画等场景。文章详细解释了其双缓冲机制,该机制通过前后缓冲区交换来减少图像闪烁,提升视觉体验。然而,SurfaceView 与普通 View 叠加时可能存在 Z-Order 不一致、同步问题及混合渲染难题。文中提供了使用 TextureView、调整 Z-Order 和创建自定义组合控件等多种解决方案。
124 9
|
2月前
|
API Android开发 数据安全/隐私保护
Android经典实战之窗口和WindowManager
本文介绍了Android开发中“窗口”的基本概念及其重要性。窗口是承载用户界面的基础单位,而`WindowManager`系统服务则负责窗口的创建、更新和移除等操作。了解这些概念有助于开发复杂且用户体验良好的应用。
55 2
|
3月前
|
编解码 网络协议 前端开发
如何实现Android平台GB28181设备接入模块按需打开摄像头并回传数据
后台采集摄像头,如果想再进一步扩展,可以把android平台gb28181的camera2 demo,都移植过来,实现功能更强大的国标设备侧,这里主要是展示,收到国标平台侧的回传请求后,才打开摄像头,才开始编码打包,最大限度的减少资源的占用
|
3月前
|
编解码 网络协议 Android开发
Android平台GB28181设备接入模块实现后台service按需回传摄像头数据到国标平台侧
我们在做Android平台GB28181设备对接模块的时候,遇到这样的技术需求,开发者希望能以后台服务的形式运行程序,国标平台侧没有视频回传请求的时候,仅保持信令链接,有发起视频回传请求或语音广播时,打开摄像头,并实时回传音视频数据或接收处理国标平台侧发过来的语音广播数据。
|
4月前
|
Android开发 开发者
Android经典面试题之SurfaceView和TextureView有什么区别?
分享了`SurfaceView`和`TextureView`在Android中的角色。`SurfaceView`适于视频/游戏,独立窗口低延迟,但变换受限;`TextureView`支持复杂变换,视图层级中渲染,适合动画/视频特效,但性能略低。两者在性能、变换、使用和层级上有差异,开发者需按需选择。
71 1
|
5月前
|
Android开发 开发者
Android UI设计中,Theme定义了Activity的视觉风格,包括颜色、字体、窗口样式等,定义在`styles.xml`。
【6月更文挑战第26天】Android UI设计中,Theme定义了Activity的视觉风格,包括颜色、字体、窗口样式等,定义在`styles.xml`。要更改主题,首先在该文件中创建新主题,如`MyAppTheme`,覆盖所需属性。然后,在`AndroidManifest.xml`中应用主题至应用或特定Activity。运行时切换主题可通过重新设置并重启Activity实现,或使用`setTheme`和`recreate()`方法。这允许开发者定制界面并与品牌指南匹配,或提供多主题选项。
81 6