實現功能
實現氣泡粒子向上飄動 (視頻轉GIf有點卡)
效果圖
功能
- 粒子的刷新使用:主線程handler 指定頻率刷新,
- 粒子的生命週期分爲存活期 和消亡期 每次生成粒子都從消亡的粒子中獲取 實現粒子緩存
- 粒子預置 可以提前佈置指定數量的粒子
- 粒子自己控制自己的生命週期 避免內存泄漏
代碼詳情
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
}
}
項目地址:項目地址傳送門 點我 點我 點我!!!