Android打造圓形相機並實現人臉識別(一)

  先來看看效果:

  

    那接下來就會分別分享一下我做這個東東的時候,遇到的坑以及最終實現的方案。

    圓形相機預覽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來實現相機預覽功能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章