高仿QQ 發送圖片高亮HaloProgressView

以前看到qq 的圖片發送效果很酷炫,很吸引人,不過如今這個效果好像沒有了。決定試試實現。大體想了下,實現效果還不錯。java

須要實現的效果

一圖勝千言,看圖以下: git

20181229_112329.gif

怎樣實現呢?

首先從圖中看分兩部分,一部分是進度條帶光暈得效果。第二部分是圓圈擴散到整個圖片,到顯示完整圖片的過程。接下來一步一步跟着代碼分析實現。github

1.繪製的範圍包括圖片顯示都在圓角矩形內,因此首先要裁剪canvas到圓角矩形。canvas

val path = Path()
        canvas.save()
        path.addRoundRect(RectF(0f, 0f, width.toFloat(), height.toFloat())
                , round, round, Path.Direction.CW)
        canvas.clipPath(path)
複製代碼

先保存畫布,save()到最後要canvas.restore().由於顯示圖片,能夠有兩種選擇,第一種:本身繪製圖片,經過drawable得方式。第二種:繼承ImageView 同時還能夠得到ImageView提供的各類屬性,scaleType之類。本質上ImageView也是通Drawable實現。IamgeView還幫咱們處理了測量的狂傲,因此有什麼理由不選擇繼承呢。而後繪製圖片只有簡單一行代碼,再裁剪畫布以後:bash

super.onDraw(canvas)
複製代碼

2.繪製背景 能夠看到效果圖,圖片在黑色半透明的下方。而且在最後顯示出來。着一點都是跟canvas繪製背景相關的。很少說,先設置畫筆。app

private var paint: Paint = Paint()
    paint.isAntiAlias = true
    paint.color = getColor(R.color.bantouming)

複製代碼

背景怎麼繪製,直接經過canvas.drawPaint方法便可實現。把paint的顏色繪製到整個畫布。而且再圖片後邊繪製,因此在上方。優化

canvas.drawPaint(paint)
複製代碼

3.繪製進度 這裏根據每一階段狀態的不一樣,經過三個狀態值區分:動畫

companion object {
        private const val READY = 1
        private const val PROGRESS = 2
        private const val FINISH = 3
    }
複製代碼

爲了方便的繪製,而且整個view是對稱的。因此座標點移動到view中心,很是有利於實現。ui

canvas.save()
        canvas.translate(width / 2f, height / 2f)
複製代碼

固然最後別忘了canvas.restore(). 在中間都好說了。先看百分比的實現。主要是drawText()的x,y比較很差掌握,不過搞明白基線之類的,就沒問題了。spa

先看百分比的paint

private val textPaint by lazy {
        Paint().apply {
            isAntiAlias = true
            style = Paint.Style.STROKE
            textSize = dp2px(16f).toFloat()
            color = getColor(R.color.main_gray)
        }
    }
複製代碼

接下來繪製,這行代碼可能比較長。須要優化,到這裏就先別吐槽。有點偷懶。 能夠看到根據文字的寬,高。來繪製的。高這裏須要額外注意

textPaint.textHeight().div(2) - textPaint.descent()
複製代碼

須要減去textPiat.descent(),若是不減繪製會偏下。

val text = "${progress}%"
                canvas.drawText(text, 0 - textPaint.measureText(text).div(2), textPaint.textHeight().div(2) - textPaint.descent(), textPaint)
複製代碼

4.繪製光暈 這算是實現比較疑難的地方。要注意3個地方。1.光暈的實現 2.呼吸效果 3.PorterDuffXmode 使用。

先看呼吸效果如何實現。可能簡單的想到的是經過圓環實現。但這樣挺麻煩的,若是經過兩個圓疊加,並設置paint.xfermode(PorterDuff.Mode.DST_OUT),可實現把內部圓裁剪掉。關於怎麼使用,請看以前的關於xfermode的文章。光暈的實現須要依賴shader,這裏經過RadilGradient 實現。具體用法也可看以前文章。 設置shader

paint.setShader(RadialGradient(0f, 0f, outRadius
                        , intArrayOf(Color.TRANSPARENT, Color.WHITE, Color.WHITE, Color.TRANSPARENT)
                        , floatArrayOf(0.1f, 0.4f, 0.8f, 1f), Shader.TileMode.CLAMP))
複製代碼

接下來的呼吸效果經過動畫設置大圓半徑的變化來實現。

canvas.drawCircle(0f, 0f, innRaduus + (outRadius - innRaduus) * animatorValue, paint)

複製代碼

完整代碼以下

canvas.drawCircle(0f, 0f, innRaduus + (outRadius - innRaduus) * animatorValue, paint)
                paint.setXfermode(PorterDuffXfermode(PorterDuff.Mode.DST_OUT))
                paint.setShader(null)
                paint.color = Color.WHITE
                canvas.drawCircle(0f, 0f, innRaduus, paint)
                paint.setXfermode(null)
複製代碼

若是僅僅是這樣那麼繪製出來中間喝一個黑洞。由於背景是透明的。因此這時候在繪製以前須要canvas.savlayer。以下

val sc = canvas.saveLayer(-outRadius, -outRadius, outRadius, outRadius, paint, Canvas.ALL_SAVE_FLAG)

複製代碼

保存的範圍包括大圓小圓 最後要restore

canvas.restoreToCount(sc)

複製代碼

再加上animatorValue 從0到1的動畫就完成PROGRES階段的動畫了。

5.繪製FINISH動畫,揭露圖片效果 一樣這裏也須要使用PorterDuff.Mode.DST_OUT,不過這裏須要的是對整個圓角畫布範圍進行操做。DST 是canvas.drawPaint繪製的背景。SRC 是一整個圓角矩形對角線的一半爲最大半徑,從PROGRES 狀態大圓的半徑的範圍,到最大範圍的動畫變化。以下:

val sc = canvas.saveLayer(-width.div(2f), -height.div(2f), width.div(2f), height.div(2f), paint, Canvas.ALL_SAVE_FLAG)
                canvas.drawPaint(paint)
                val maxRadius = Math.sqrt(Math.pow(width.toDouble(), 2.0) + Math.pow(height.toDouble(), 2.0)).div(2)
                paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT)
                paint.color = Color.WHITE
                canvas.drawCircle(0f, 0f, (outRadius + (maxRadius - outRadius) * finishAnimValue).toFloat(), paint)
                paint.xfermode = null
                canvas.restoreToCount(sc)
複製代碼

6.動畫的使用,與交替。這一點比較簡單的ValueAnimator的使用,設置屬性,Listener便可。有興趣可參考源碼 我是源碼

相關文章
相關標籤/搜索