绘制圆形surfaceview,解决预览框的畸变问题



绘制圆形的SurfaceView

  1. 首先介绍一下什么是SurfaceView

Surface意为表层、表面,顾名思义SurfaceView就是指一个在表层的View对象。为什么说是在表层呢,这是因为它有点特殊跟其他View不一样,其他View是绘制在“表层”的上面,而它就是充当“表层”本身。创建SurfaceView的时候需要实现SurfaceHolder.Callback接口,它可以用来监听SurfaceView的状态,比如:SurfaceView的改变 、SurfaceView的创建 、SurfaceView 销毁等,我们可以在相应的方法中做一些比如初始化的操作或者清空的操作等等。

Android系统提供了View进行绘图处理,我们通过自定义的View可以满足大部分的绘图需求,但是这有个问题就是我们通常自定义的View是用于主动更新情况的,用户无法控制其绘制的速度,由于View是通过invalidate方法通知系统去调用view.onDraw方法进行重绘,而Android系统是通过发出VSYNC信号来进行屏幕的重绘,刷新的时间是16ms,如果在16ms内View完成不了执行的操作,用户就会看着卡顿,比如当draw方法里执行的逻辑过多,需要频繁刷新的界面上,例如游戏界面,那么就会不断的阻塞主线程,从而导致画面卡顿。而SurfaceView相当于是另一个绘图线程,它是不会阻碍主线程,并且它在底层实现机制中实现了双缓冲机制。

SurfaceView最常见的应用场景就是在摄像头的预览,手机录像和拍照的时候屏幕上的显示,就是在surfaceView进行显示的


这里写图片描述
下面的代码是他最常见的生命周期, 当SurfaceView的界面不可见时,就会调用surfaceDestroyed()函数来杀死本进程,当重新进入本界面时,需要重新加载。

@Override
    public void surfaceCreated(SurfaceHolder holder) {
      
    }
   @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
 
    }
 
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
   
    }

2.自己定义MySurfaceView类,继承自SurfaceView类

先看一下效果图


这里写图片描述
我的理解是,可以把它看成是一个红色的图层覆盖了原来的SurfaceView,即我们只能看到圆框中的内容

package com.example.heartratedect;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Region;
import android.hardware.Camera;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceView;

/**
 * 圆形SurfaceView
 * 这个SurfaceView 使用时 必须设置其background,可以设置全透明背景
 */
public class MySurfaceView extends SurfaceView {

    private Paint paint;
    private int widthSize;
    private Camera camera;
    private int height; 



    public MySurfaceView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initView();
    }

    public MySurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public MySurfaceView(Context context) {
        super(context);
        initView();
    }


    private void initView() {
        this.setFocusable(true);
        this.setFocusableInTouchMode(true);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        widthSize = MeasureSpec.getSize(widthMeasureSpec);
        //获取屏幕长宽比例,这样设置不会发生畸变,千万不要根据一个手机设定一个数
        //那样换一个手机就可能会出现显示的比例问题
        int screenWidth = CommonUtils.getScreenWidth(getContext());
        int screenHeight = CommonUtils.getScreenHeight(getContext());
        height=600;
        //可以理解为红色的背景盖住了大部分的区域,我们只能看到圆框里面的,如果还是按照原来的比例绘制surfaceview
        //需要把手机拿的很远才可以显示出整张脸,故而我用了一个比较取巧的办法就是,按比例缩小,试验了很多数后,感觉0.55
        //是最合适的比例
        double screenWidth1= 0.55*screenWidth;
        double screenHeight1= 0.55*screenHeight;
        Log.e("onMeasure", "widthSize="+widthSize);
        Log.e("onMeasure", "draw: widthMeasureSpec = " +screenWidth + "  heightMeasureSpec = " + screenHeight);
        //绘制的输入参数必须是整数型,做浮点型运算后为float型数据,故需要做取整操作
        setMeasuredDimension((int) screenWidth1, (int) screenHeight1);
        //setMeasuredDimension(widthSize, heightSize);

    }

    @Override
    //绘制一个圆形的框,并设置圆框的坐标和半径大小
    //这个绘制在16:9的手机上显示很好,但是在更长的手机上(大于16/9)会偏上,
    public void draw(Canvas canvas) {
        Log.e("onDraw", "draw: test");
        Path path = new Path();
        //path.addCircle(widthSize / 2, height / 2, height / 2, Path.Direction.CCW);
        path.addCircle(widthSize / 2, widthSize / 2, widthSize / 2, Path.Direction.CCW);
        canvas.clipPath(path, Region.Op.REPLACE);
        super.draw(canvas);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        Log.e("onDraw", "onDraw");
        super.onDraw(canvas);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {

        int screenWidth = CommonUtils.getScreenWidth(getContext());
        int screenHeight = CommonUtils.getScreenHeight(getContext());
        Log.d("screenWidth",Integer.toString(screenWidth));
        Log.d("screenHeight",Integer.toString(screenHeight));
        w = screenWidth;
        h = screenHeight;
        super.onSizeChanged(w, h, oldw, oldh);

    }
}
package com.example.heartratedect;

