安卓camera2仿扫一扫,自定义预览拍照界面

  • Post author:
  • Post category:其他



目录


一:布局展示,显示透明相机预览和非透明遮盖区域


二:预览布局左移


三:最终拍照得到的bitmap裁剪


四:项目下载地址,只有1积分


先上一波我的需求图:

https://img-blog.csdnimg.cn/20200731152414933.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3MzIxMDk4,size_16,color_FFFFFF,t_70

而且,拍照完,还要截取对应的位置去展示,如下:

https://img-blog.csdnimg.cn/20200731153247315.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3MzIxMDk4,size_16,color_FFFFFF,t_70

为了方便,3个界面被我整合到一个activity了,根据传入type不同,展示不同的相机预览布局,编写代码中遇到最大的问题就是:相机预览拉伸至全屏,肯定会拉伸(可以看看自带的手机相机,基本上为了保证清晰度,底部都是黑色区域做填充),因此,如何不拉伸是一个问题。

还有就是,处理完拉伸后(扩大相机的预览,将部分视图渲染至屏幕外),这会导致拍出的图片肯定会比手机宽度还宽。基本如下所示:

https://img-blog.csdnimg.cn/20200731154845948.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3MzIxMDk4,size_16,color_FFFFFF,t_70

然后,我就将拿到的camera2的预览宽度后,减去屏幕宽度,将剩余宽度的一半作为textureview向左偏移的宽度,如下:

https://img-blog.csdnimg.cn/20200731155459659.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3MzIxMDk4,size_16,color_FFFFFF,t_70

看上图,大家应该都知道最终图片截取的X坐标=(textureview宽度-屏幕宽度)/2,但是在有的机型拍照出来的bitmap宽度比预览宽度还大,最终修改为截取的X坐标=(bitmap宽度-屏幕宽度)/2,目前暂时使用这个方案,如大家遇到问题,帮忙指出一下,谢谢。

一:布局展示,显示透明相机预览和非透明遮盖区域

https://img-blog.csdnimg.cn/20200731160851843.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3MzIxMDk4,size_16,color_FFFFFF,t_70

看下自定义控件CustomLayout:因为一个activity,展示多种拍照预览布局,所以CustomLayout中判断哪个布局显示,将对应外部区域都显示为黑色遮盖,在这里可以看到:

 @SuppressLint("NewApi")
    private void resetBackgroundHoleArea() {
        Path path = null;
        // 以子View为范围构造需要透明显示的区域
        View view = null;
        //正面身份证
        if (findViewById(R.id.layout_sfz_up).getVisibility() == VISIBLE) {
            view = findViewById(R.id.layout_sfz_up);
        }
        //反面身份证
        else if (findViewById(R.id.layout_sfz_down).getVisibility() == VISIBLE) {
            view = findViewById(R.id.layout_sfz_down);
        }
        //营业执照
        else if (findViewById(R.id.layout_yyzz).getVisibility() == VISIBLE) {
            view = findViewById(R.id.layout_yyzz);
        }
        //银行卡
        else if (findViewById(R.id.layout_bankcar).getVisibility() == VISIBLE) {
            view = findViewById(R.id.layout_bankcar);
        }
        if (view != null) {
            path = new Path();
            // 矩形透明区域
            path.addRoundRect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom(), dp2Px(mContext, 10), dp2Px(mContext, 10), Path.Direction.CW);
        }
        if (path != null) {
            background.setSrcPath(path);
        }
    }

主要看到background.setSrcPath(path)这句话:真正的绘制黑色遮盖逻辑在里面:

public class CustomDrawable extends Drawable {

    private Paint srcPaint;
    private Path srcPath = new Path();
    private Drawable innerDrawable;

    public CustomDrawable(Drawable innerDrawable) {
        this.innerDrawable = innerDrawable;
        srcPath.addRect(100, 100, 200, 200, Path.Direction.CW);
        srcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        srcPaint.setColor(0xffffffff);
    }

    //设置内部透明的部分
    public void setSrcPath(Path srcPath) {
        this.srcPath = srcPath;
    }

    @Override
    public void draw(@NonNull Canvas canvas) {
        innerDrawable.setBounds(getBounds());
        if (srcPath == null || srcPath.isEmpty()) {
            innerDrawable.draw(canvas);
        } else {
            //将绘制操作保存到新的图层,因为图像合成是很昂贵的操作,将用到硬件加速,这里将图像合成的处理放到离屏缓存中进行
            int saveCount = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), srcPaint, Canvas.ALL_SAVE_FLAG);
            //dst 绘制目标图
            innerDrawable.draw(canvas);
            //设置混合模式
            srcPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
            //src 绘制源图
            canvas.drawPath(srcPath, srcPaint);
            //清除混合模式
            srcPaint.setXfermode(null);
            //还原画布
            canvas.restoreToCount(saveCount);
        }
    }

    @Override
    public void setAlpha(int alpha) {
        innerDrawable.setAlpha(alpha);
    }

    @Override
    public void setColorFilter(@Nullable ColorFilter colorFilter) {
        innerDrawable.setColorFilter(colorFilter);
    }

    @Override
    public int getOpacity() {
        return innerDrawable.getOpacity();
    }
}

不熟悉绘制模式PorterDuff.Mode.CLEAR,可以学习一下。

二:预览布局左移

自定义texttureview里面主要看一下代码:

 //外部传入camera2的预览大小后,hasSetAspectRatio表示已经拿到此值
 public void setAspectRatio(int width, int height) {
        if (width < 0 || height < 0) {
            throw new IllegalArgumentException("Size cannot be negative.");
        }
        hasSetAspectRatio = true;
        mRatioWidth = width;
        mRatioHeight = height;
        requestLayout();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        Log.e("scaleBitmap: ", "---------------------onmesure触发");
        //初次测量mRatioWidth和mRatioHeight有为0的情况,且最终layout的参数以实际camera预览参数为主
        //向左偏移
        if (hasSetAspectRatio && 0 != mRatioWidth && 0 != mRatioHeight) {
            widthTrue = mRatioWidth;
            heightTrue = mRatioHeight;
            setMeasuredDimension(mRatioWidth, mRatioHeight);
            ViewGroup.LayoutParams vp = this.getLayoutParams();
            if (vp instanceof ViewGroup.MarginLayoutParams) {
                Log.e("scaleBitmap: ", "----------------左移" + ((mRatioWidth - screenW) / 2));
                ((ViewGroup.MarginLayoutParams) vp).leftMargin = -(mRatioWidth - screenW) / 2;
                requestLayout();
            }
            return;
        }
        if (0 == mRatioWidth || 0 == mRatioHeight) {
            widthTrue = width;
            heightTrue = height;
            setMeasuredDimension(width, height);
        } else {
            if (width > height * mRatioWidth / mRatioHeight) {    
                widthTrue = width;
                heightTrue = width * mRatioHeight / mRatioWidth;
                setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
            } else {
                widthTrue = height * mRatioWidth / mRatioHeight;
                heightTrue = height;
                setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
            }
        }
    }

三:最终拍照得到的bitmap裁剪

Bitmap.createBitmap(bitmap,
                (bitmap.getWidth() - screenWidth) / 2,//以bitmap的实际宽度去算偏移值
                martop,//顶部偏移一点截取,免得高度区域过大
                screenWidth,//screenWidth+ outScreenWidth
                newHeight,
                null, false);

四:项目下载地址,只有1积分


https://download.csdn.net/download/qq_37321098/12676395



版权声明:本文为qq_37321098原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。