Android 高度自定义的粒子框架(支持普通View和surfaceView)

序言

当前Android 主流的系统中粒子的实现方式大致可以分为两类三种

1. 继承普通的View,主线程刷新绘制

2. 使用surfaceView,子线程刷新绘制

3. 使用surfaceView+openGl,子线程刷新绘制

因为第三种方式不是很常用,这里使用前两种方式实现粒子

效果图(视频转GIf有点卡)

image

实现功能

1. 可以自定义粒子刷新的频率,每次刷新的数量

2. 粒子自己维护自己的生命周期

3. 可以预先自己定义的粒子数量加载粒子 避免一加载就是满屏粒子的尴尬

4. 复用消亡粒子

5. 实现粒子的高度自定义 避免过度封装

理论概述

两种实现方式 对粒子的处理大致相同 不同的是绘制的位置区别

这里以第一种方式为例 画出粒子的UML框架:

image

如上图所示

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)

    }

}

项目地址:项目地址传送门 点我 点我 点我!!!

你可能感兴趣的:(Android 高度自定义的粒子框架(支持普通View和surfaceView))