import android.content.Context;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;

public class CommonUtils {


  public static int getScreenWidth(Context context) {
        WindowManager windowManager = (WindowManager)        context.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics outMetrics = new DisplayMetrics();// 创建了一张白纸
        windowManager.getDefaultDisplay().getMetrics(outMetrics);// 给白纸设置宽高
        return outMetrics.widthPixels;
    }


    public static int getScreenHeight(Context context) {
        WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics outMetrics = new DisplayMetrics();// 创建了一张白纸
        windowManager.getDefaultDisplay().getMetrics(outMetrics);// 给白纸设置宽高
        return outMetrics.heightPixels;
    }

 
}

3.接下来是MySurfaceView的实例化

实例化的方法和SurfaceView一样

 private Camera camera;
 private MySurfaceView surfaceView;
 surfaceView = (MySurfaceView)CameraLayout. findViewById(R.id.camera_mysurfaceview);
 cameraSurfaceHolder = surfaceView.getHolder();
 cameraSurfaceHolder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                try {
                    initView();
                    camera.setPreviewDisplay(holder);
                    isPreview = true;
                    camera.startPreview();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                cameraSurfaceHolder = holder;
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
                cameraSurfaceHolder = holder;
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                releaseCamera();
            }
        });
        // This method was deprecated in API level 11. this is ignored, this value is set automatically when needed.   
        cameraSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        
private void initView(){
        // 初始化摄像头  ,设置为前置相机
        camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_FRONT);
        Camera.Parameters parameters = camera.getParameters();
        // 每秒30帧  
        parameters.setPreviewFrameRate(30);
        camera.setParameters(parameters);
        camera.setDisplayOrientation(90);
    }
     /**
     * 释放摄像头资源
     */
