一、寫在前面的話
效果如上圖,筆者在午休的時候,重新追了一遍神探狄仁傑II,蛇靈密謀利用日食,引洛河之水顛覆武周社稷。日食來臨時,天地昏暗,日食之後萬物回覆光明。看完一想,要不我也搞一個日食效果看看,於是,就有了這篇文章。
二、分析動畫
首先有一個圓,取個名字,叫太陽(Sun),月亮(Moon)緩緩從Sun上滑過,並且隨着兩個圓重合,背景顏色逐漸變深,在完全重合的時候深度達到最大,然後Moon緩緩與Sun分離,分離的時候背景顏色逐漸變淺,最後恢復原狀,logo出現。這裏有兩個技術點,第一是背景色的控制;第二就是Moon這個圓只有與Sun重合的部分纔可見,其餘部分不可見,這樣的日食纔是逼真的。
三、作畫(onDraw)
//draw太陽
canvas?.drawCircle(centerX.toFloat(), centerY.toFloat(), mSunR.toFloat(), mSunPaint)
首先畫一個圓,作爲太陽。那麼月亮是不是按照同樣的方式畫一個比較小的圓?其實不是的,如果按照同樣的方式,畫出的圓回覆蓋在月亮上,真正的日食因爲太陽的亮度很大,通過肉眼只能在太陽上看到月亮,所以我們要用到PorterDuffXfermode這個東西,如下所示:
xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP)
這個的mode有很多種,這個不是本文的重點,這裏不做介紹,大概的意思就是隻有重合的部分可見。畫月亮的部分如下所示
//draw月亮
mSunPaint.color = mMoonColor
mSunPaint.xfermode = xfermode
canvas?.drawCircle(mMoonX.toFloat(), centerY.toFloat(), mMoonR.toFloat(), mSunPaint)
mSunPaint.xfermode = null
接下來畫logo,要在Sun和Moon第一次重合後才畫,並且要在畫月亮之前。所以onDraw部分完整的代碼如下
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
mSunPaint.color = mSunColor
//draw太陽
canvas?.drawCircle(centerX.toFloat(), centerY.toFloat(), mSunR.toFloat(), mSunPaint)
if (isLogoShow) {
mLogoPaint.xfermode = xfermode
canvas?.drawBitmap(mLogoBitmap, mLogoBitmapSrcRect, mLogoBitmapDesRect, mLogoPaint)
mLogoPaint.xfermode = null
}
//draw月亮
mSunPaint.color = mMoonColor
mSunPaint.xfermode = xfermode
canvas?.drawCircle(mMoonX.toFloat(), centerY.toFloat(), mMoonR.toFloat(), mSunPaint)
mSunPaint.xfermode = null
}
元素都畫好了,接下里就是讓他們動起來,我這裏用的是handler實現的。首先是進入的動畫,每一次改變Moon的X座標
MSG_IN -> {
if (mMoonX < centerX) {
mMoonX += mMoonXOffset
invalidate()
it.target.sendEmptyMessageDelayed(MSG_IN, mAnimationSpeed)
} else if (kotlin.math.abs(mMoonX - centerX) <= mMoonXOffset) {
isLogoShow = true
invalidate()
it.target.sendEmptyMessageDelayed(MSG_OUT, 500)
}
progressOffset = txfloat((mMoonX - mMoonStartX), (4 * mSunR))
}
退出動畫也和進入動畫一樣,做不過要畫上logo,畫筆的透明度逐漸變大,這樣logo出現的不會太突兀。
MSG_OUT -> {
if (mMoonX - centerX < 2 * mSunR) {
mMoonX += mMoonXOffset
var tempAlpha = 255 * (mMoonX - centerX) / (2 * mSunR)
if (tempAlpha > mLogoPaint.alpha) {
if (tempAlpha > 255) {
tempAlpha = 255
}
mLogoPaint.alpha = tempAlpha
}
invalidate()
it.target.sendEmptyMessageDelayed(MSG_OUT, mAnimationSpeed)
}
progressOffset = txfloat((mMoonX - mMoonStartX), (4 * mSunR))
}
接下來就是背景,這裏的顏色的變化我給大家提供一個方法計算顏色。
/**
* 根據fraction值來計算當前的顏色。
*/
private fun getCurrentColor(fraction: Float, startColor: Int, endColor: Int): Int {
val redCurrent: Int
val blueCurrent: Int
val greenCurrent: Int
val alphaCurrent: Int
val redStart = Color.red(startColor)
val blueStart = Color.blue(startColor)
val greenStart = Color.green(startColor)
val alphaStart = Color.alpha(startColor)
val redEnd = Color.red(endColor)
val blueEnd = Color.blue(endColor)
val greenEnd = Color.green(endColor)
val alphaEnd = Color.alpha(endColor)
val redDifference = redEnd - redStart
val blueDifference = blueEnd - blueStart
val greenDifference = greenEnd - greenStart
val alphaDifference = alphaEnd - alphaStart
redCurrent = (redStart + fraction * redDifference).toInt()
blueCurrent = (blueStart + fraction * blueDifference).toInt()
greenCurrent = (greenStart + fraction * greenDifference).toInt()
alphaCurrent = (alphaStart + fraction * alphaDifference).toInt()
return Color.argb(alphaCurrent, redCurrent, greenCurrent, blueCurrent)
}
我們背景需要通過監聽器回調出去,讓view的父佈局去實現。爲什麼呢?因爲如果我們在控件內部畫了背景,那麼通過xfermode方法畫的Moon就在整個背景上可見(我們要求日食Moon只能在與Sun重合的部分纔可見)
if (it.what == MSG_IN || it.what == MSG_OUT) {
val color = if (progressOffset * 2 > 1) {
getCurrentColor(2 - progressOffset * 2, mBgStartColor, mBgEndColor)
} else {
getCurrentColor(progressOffset * 2, mBgStartColor, mBgEndColor)
}
eclipseListener?.onColor(color)
}
這樣,我們日食就完成了。
四、使用
Add it in your root build.gradle at the end of repositories:
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
Add the dependency
dependencies {
implementation 'com.github.cqcby1994:MyView:1.2.1'
}
<com.chen.eclipseview.EclipseLogo
android:id="@+id/eclipse_view"
app:logoSize="large"
app:speed="middle"
app:sunColor="@android:color/white"
app:moonColor="#279536"
app:endBackgroundColor="#279536"
app:startBackgroundColor="@android:color/white"
app:logoSrc="@mipmap/wechat"
android:layout_width="200dp"
android:layout_height="200dp"
/>
大家可以自己定義速度(speed)、logo、logo大小、顏色等參數。
github:點我去看看
如果你覺得還不錯,動動小手,給作者一個star鼓勵鼓勵,如果你有想實現的控件願意和筆者交流的,可以在下面留言,多謝。