实现功能
实现气泡粒子向上飘动 (视频转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
}
}
项目地址:项目地址传送门 点我 点我 点我!!!