目录
先上一波我的需求图:
而且,拍照完,还要截取对应的位置去展示,如下:
为了方便,3个界面被我整合到一个activity了,根据传入type不同,展示不同的相机预览布局,编写代码中遇到最大的问题就是:相机预览拉伸至全屏,肯定会拉伸(可以看看自带的手机相机,基本上为了保证清晰度,底部都是黑色区域做填充),因此,如何不拉伸是一个问题。
还有就是,处理完拉伸后(扩大相机的预览,将部分视图渲染至屏幕外),这会导致拍出的图片肯定会比手机宽度还宽。基本如下所示:
然后,我就将拿到的camera2的预览宽度后,减去屏幕宽度,将剩余宽度的一半作为textureview向左偏移的宽度,如下:
看上图,大家应该都知道最终图片截取的X坐标=(textureview宽度-屏幕宽度)/2,但是在有的机型拍照出来的bitmap宽度比预览宽度还大,最终修改为截取的X坐标=(bitmap宽度-屏幕宽度)/2,目前暂时使用这个方案,如大家遇到问题,帮忙指出一下,谢谢。
一:布局展示,显示透明相机预览和非透明遮盖区域
看下自定义控件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积分