private void releaseCamera() {
        try {
            if (camera != null) {
                camera.setPreviewCallback(null);
                camera.stopPreview();
                camera.lock();
                camera.release();
                camera = null;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

上述代码并没有涉及到进度条的定义及绘制,所以不会存在显示不协调的问题。我的程序在调试的过程中对曲面屏非常不友好,归结原因应该是两侧的延展和屏幕获取的比例不一致,这个地方没有深入研究,等实验室买台曲面屏的手机,做好调试再来更新。

4.布局文件部分

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_camera"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/background_camera_activity">

     //注意包名应该导入自己的
    <com.example.MySurfaceView
        android:layout_marginTop="40dp"
        android:layout_marginLeft="-30dp"
        android:layout_centerHorizontal="true"
        android:id="@+id/camera_mysurfaceview"
        android:layout_width="200dp"
        android:layout_height="200dp"
       android:background="@drawable/circle"/>
</RelativeLayout>

同时需要给他加一个背景的圆框才行,即上面代码中background的设置,新建一个xml文件命名为circle,写下下面代码并放置在drawable文件夹下

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

    <solid android:color="#00000000" />

    <corners android:radius="90dp" />

    <stroke
        android:width="4dp"
        android:color="#fff" />

</shape>

这样就大工告成了,本人也是刚入门的小菜,如有啥问题,欢迎沟通,后面有啥进展我会再继续更新。其中有些取巧的的行为:比如缩小surfaceview的比例使之正好和圆形框的直径差不多大小,但是使用体验上还是有些不舒服,后面还是要继续改进

本Demo的GitHub地址:

https://github.com/qunqunstyle/multithreading

  • 20
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 20
    评论
SurfaceView绘制预览画面时进行镜像翻转,需要对绘制的 Bitmap 进行处理。可以使用 Matrix 类的 setScale() 方法进行缩放,再使用 postTranslate() 方法进行平移,最后将处理后的 Bitmap 绘制SurfaceView 上即可实现镜像翻转。 具体实现步骤如下: 1. 获取相机预览数据,转换成 Bitmap 对象; 2. 创建一个 Matrix 对象,使用 setScale() 方法设置水平缩放比例为 -1,垂直缩放比例为 1,也就是进行水平镜像翻转; 3. 使用 postTranslate() 方法对 Bitmap 进行平移,将其向左平移一个图片宽度的距离,这样可以使得镜像翻转后的图片显示在 SurfaceView 的左侧; 4. 将处理后的 Bitmap 绘制SurfaceView 上。 以下是代码示例: ```java public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback { private SurfaceHolder mHolder; private Camera mCamera; public CameraPreview(Context context, Camera camera) { super(context); mCamera = camera; mHolder = getHolder(); mHolder.addCallback(this); } @Override public void surfaceCreated(SurfaceHolder holder) { try { mCamera.setPreviewDisplay(holder); mCamera.startPreview(); } catch (IOException e) { e.printStackTrace(); } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { if (mHolder.getSurface() == null) { return; } try { mCamera.stopPreview(); } catch (Exception e) { e.printStackTrace(); } Camera.Parameters parameters = mCamera.getParameters(); Camera.Size size = getBestPreviewSize(parameters, width, height); parameters.setPreviewSize(size.width, size.height); mCamera.setParameters(parameters); // 获取相机预览数据,转换成 Bitmap 对象 byte[] data = new byte[width * height * 3 / 2]; mCamera.addCallbackBuffer(data); mCamera.setPreviewCallbackWithBuffer(new Camera.PreviewCallback() { @Override public void onPreviewFrame(byte[] data, Camera camera) { Bitmap bitmap = YuvUtil.yuv2Bitmap(data, width, height); if (bitmap != null) { // 创建一个 Matrix 对象,使用 setScale() 方法设置水平缩放比例为 -1,垂直缩放比例为 1 Matrix matrix = new Matrix(); matrix.postScale(-1, 1); // 使用 postTranslate() 方法对 Bitmap 进行平移,将其向左平移一个图片宽度的距离 matrix.postTranslate(bitmap.getWidth(), 0); // 将处理后的 Bitmap 绘制SurfaceView 上 Canvas canvas = getHolder().lockCanvas(); canvas.drawBitmap(bitmap, matrix, null); getHolder().unlockCanvasAndPost(canvas); } mCamera.addCallbackBuffer(data); } }); mCamera.startPreview(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { mCamera.stopPreview(); mCamera.release(); } private Camera.Size getBestPreviewSize(Camera.Parameters parameters, int width, int height) { Camera.Size bestSize = null; List<Camera.Size> sizes = parameters.getSupportedPreviewSizes(); float aspectRatio = (float) width / height; float minDiff = Float.MAX_VALUE; for (Camera.Size size : sizes) { float sizeAspectRatio = (float) size.width / size.height; float diff = Math.abs(aspectRatio - sizeAspectRatio); if (diff < minDiff) { bestSize = size; minDiff = diff; } } return bestSize; } } ``` 需要注意的是,这种方式会导致预览画面左右倒置,如果需要将预览画面上下倒置,可以使用 setScale() 方法设置垂直缩放比例为 -1,水平缩放比例为 1,然后使用 postTranslate() 方法将 Bitmap 向上平移一个图片高度的距离。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值