本篇文章參考自【難以想象的CSS】天氣不可能那麼可愛,使用Android中的自定義View實現相似的效果。java
這段時間一直在研究自定義View,剛好看到使用CSS實現的天氣效果很不錯,遂嘗試使用自定義View實現一發。
不要臉的套用原做者的一句話,但願原做者不要揍我~git
只有你想不到,沒有自定義View實現不了的。今日分享由自定義View實現的效果 - Weathergithub
晴空一鶴排雲上,便引詩情到碧霄。canvas
因爲不可抗力緣由(懶),這裏只實現了晴、雪兩種天氣效果。原做者文章裏實現了晴、雪、雲、雨、超級月亮?(原文Supermoon,本人水平有限,實在不知道怎麼翻譯),有興趣的讀者能夠自行實現。數組
兩種天氣效果的實現源碼均已上傳至Github - Weather,客官請自取,若是剛好遇上鐵汁您心情好,不妨點個star。bash
接下來按照慣例分析一波項目裏使用的重要API:markdown
首先咱們來看看源碼中的註釋是怎麼描述的:app
/** * This takes a mask, and blurs its edge by the specified radius. Whether or * or not to include the original mask, and whether the blur goes outside, * inside, or straddles, the original mask's border, is controlled by the * Blur enum. */ /* 翻譯成大白話的意思就是BlurMaskFilter能夠在本來的View上添加一層指定模糊半徑的蒙層,具體模糊的方式,由Blur枚舉類型控制 */ 複製代碼
這裏咱們用BlurMaskFilter實現陰影效果~dom
Android系統裏的LinearGradient是paint的一種shader(着色器)方案。LinearGradient指的就是線性漸變:設置兩個點和兩種顏色,以這兩個點做爲端點,使用兩種顏色的漸變來繪製顏色,大概像下面這樣:
ide
咱們這裏星球的顏色所有都是經過指定paint的shader爲LinearGradient實現的。
上面介紹了部分重要的API,接下來咱們來一步一步實現 Snowy 的效果
作一個黑色背景,由於黑色視覺反差大視覺效果槓槓的,這裏先畫一個圓使其位於Canvas畫布中心位置,再使用BlurMaskFilter做出陰影,達成光暈的效果:
// 光暈的paint private val outPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { // 光暈的顏色 color = Color.parseColor("#e6e8db") // 使用BlurMaskFilter製做陰影效果 maskFilter = BlurMaskFilter(shadowRadius.toFloat(), BlurMaskFilter.Blur.SOLID) } /** 如下代碼是在onDraw()方法中 */ canvas.drawColor(Color.BLACK) canvas.drawCircle(centerX, centerY, outRadius.toFloat(), outPaint) 複製代碼
這裏使用LinearGradient作一個漸變的圓,並位於Canvas畫布中心位置,與step1中的光暈造成同心圓,這樣馬上就有一個不靈不靈的效果了~
private val innerCirclePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { shader = LinearGradient(centerX - innerRadius, centerY + innerRadius, centerX, centerY - innerRadius, Color.parseColor("#e0e2e5"), Color.parseColor("#758595"), Shader.TileMode.CLAMP) } /** 如下代碼是在onDraw()方法中 */ if (canvas != null){ // 繪製黑色背景 canvas.drawColor(Color.BLACK) // 繪製漸變圓 canvas.drawCircle(centerX, centerY, innerRadius.toFloat(), innerCirclePaint) } 複製代碼
注意,當設置了paint的**shader**屬性後,paint的**color**屬性就會失效,也就是說,當設置了 Shader 以後,Paint 在繪製圖形和文字時就不使用 setColor/ARGB() 設置的顏色了,而是使用 Shader 的方案中的顏色。
咱們這裏使用drawArc繪製一段圓弧:
// 確認雪人手臂位置的Rect private val snowyManHandRect = RectF(centerX - dp2px(40f), centerY, centerX + dp2px(40f), snowyManBodyY - dp2px(8f)) // 雪人手臂的paint private val snowyManHandPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = Color.BLACK style = Paint.Style.STROKE strokeWidth = dp2px(5f).toFloat() alpha = 120 } /** 如下代碼是在onDraw()方法中 */ if (canvas != null){ canvas.drawColor(Color.BLACK) canvas.drawCircle(centerX, centerY, outRadius.toFloat(), outPaint) canvas.drawCircle(centerX, centerY, innerRadius.toFloat(), innerCirclePaint) canvas.drawArc(snowyManHandRect, 155f,-120f,false, snowyManHandPaint) } 複製代碼
看到這裏有人會說,這是什麼鬼啊,哪裏像雪人的手臂啦,別急,咱們「走着瞧」
雪人的身體是由兩個相切(感謝個人數學老師,我居然還記得這麼專業的數學名詞)的大小不一樣的圓組成:
private val snowyManHeaderRadius = dp2px(12f) private val snowyManBodyRadius = dp2px(25f) private val snowyManHeaderX = centerX private val snowyManHeaderY = centerY + outRadius - snowyManHeaderRadius - snowyManBodyRadius * 2 private val snowyManBodyX = centerX - dp2px(3f) private val snowyManBodyY = centerY + outRadius - snowyManBodyRadius - dp2px(5f) private val snowyManPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = Color.parseColor("#e6e8db") } /** 如下代碼是在onDraw()方法中 */ canvas.drawCircle(snowyManHeaderX, snowyManHeaderY, snowyManHeaderRadius.toFloat(), snowyManPaint) canvas.drawCircle(snowyManBodyX, snowyManBodyY, snowyManBodyRadius.toFloat(), snowyManPaint) 複製代碼
這亞子第三步中畫的雪人手臂像手臂了吧,哼~
畫雪花以前咱們須要想象一下雪花在現實生活中的表現是什麼樣的:
再結合咱們設備的信息,咱們知道,雪花在設備上飄落時會有一個運動的範圍,這個範圍取決於它的父佈局的寬和高。 綜合以上信息,咱們能夠畫出雪花的類圖:
代碼以下:/** * snowyView 中飄落的雪花實體 * 使用Builder模式構造 */ class SnowFlake( var radius: Float?, val speed: Float?, val angle: Float?, val moveScopeX: Float?, val moveScopeY: Float? ) { private val TAG = "SnowFlake" private val random = java.util.Random() private var presentX = random.nextInt(moveScopeX?.toInt() ?: 0).toFloat() private var presentY = random.nextInt(moveScopeY?.toInt() ?: 0).toFloat() private var presentSpeed = getSpeed() private var presentAngle = getAngle() private var presentRadius = getRadius() private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = Color.parseColor("#e6e8db") alpha = 100 } // 繪製雪花 fun draw(canvas: Canvas){ moveX() moveY() if (moveScopeX != null && moveScopeY != null){ if (presentX > moveScopeX || presentY > moveScopeY || presentX < 0 || presentY < 0){ reset() } } canvas.drawCircle(presentX, presentY, presentRadius, paint) } // 移動雪花(x軸方向) fun moveX(){ presentX += getSpeedX() } // 移動雪花(Y軸方向) fun moveY(){ presentY += getSpeedY() } fun getSpeed(): Float{ var result: Float speed.let { result = it ?: (random.nextFloat() + 1) } return result } // 獲取雪花大小 fun getRadius(): Float{ var size: Float radius.let { size = it ?: random.nextInt(15).toFloat() } return size } // 獲取雪花下落角度 fun getAngle(): Float{ angle.let { if (it != null){ if (it > 30){ return 30f } if (it < 0){ return 0f } return it }else{ return random.nextInt(30).toFloat() } } } // 獲取雪花x軸的速度 fun getSpeedX(): Float{ return (presentSpeed * Math.sin(presentAngle.toDouble())).toFloat() } // 獲取雪花Y軸的速度 fun getSpeedY(): Float{ return (presentSpeed * Math.cos(presentAngle.toDouble())).toFloat() } // 重置雪花位置 fun reset(){ presentSpeed = getSpeed() presentAngle = getAngle() presentRadius = getRadius() presentX = random.nextInt(moveScopeX?.toInt()?:0).toFloat() presentY = 0f } data class Builder( var mRadius: Float? = null, var mSpeed: Float? = null, var mAngle: Float? = null, var moveScopeX: Float? = null, var moveScopeY: Float? = null ){ fun radius(radius: Float) = apply { this.mRadius = radius } fun speed(speed: Float) = apply { this.mSpeed = speed } fun angle(angle: Float) = apply { this.mAngle = angle } fun scopeX(scope: Float) = apply { this.moveScopeX = scope } fun scopeY(scope: Float) = apply { this.moveScopeY = scope } fun build() = SnowFlake(mRadius, mSpeed, mAngle, moveScopeX, moveScopeY) } } 複製代碼
這裏咱們隨機構造出30個雪花,他們的速度、大小、下落角度、初始位置都是隨機生成的,而後在SnowyView的onDraw()方法中繪製出來,並每隔5ms就刷新一次View,因爲雪花的位置是不停變換的,視覺上就造成了雪花紛紛揚揚的效果:
雪花紛紛何所似?——未若柳絮因風起// snowFlakes 爲包含30個雪花的數組 for (snow in snowFlakes){ snow.draw(canvas) } handler.postDelayed({ invalidate() },5) 複製代碼
感謝原做者D文斌的文章:【難以想象的CSS】天氣不可能那麼可愛 感謝扔物線大神的HenCoder系列文章(剛看到扔物線大佬的blog居然更新了)