Android 粒子

實現功能

實現氣泡粒子向上飄動 (視頻轉GIf有點卡)
在這裏插入圖片描述

效果圖

功能

  1. 粒子的刷新使用:主線程handler 指定頻率刷新,
  2. 粒子的生命週期分爲存活期 和消亡期 每次生成粒子都從消亡的粒子中獲取 實現粒子緩存
  3. 粒子預置 可以提前佈置指定數量的粒子
  4. 粒子自己控制自己的生命週期 避免內存泄漏

代碼詳情

BaseView.kotlin 粒子View的基類 實現粒子刷新頻率控制

abstract class BaseView(context: Context, attributeSet: AttributeSet?) :
   View(context, attributeSet) {

companion object {
    //每秒刷新40幀
    const val RENDER_TIME = 25L
}
//實現刷新的handler
private var renderHandler: RenderHandler? = null
var renderTime = RENDER_TIME
//是否是自動執行動畫 默認是true 
var isAutoPlay = true

/**
 * 設置大小則按照設置的大小計算 否則按照屏幕的寬高來計算
 */
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 setVisibility(visibility: Int) {
    super.setVisibility(visibility)
    if (isAutoPlay) {
        if (visibility == VISIBLE) {
            startAnimation()
        } else {
            renderHandler?.removeCallbacksAndMessages(null)
        }
    }
}

fun startAnimation() {
    if (renderHandler == null) {
        renderHandler = RenderHandler(this)
    }
    renderHandler?.removeCallbacksAndMessages(null)
    renderHandler?.sendEmptyMessage(0)
}

fun stopAnimation() {
    renderHandler?.removeCallbacksAndMessages(null)
    renderHandler = null
}

@SuppressLint("DrawAllocation")
override fun onDraw(canvas: Canvas?) {
    if (visibility != VISIBLE) {
        return
    }
    //繪製粒子
    if (renderHandler != null) {
        if (canvas == null) {
            renderHandler?.removeCallbacksAndMessages(null)
            return
        }
        drawItems(canvas)
    } else if (isAutoPlay) {
        renderHandler = RenderHandler(this)
        renderHandler?.sendEmptyMessage(0)
    }
}

/**
 * 提前生成粒子
 */
abstract fun preCreate()

/**
 * 繪製所有的子View
 */
abstract fun drawItems(canvas: Canvas)

/**
 * 對所有的子View 做變形操作
 */
abstract fun transForms()

open fun destroyAllView() {
}


override fun onDetachedFromWindow() {
    renderHandler?.removeCallbacksAndMessages(null)
    destroyAllView()
    super.onDetachedFromWindow()
}

class RenderHandler(baseView: BaseView? = null) : Handler() {
    private val weakReference = WeakReference(baseView)

    init {
        weakReference.get()?.preCreate()
    }
    override fun handleMessage(msg: Message) {
        weakReference.get()?.transForms()
        weakReference.get()?.invalidate()
        sendEmptyMessageDelayed(0, weakReference.get()?.renderTime ?: RENDER_TIME)
    }
}
}

CoordinatorView.kotlin 繼承與BaseView 控制粒子位置變換 粒子繪製 實現粒子緩存功能

abstract class CoordinatorView(context: Context, attributeSet: AttributeSet? = null) :
    BaseView(context, attributeSet) {

private var cacheItems = LinkedList<BaseItem>()
//要繪製的所有View
private var drawItems = ArrayList<BaseItem>()
//添加新VIew的間隔時間
var addItemPerTime: Long = 1000
//總的子VIew的數量
var childTotal = 50
private var temTime = -1

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 drawItems(canvas: Canvas) {
    if (drawItems.size < childTotal) {
        if (temTime == -1) {
            temTime = ((addItemPerTime / renderTime.toFloat() + 0.5).toInt())
        } else if (temTime == 0) {
            repeat(getIncreaseCountPerRefresh()) {
                drawItems.add(getItem())
            }
        }
        temTime--
    }
    drawItems.forEach {
        it.draw(canvas)
    }
}

private fun getItem(): BaseItem {
    val newItem = if (cacheItems.size > 0) {
        cacheItems.removeFirst()
    } else {
        newItem(measuredWidth, measuredHeight)
    }
    newItem.init(context)
    return newItem
}

/**
 * 獲取每次新增粒子的數量
 */
open fun getIncreaseCountPerRefresh(): Int {
    return 1
}

override fun preCreate() {
    repeat(preCreateCount()) {
        val newItem = newItem(measuredWidth, measuredHeight)
        newItem.preInit(context)
        drawItems.add(newItem)
    }
}


override fun destroyAllView() {
    drawItems.forEach {
        it.destroy()
    }
    cacheItems.forEach{
        it.destroy()
    }
}


abstract fun newItem(parentWidth: Int, parentHeight: Int): BaseItem

abstract fun preCreateCount(): Int
}

BaseItem 是粒子的基類 自定義的粒子要繼承該類

interface BaseItem {

/**
 * 初始化View
 * 初始化:出生的位置 移動的速度 移動的距離 移動的方向
 */
fun init(context: Context)

/**
 * 提前生成粒子的初始化
 */
fun preInit(context: Context)

/**
 * 移動View
 *@return true 還能移動 false 消亡
 */
fun move():Boolean

/**
 * 重置
 */
fun reset()

/**
 * 繪製View
 */
fun draw(canvas: Canvas)

/**
 * 當前粒子是否存活
 */
fun isLive():Boolean

/**
 * 粒子消亡
 */
fun destroy()
}

每次實現自定義粒子 總要是繼承baseitem init方法中實現初始位置 在move中實現每一次刷新中要移動的位置,如上圖效果粒子實現的item如下

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)

//起點
private var origX: Float = 0f
private var origY: Float = parentHeight.toFloat()

//終點
private var desY: Float = 0f
//當前的位置
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
}

/**
 * 初始化 隨機生成氣泡的出生地點
 */
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) {
    //起點的X軸座標
    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是 BubbleView.kotlin

class BubbleView(context: Context, attributeSet: AttributeSet? = null) :
    CoordinatorView(context, attributeSet) {
init {
    //設置總的粒子數目
    childTotal = 50
    addItemPerTime = 800
}

override fun newItem(parentWidth: Int, parentHeight: Int): BaseItem {
    return BubbleItem(parentWidth, parentHeight,context)
}

override fun preCreateCount(): Int {
    return 8
}

override fun getIncreaseCountPerRefresh(): Int {
    return 2
}
}

項目地址:項目地址傳送門 點我 點我 點我!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章