先來看看效果:
那接下來就會分別分享一下我做這個東東的時候,遇到的坑以及最終實現的方案。
圓形相機預覽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來實現相機預覽功能。