之前看到qq 的圖片發送效果很酷炫,很吸引人,不過現在這個效果好像沒有了。試了幾次,決定試試實現。大致想了下,實現效果還不錯
需要實現的效果
一圖勝千言,看圖如下:
怎樣實現呢?
首先從圖中看分兩部分,一部分是進度條帶光暈得效果。第二部分是圓圈擴散到整個圖片,到顯示完整圖片的過程。接下來一步一步跟著代碼分析實現。
1、繪制的範圍包括圖片顯示都在圓角矩形內,所以首先要裁剪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還幫我們處理了測量的狂傲,所以有什麽理由不選擇繼承呢。然後繪制圖片只有簡單一行代碼,再裁剪畫布之後:
super.onDraw(canvas)
2、繪制背景
可以看到效果圖,圖片在黑色半透明的下方。並且在最後顯示出來。著一點都是跟canvas繪制背景相關的。不多說,先設置畫筆。
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中心,非常有利于實現。
canvas.save() canvas.translate(width / 2f, height / 2f)
當然最後別忘了canvas.restore() 。
在中間都好說了。先看百分比的實現。主要是drawText()的x,y比較不好掌握,不過搞明白基線之類的,就沒問題了。
先看百分比的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)
動畫的使用與交替。這一點比較簡單的ValueAnimator的使用,設置屬性,Listener即可。有興趣可參考源碼
Github: https://github.com/hewking/HaloImageProgressView
感謝大家能耐著性子看完啰裏啰嗦的文章
在這裏我也分享一份私貨,自己收錄整理的Android學習PDF+架構視頻+面試文檔+源碼筆記,還有高級架構技術進階腦圖、Android開發面試專題資料,高級進階架構資料幫助大家學習提升進階,也節省大家在網上搜索資料的時間來學習,也可以分享給身邊好友一起學習
如果你有需要的話,可以點贊+評論+轉發,關注我,然後私信我【進階】我發給你