序言
当前Android 主流的系统中粒子的实现方式大致可以分为两类三种
1. 继承普通的View,主线程刷新绘制
2. 使用surfaceView,子线程刷新绘制
3. 使用surfaceView+openGl,子线程刷新绘制
因为第三种方式不是很常用,这里使用前两种方式实现粒子
效果图(视频转GIf有点卡)
实现功能
1. 可以自定义粒子刷新的频率,每次刷新的数量
2. 粒子自己维护自己的生命周期
3. 可以预先自己定义的粒子数量加载粒子 避免一加载就是满屏粒子的尴尬
4. 复用消亡粒子
5. 实现粒子的高度自定义 避免过度封装
理论概述
两种实现方式 对粒子的处理大致相同 不同的是绘制的位置区别
这里以第一种方式为例 画出粒子的UML框架:
如上图所示
ParticleView继承Runnable 使用handler定时刷新,每次刷新进行以下一个步骤
1. 新增粒子 从cacheItems集合中获取缓存粒子 如果没有则调用adapter进行创建
2. 调用transForms 移动粒子并将消亡粒子移除绘制集合中
3. 调用drawItems方法绘制所有的粒子
使用
这里因为是高度资质粒子,所以如果我们想要绘制粒子需要首先继承BaseItem接口定义自己的粒子类:
然后实现适配器ParticleAdapter
如:
particle.setAdapter(object : ParticleAdapter() {
override fun newItem(parentWidth: Int, parentHeight: Int): BaseItem {
//返回自己实现的粒子
return BubbleItem(parentWidth, parentHeight, this@MainActivity)
}
override fun preCreateCount(): Int {
return 50
}
})
particle.setIncreaseParticleInterval(100)
particle.setRenderTime(16)
particle.setIncreaseParticleCount(1)
我这里实现的粒子如下: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 主线程刷新绘制
普通的View绘制就是 使用handler主线程绘制刷新实现如下:
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()
//要绘制的所有View
private var drawItems = ArrayList()
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
}
/**
* 设置大小则按照设置的大小计算 否则按照屏幕的宽高来计算
*/
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,子线程刷新绘制
实现如下:
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()
//要绘制的所有View
private var drawItems = ArrayList()
init {
surfaceHolder.setKeepScreenOn(true)
surfaceHolder.addCallback(this)
isFocusable = true
isFocusableInTouchMode = true
setZOrderOnTop(true)
//设置背景为透明色
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)
}
}
项目地址:项目地址传送门 点我 点我 点我!!!