先来看看效果:
那接下来就会分别分享一下我做这个东东的时候,遇到的坑以及最终实现的方案。
圆形相机预览View
做这个View的时候,先是想着自己直接定义一个自定义的TexureView,然后重写onDraw方法,draw一个圆形border就好了。但是发现继承自TexureView以后,却没有了onDraw之类的方法,看来还是得再去研究下这块。在万分着急之时,看到了这篇博客:https://blog.csdn.net/weixin_43901866/article/details/99452491
可以发现,其实这里是将TexureView和Border分开了,TextureView主要用View轮廓的裁剪来实现圆形;而Border则通过draw的方法去添加。
这不是完美解决么,于是仿着这篇博客,写了一个RoundTextureView和一个CircleTexureBorderView。
先看看我是这么写的:
class RoundTextureView: TextureView {
private var mRadius = 0F
constructor(context: Context): this(context, null)
constructor(context: Context, attrs: AttributeSet?): super(context, attrs) {
val a = context.obtainStyledAttributes(attrs, R.styleable.RoundTextureView)
a.apply {
mRadius = if (hasValue(R.styleable.RoundTextureView_textureRadius)) {
// 默认为圆形
a.getFloat(R.styleable.RoundTextureView_textureRadius, measuredWidth.toFloat() / 2)
} else {
min(measuredWidth, measuredHeight).toFloat() / 2
}
}
a.recycle()
outlineProvider = object: ViewOutlineProvider() {
override fun getOutline(view: View?, outline: Outline?) {
val rect = if (measuredWidth <= measuredHeight) {
Rect(0, (measuredHeight - measuredWidth) / 2, measuredWidth, (measuredHeight + measuredWidth) / 2)
} else {
Rect((measuredWidth - measuredHeight) / 2, 0, measuredHeight, (measuredHeight + measuredWidth) / 2)
}
outline?.setOval(rect)
}
}
clipToOutline = true
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
mRadius = min(measuredWidth, measuredHeight).toFloat() / 2
}
fun turnRound() {
invalidateOutline()
}
}
预览一下:发现 没问题 很ok。
接下来就到这个Border了 Border的结构也比较简单 主要是拿到TextureView的宽/高 用来定义radius就可以了 而这个值是可以从外部传入的。
class CircleTextureBorderView : View {
private val mPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val mAnimatePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val mTextPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private var mTextureViewWidth: Int = measuredWidth
private var mColor = Color.CYAN
private var mTipsText: String = "请放入人脸"
private var mTextHeight = 0F
constructor(context: Context): this(context, null)
constructor(context: Context, attributeSet: AttributeSet?): super(context, attributeSet) {
mPaint.strokeWidth = ScreenUtils.dip2px(context, 3F).toFloat()
mPaint.style = Paint.Style.STROKE
mAnimatePaint.style = Paint.Style.FILL
mAnimatePaint.color = Color.parseColor("#7F000000")
mTextPaint.color = Color.WHITE
mTextPaint.style = Paint.Style.FILL
mTextPaint.textSize = ScreenUtils.dip2px(context, 12F).toFloat()
mTextPaint.strokeWidth = 1F
mTextHeight = mTextPaint.fontMetrics.descent - mTextPaint.fontMetrics.ascent
attributeSet?.apply {
val a = context.obtainStyledAttributes(this, R.styleable.CircleTextureBorderView)
// mTextureViewWidth = a.getDimensionPixelSize(R.styleable.CircleTextureBorderView_circleTextureWidth,
// measuredWidth)
mColor = a.getColor(R.styleable.CircleTextureBorderView_circleTextureBorderColor,
Color.CYAN)
a.recycle()
}
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
mPaint.color = mColor
val radius = mTextureViewWidth.toFloat() / 2
canvas?.drawCircle(measuredWidth.toFloat() / 2, measuredWidth.toFloat() / 2, radius, mPaint)
// 外边框比内部大10-12dp 边框的厚度为1
mPaint.strokeWidth = 1F
canvas?.drawCircle(measuredWidth.toFloat() / 2, measuredWidth.toFloat() / 2,
radius + ScreenUtils.dip2px_20(context), mPaint)
mPaint.strokeWidth = ScreenUtils.dip2px(context, 3F).toFloat()
val left = (measuredWidth - mTextureViewWidth.toFloat()) / 2
val right = (measuredWidth + mTextureViewWidth.toFloat()) / 2
val top = (measuredHeight - mTextureViewWidth.toFloat()) / 2
val bottom = (measuredHeight + mTextureViewWidth.toFloat()) / 2
// x: (measuredWidth.toFloat() - mTextPaint.measureText(mTipsText)) / 2
// y: (measuredHeight.toFloat() + mTextureViewWidth / 2) +
// (mTextPaint.fontMetrics.descent - mTextPaint.fontMetrics.ascent) / 2
canvas?.drawArc(left, top, right, bottom, 150F, -120F, false, mAnimatePaint)
canvas?.drawText(mTipsText, (measuredWidth.toFloat() - mTextPaint.measureText(mTipsText)) / 2,
(measuredHeight.toFloat() + mTextureViewWidth / 2) / 2 + mTextHeight * 1.5F, mTextPaint)
}
fun setTipsText(str: String) {
this.mTipsText = str
postInvalidate()
}
fun setCircleTextureWidth(width: Int) {
this.mTextureViewWidth = width
postInvalidate()
}
}
这里就遇到过一个坑:就是当时想直接在xml中把两个view的宽高写死,但是后来发现效果并不好(太大),于是又对TextureView重写了onGlobalLayoutListener的接口方法,然后将TextureView的实际宽高进行调整,但是却忘了调整BorderView了,虽然错误很低级,但是还是想记录提示自己一下。
而这里的那个扇形提示,就没有再对外做扩展,就固定了120度,当然如果有其他需求的可以在这里去写一个对外方法定义这个角度。(其实主要还是这个角度比较好算~)
那么到这里,就完成了第一阶段,对这个预览View的实现。接下来会去讲讲使用Camera2来实现相机预览功能。