序言
當(dāng)前Android 主流的系統(tǒng)中粒子的實(shí)現(xiàn)方式大致可以分為兩類三種
1. 繼承普通的View善涨,主線程刷新繪制
2. 使用surfaceView,子線程刷新繪制
3. 使用surfaceView+openGl啃奴,子線程刷新繪制
因?yàn)榈谌N方式不是很常用潭陪,這里使用前兩種方式實(shí)現(xiàn)粒子
效果圖(視頻轉(zhuǎn)GIf有點(diǎn)卡)
實(shí)現(xiàn)功能
1. 可以自定義粒子刷新的頻率,每次刷新的數(shù)量
2. 粒子自己維護(hù)自己的生命周期
3. 可以預(yù)先自己定義的粒子數(shù)量加載粒子 避免一加載就是滿屏粒子的尷尬
4. 復(fù)用消亡粒子
5. 實(shí)現(xiàn)粒子的高度自定義 避免過度封裝
理論概述
兩種實(shí)現(xiàn)方式 對(duì)粒子的處理大致相同 不同的是繪制的位置區(qū)別
這里以第一種方式為例 畫出粒子的UML框架:
如上圖所示
ParticleView繼承Runnable 使用handler定時(shí)刷新最蕾,每次刷新進(jìn)行以下一個(gè)步驟
1. 新增粒子 從cacheItems集合中獲取緩存粒子 如果沒有則調(diào)用adapter進(jìn)行創(chuàng)建
2. 調(diào)用transForms 移動(dòng)粒子并將消亡粒子移除繪制集合中
3. 調(diào)用drawItems方法繪制所有的粒子
使用
這里因?yàn)槭歉叨荣Y質(zhì)粒子依溯,所以如果我們想要繪制粒子需要首先繼承BaseItem接口定義自己的粒子類:
然后實(shí)現(xiàn)適配器ParticleAdapter
如:
particle.setAdapter(object : ParticleAdapter() {
override fun newItem(parentWidth: Int, parentHeight: Int): BaseItem {
//返回自己實(shí)現(xiàn)的粒子
return BubbleItem(parentWidth, parentHeight, this@MainActivity)
}
override fun preCreateCount(): Int {
return 50
}
})
particle.setIncreaseParticleInterval(100)
particle.setRenderTime(16)
particle.setIncreaseParticleCount(1)
我這里實(shí)現(xiàn)的粒子如下:BubbleItem.kotlin
class BubbleItem(private val parentWidth: Int, private val parentHeight: Int,context: Context) :
BaseItem {
companion object {
const val STATE_LIVE = 1
const val STATE_DIE = 0
}
private val baseBubbleRadius = ScreenUtil.dip2px(context,2f)
private val intervalBubbleRadius = ScreenUtil.dip2px(context,3f)
//起點(diǎn)
private var origX: Float = 0f
private var origY: Float = parentHeight.toFloat()
//終點(diǎn)
private var desY: Float = 0f
//當(dāng)前的位置
private var curX = 0f
private var curY = 0f
//每次刷新 在Y軸的偏移量
private var speedY = ScreenUtil.dip2px(context,2f)
private val baseSpeedX =ScreenUtil.dip2px(context,0.5f)
//每次刷新 在X軸的偏移量
private var speedX = 0f
var radius = 20f
//透明的距離
private var alphaDis = 0f
var state = STATE_DIE
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private var drawBitmap: Bitmap? = null
private var resRect: Rect? = null
init {
paint.style = Paint.Style.FILL_AND_STROKE
paint.color = Color.BLUE
}
/**
* 初始化 隨機(jī)生成氣泡的出生地點(diǎn)
*/
override fun init(context: Context) {
//獲取氣泡的bitmap
if (drawBitmap == null) {
drawBitmap = BitmapFactory.decodeResource(context.resources, R.drawable.bubble)
resRect = Rect(0, 0, drawBitmap?.width ?: 0, drawBitmap?.height ?: 0)
}
origX = Random.nextInt(100, parentWidth - 100).toFloat()
desY = 2 * parentHeight / 3 - Random.nextInt(0, parentHeight / 2).toFloat()
alphaDis = (origY - desY) * 0.2f
radius = Random.nextFloat() * intervalBubbleRadius + baseBubbleRadius
speedX = baseSpeedX * Random.nextFloat() * if (Random.nextBoolean()) {
1
} else {
-1
}
curX = origX
curY = origY
state = STATE_LIVE
//在邊界處的粒子 沒有橫向速度
if (curX <= 200 || curX > (parentWidth - 200)) {
speedX = 0f
}
paint.alpha = 255
}
override fun preInit(context: Context) {
//起點(diǎn)的X軸坐標(biāo)
init(context)
curY = desY + max((origY - desY) * Random.nextFloat(), 0f)
}
override fun move(): Boolean {
curY -= speedY
curX += speedX
val diff = curY - desY
if (diff <= alphaDis) {
if (diff <= alphaDis * 0.4 && diff >=0.3 * alphaDis) {
paint.alpha = 255
} else {
//開始透明
paint.alpha = (255 * diff / alphaDis + 0.5f).toInt()
}
}
if (curY < desY) {
state = STATE_DIE
return false
}
if (curX <= 20 || curX >= parentWidth - 20) {
state = STATE_DIE
return false
}
return true
}
override fun reset() {
}
override fun draw(canvas: Canvas) {
drawBitmap?.apply {
if (!isRecycled) {
canvas.drawBitmap(this, resRect, RectF(curX - radius, curY - radius, curX + radius, curY + radius), paint)
}
}
}
override fun isLive(): Boolean {
return state == STATE_LIVE
}
override fun destroy() {
drawBitmap?.recycle()
drawBitmap = null
}
}
使用普通View 主線程刷新繪制
普通的View繪制就是 使用handler主線程繪制刷新實(shí)現(xiàn)如下:
class ParticleView(context: Context, attributeSet: AttributeSet) : View(context, attributeSet),
BaseParticle, Runnable {
private var particleAdapter: ParticleAdapter? = null
private var isAutoPlay = true
private var intervalTime = 10 * Constant.RENDER_TIME
private var renderTime = Constant.RENDER_TIME
private var increaseParticleCount = 1
private var childTotal = Int.MAX_VALUE
private var temTime = -1
//緩存的view
private var cacheItems = LinkedList<BaseItem>()
//要繪制的所有View
private var drawItems = ArrayList<BaseItem>()
private var renderHandler: Handler = Handler()
private var isInit: Boolean = false
override fun preCreate() {
repeat(particleAdapter?.preCreateCount() ?: 0) {
val newItem = particleAdapter!!.newItem(measuredWidth, measuredHeight)
newItem.preInit(context)
drawItems.add(newItem)
}
}
override fun getItem(): BaseItem {
val newItem = if (cacheItems.size > 0) {
cacheItems.removeFirst()
} else {
particleAdapter!!.newItem(measuredWidth, measuredHeight)
}
newItem.init(context)
return newItem
}
override fun drawItems(canvas: Canvas) {
if (drawItems.size < childTotal) {
if (temTime == -1) {
temTime = ((intervalTime / renderTime.toFloat() + 0.5).toInt())
} else if (temTime == 0) {
repeat(increaseParticleCount) {
drawItems.add(getItem())
}
}
temTime--
}
drawItems.forEach {
it.draw(canvas)
}
}
override fun transForms() {
val iterator = drawItems.iterator()
while (iterator.hasNext()) {
val next = iterator.next()
val isLive = next.move()
if (!isLive) {
iterator.remove()
cacheItems.add(next)
}
}
}
override fun destroyAllView() {
drawItems.forEach {
it.destroy()
}
cacheItems.forEach {
it.destroy()
}
}
override fun startAnimation() {
renderHandler.removeCallbacks(this)
renderHandler.post(this)
}
override fun stopAnimation() {
renderHandler.removeCallbacks(this)
}
override fun setAdapter(adapter: ParticleAdapter) {
particleAdapter = adapter
if (particleAdapter!!.maxParticleCount() <= 0) {
return
}
childTotal = particleAdapter!!.maxParticleCount()
}
override fun setRenderTime(renderTime: Long) {
if (intervalTime < renderTime || renderTime < 0) {
return
}
this.renderTime = renderTime
}
override fun setIncreaseParticleInterval(intervalTime: Long) {
if (intervalTime < renderTime || renderTime < 0) {
return
}
this.intervalTime = intervalTime
}
override fun setIncreaseParticleCount(count: Int) {
if (count <= 0) {
return
}
increaseParticleCount = count
}
override fun setIsAutoPlay(isAuto: Boolean) {
isAutoPlay = isAuto
}
/**
* 設(shè)置大小則按照設(shè)置的大小計(jì)算 否則按照屏幕的寬高來計(jì)算
*/
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
//獲取屏幕寬高
val screenWidth = ScreenUtil.getScreenWidth(context)
val screenHeight = ScreenUtil.getScreenRealHeight(context)
setMeasuredDimension(
getDefaultSize(screenWidth, widthMeasureSpec),
getDefaultSize(screenHeight, screenHeight)
)
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
if (!isInit) {
isInit = true
preCreate()
}
}
override fun onDraw(canvas: Canvas?) {
if (visibility != VISIBLE) {
return
}
if (canvas == null) {
renderHandler.removeCallbacks(this)
return
}
drawItems(canvas)
}
override fun run() {
transForms()
invalidate()
renderHandler.postDelayed(this, renderTime)
}
override fun setVisibility(visibility: Int) {
super.setVisibility(visibility)
if (isAutoPlay) {
if (visibility == VISIBLE) {
startAnimation()
} else {
renderHandler.removeCallbacksAndMessages(null)
}
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
renderHandler.removeCallbacks(this)
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
renderHandler.post(this)
}
}
使用surfaceView,子線程刷新繪制
實(shí)現(xiàn)如下:
class ParticleSurfaceView(context: Context, attributeSet: AttributeSet) :
SurfaceView(context, attributeSet), BaseParticle, SurfaceHolder.Callback, Runnable {
private var particleAdapter: ParticleAdapter? = null
private var isAutoPlay = true
private var intervalTime = 10 * Constant.RENDER_TIME
private var renderTime = Constant.RENDER_TIME
private var increaseParticleCount = 1
private var childTotal = Int.MAX_VALUE
private var temTime = -1
private val surfaceHolder: SurfaceHolder = holder
private lateinit var handlerThread: HandlerThread
private lateinit var renderHandler: Handler
//緩存的view
private var cacheItems = LinkedList<BaseItem>()
//要繪制的所有View
private var drawItems = ArrayList<BaseItem>()
init {
surfaceHolder.setKeepScreenOn(true)
surfaceHolder.addCallback(this)
isFocusable = true
isFocusableInTouchMode = true
setZOrderOnTop(true)
//設(shè)置背景為透明色
surfaceHolder.setFormat(PixelFormat.TRANSPARENT)
}
override fun preCreate() {
repeat(particleAdapter?.preCreateCount() ?: 0) {
val newItem = particleAdapter!!.newItem(measuredWidth, measuredHeight)
newItem.preInit(context)
drawItems.add(newItem)
}
}
override fun getItem(): BaseItem {
val newItem = if (cacheItems.size > 0) {
cacheItems.removeFirst()
} else {
particleAdapter!!.newItem(measuredWidth, measuredHeight)
}
newItem.init(context)
return newItem
}
override fun drawItems(canvas: Canvas) {
if (drawItems.size < childTotal) {
if (temTime == -1) {
temTime = ((intervalTime / renderTime.toFloat() + 0.5).toInt())
} else if (temTime == 0) {
repeat(increaseParticleCount) {
drawItems.add(getItem())
}
}
temTime--
}
drawItems.forEach {
it.draw(canvas)
}
}
override fun transForms() {
val iterator = drawItems.iterator()
while (iterator.hasNext()) {
val next = iterator.next()
val isLive = next.move()
if (!isLive) {
iterator.remove()
cacheItems.add(next)
}
}
}
override fun destroyAllView() {
drawItems.forEach {
it.destroy()
}
cacheItems.forEach {
it.destroy()
}
}
override fun startAnimation() {
renderHandler.removeCallbacks(this)
renderHandler.post(this)
}
override fun stopAnimation() {
renderHandler.removeCallbacks(this)
}
override fun setAdapter(adapter: ParticleAdapter) {
particleAdapter = adapter
if (particleAdapter!!.maxParticleCount() <= 0) {
return
}
childTotal = particleAdapter!!.maxParticleCount()
}
override fun setRenderTime(renderTime: Long) {
if (intervalTime < renderTime || renderTime < 0) {
return
}
this.renderTime = renderTime
}
override fun setIncreaseParticleInterval(intervalTime: Long) {
if (intervalTime < renderTime || renderTime < 0) {
return
}
this.intervalTime = intervalTime
}
override fun setIncreaseParticleCount(count: Int) {
if (count <= 0) {
return
}
increaseParticleCount = count
}
override fun setIsAutoPlay(isAuto: Boolean) {
isAutoPlay = isAuto
}
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
}
override fun surfaceDestroyed(holder: SurfaceHolder?) {
renderHandler.removeCallbacks(this)
destroyAllView()
}
override fun surfaceCreated(holder: SurfaceHolder?) {
if (particleAdapter == null) {
throw NullPointerException("particleAdapter must not be null")
}
handlerThread = HandlerThread("BaseSurfaceView")
handlerThread.start()
renderHandler = Handler(handlerThread.looper)
preCreate()
if (isAutoPlay) {
renderHandler.post(this)
}
}
override fun run() {
transForms()
val lockCanvas = surfaceHolder.lockCanvas(null)
lockCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
drawItems(lockCanvas)
surfaceHolder.unlockCanvasAndPost(lockCanvas)
renderHandler.postDelayed(this, renderTime)
}
}
項(xiàng)目地址:項(xiàng)目地址傳送門 點(diǎn)我 點(diǎn)我 點(diǎn)我!!!