Android自定義View實(shí)現(xiàn)粉碎的面具效果
0.
首先話不多說(shuō),先上效果圖

這個(gè)gif把效果放慢了,真是運(yùn)行時(shí)會(huì)快很多。
1.分析
看效果,咱們可以分析一下,整個(gè)效果有四種狀態(tài),第一種就是普通狀態(tài),第二種是抖動(dòng)狀態(tài),第三種是隱藏圖片和粉碎狀態(tài),最后就是粉碎完成的狀態(tài),這么一分析就很好搞了,根據(jù)不同的狀態(tài)來(lái)寫代碼。
2.普通狀態(tài)
首先是普通狀態(tài),就是一個(gè)圖片的展示,這里我們可以看一下setImage方法
fun setImage(resId: Int)
{
image = BitmapFactory.decodeResource(context.resources, resId, null)
preapreCircleColor()
postInvalidate()
}
可以看到image是一個(gè)bitmap,圖片來(lái)自drawable,這沒什么可說(shuō)的,還有一個(gè)就是prepareCircleColor方法,這個(gè)方法是用來(lái)讀取bitmap不同位置的像素顏色,一次來(lái)確定粉碎時(shí)各個(gè)粒子的顏色。
private fun preapreCircleColor()
{
image?.let {
val step = it.width / Math.sqrt(circleNum.toDouble())
for (i in 0 until it.width step step.toInt())
{
for (j in 0 until it.height step step.toInt())
{
val color = it.getPixel(i, j)
if (circleAttributeList.size > 0)
{
circleAttributeList[i * 10 + j].color = color
}
}
}
}
}
3.抖動(dòng)狀態(tài)
抖動(dòng)我們通過(guò)一個(gè)ValueAnimator來(lái)實(shí)現(xiàn)
private fun initShakingAnimator()
{
shakingAnimator = ValueAnimator.ofInt(shakeCount)
shakingAnimator.duration = shakeDuration.toLong()
shakingAnimator.addListener(shakingListener)
shakingAnimator.addUpdateListener {
shakingNum = it.animatedValue as Int
postInvalidate()
}
}
shakeCount代表了都動(dòng)的次數(shù),shakeDuration代表抖動(dòng)的時(shí)間,這兩個(gè)屬性可以通過(guò)布局文件來(lái)配置。
在onDraw里可以看到drawShakingImage方法
private fun drawshakingImage(canvas: Canvas, centerX: Float, centerY: Float)
{
image?.let {
var offset = 0
offset = if (offset == shakeCount)
{
0
} else
{
if (shakingNum % 2 == 0) shakeOffset else -shakeOffset
}
canvas.drawBitmap(image, centerX + offset - it.width / 2, centerY + offset - it.height / 2, paint)
}
}
方法很簡(jiǎn)單,就是不停的繪制左右偏移的bitmap,當(dāng)?shù)竭_(dá)最大次數(shù)的時(shí)候偏移量為0。動(dòng)畫結(jié)束后,將狀態(tài)位置為STATE.FADE
private val shakingListener = object : AnimatorListenerAdapter()
{
override fun onAnimationEnd(animation: Animator?)
{
state = STATE.FADE
fadeOutAnimator.start()
bombAnimator.start()
}
}
3.隱藏粉碎狀態(tài)
動(dòng)都結(jié)束后,就進(jìn)入隱藏粉碎狀態(tài)了,這里我們用了兩個(gè)動(dòng)畫,fadeOutAnimator和bombAnimator,fadeOutAnimator用來(lái)隱藏圖片,而bombAnimator則是用來(lái)繪制粉碎的粒子,關(guān)于圖片的隱藏就不說(shuō)了,沒什么特別的,這里主要說(shuō)說(shuō)粉碎例子的繪制。
首先我們定義一個(gè)數(shù)據(jù)類
data class CircleAttribute(var startVerVelocity: Float, var horVelocity: Float, var orX:Float,var orY:Float, var x: Float, var y: Float, var color: Int,var radius:Float)
這個(gè)類用來(lái)表示每個(gè)粒子起始時(shí)豎直方向的速度,水平方向的速度,起始坐標(biāo),位置坐標(biāo),粒子顏色和半徑。
接著在onMeasure結(jié)束后,調(diào)用了一個(gè)方法prepareCircleAttributeList()
private fun prepareCircleAttributeList()
{
circleAttributeList.clear()
val centerX = measuredWidth.toFloat() / 2
val centerY = measuredHeight.toFloat() / 2
val maxVerVelocity = measuredHeight / bombDuration
val maxHorVelocity = measuredWidth / 2 / bombDuration
a = maxVerVelocity * 3 / bombDuration
for (i in 0 until circleNum)
{
var color = Color.WHITE
val step = Math.sqrt(circleNum.toDouble()).toInt()
var x = centerX
var y = centerY
image?.let {
val posXStep=it.width/step
val posYStep=it.height/step
val topX=centerX-it.width/2
val topY=centerY-it.height/2
val row = i / step
val col = i % step
color = it.getPixel(row * posXStep, col * posYStep)
x=topX+row*posXStep.toFloat()
y=topY+col*posYStep.toFloat()
}
val random = Math.random()
val signal = (random * 4).toInt()
val startVelocity = (Math.random() * maxVerVelocity).toFloat()
val horVelocity = if (signal % 2 == 0) (Math.random() * maxHorVelocity).toFloat() else -(Math.random() * maxHorVelocity).toFloat()
val attribute = CircleAttribute(startVelocity, horVelocity, x, y, x, y, color, (Math.random() * 15).toFloat())
circleAttributeList.add(attribute)
}
}
這個(gè)方法就是初始化每個(gè)粒子的數(shù)據(jù)的,最后將數(shù)據(jù)添加到circleAttributeList。其中a為豎直方向加速度,這里取得比較籠統(tǒng),就是就是假定三分之一的粒子粉碎時(shí)間,最大速度就能減少到0。然后就是確定粒子的位置和顏色,粒子的數(shù)量是可以在布局文件控制的,粒子的位置和顏色基本上就是對(duì)bitmap的映射,所以如果有100個(gè)點(diǎn),那么bitmap就可以看做10*10的一個(gè)粒子陣,每個(gè)粒子的位置和顏色是與其相對(duì)應(yīng)的,理解了這個(gè)看代碼應(yīng)該就明白了。
啟動(dòng)動(dòng)畫后,接下來(lái)就是位置的更新了,看initBombAnimator()方法
private fun initBombAnimator()
{
bombAnimator = ValueAnimator.ofFloat(bombDuration)
bombAnimator.duration = bombDuration.toLong()
bombAnimator.addListener(object : AnimatorListenerAdapter()
{
override fun onAnimationEnd(animation: Animator?)
{
super.onAnimationEnd(animation)
state = STATE.BOMBED
cancelAllAnimators()
bombFinishedListener?.onBombFinished()
circleAlpha = 0
}
})
bombAnimator.addUpdateListener {
val time = it.animatedValue as Float
for (i in circleAttributeList)
{
i.x = i.orX + i.horVelocity * time
i.y = i.orY - (i.startVerVelocity * time - 0.5f * a * time * time)
}
if (it.animatedFraction > 0.5)
{
circleAlpha -= (0.5 * circleAlpha * it.animatedFraction).toInt()
}
postInvalidate()
}
}
水平方向的位置就是 i.x = i.orX + i.horVelocity * time, 標(biāo)準(zhǔn)的時(shí)間速度
豎直方向的位置就是 i.y = i.orY - (i.startVerVelocity * time - 0.5f * a * time * time) 公式s=v0t+1/2att,初中生都知道。circleAlpha是用來(lái)控制粒子的alpha值的。隨著動(dòng)畫的進(jìn)行,不停的進(jìn)行invalidate,接下來(lái)看onDraw方法調(diào)用drawCircles方法
private fun drawCircles(canvas: Canvas)
{
for (i in circleAttributeList)
{
if (Color.alpha(i.color) == 0)
{
paint.alpha = 0
} else
{
paint.color = i.color
paint.alpha = circleAlpha
}
canvas.drawCircle(i.x, i.y, i.radius, paint)
}
}
這里有一點(diǎn)要注意的是,從bitmap里取到的顏色值是argb格式的,而paint設(shè)置的顏色是rgb格式的,所以如果取到的顏色alpha為0,將paint的alpha設(shè)置為0.最后動(dòng)畫結(jié)束是將狀態(tài)位置為BOMBED,并調(diào)用回調(diào)函數(shù)
interface OnBombFinishedListener
{
fun onBombFinished()
}
4.總結(jié)
基本上原理就差不多是這些了,最后附上源碼地址
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
Android bindService的使用與Service生命周期案例詳解
這篇文章主要介紹了Android bindService的使用與Service生命周期案例詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-09-09
Android 自定義組件成JAR包的實(shí)現(xiàn)方法
這篇文章主要介紹了Android 自定義組件成JAR包的實(shí)現(xiàn)方法的相關(guān)資料,偶爾會(huì)用到這樣的功能,如果你自己自定義的組件很好,需要的朋友可以參考下2016-11-11
Android垂直切換的圓角Banner與垂直指示器相關(guān)介紹與應(yīng)用詳解
這篇文章主要介紹了Android垂直切換的圓角Banner與垂直指示器相關(guān)介紹與應(yīng)用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2022-10-10
Android 滑動(dòng)Scrollview標(biāo)題欄漸變效果(仿京東toolbar)
這篇文章主要介紹了Android 滑動(dòng)Scrollview標(biāo)題欄漸變效果(仿京東toolbar),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01
Android 通過(guò)API獲取數(shù)據(jù)庫(kù)中的圖片文件方式
這篇文章主要介紹了Android 通過(guò)API獲取數(shù)據(jù)庫(kù)中的圖片文件方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-03-03
Android實(shí)現(xiàn)圖片點(diǎn)擊放大
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)圖片點(diǎn)擊放大,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-10-10
Android自定義View實(shí)現(xiàn)QQ消息氣泡
這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)QQ消息氣泡,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08
Android創(chuàng)建服務(wù)之started service詳細(xì)介紹
這篇文章主要介紹了Android創(chuàng)建服務(wù)之started service,需要的朋友可以參考下2014-02-02

