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

6 篇文章 0 订阅

序言

当前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<BaseItem>()
//要绘制的所有View
private var drawItems = ArrayList<BaseItem>()

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<BaseItem>()
//要绘制的所有View
private var drawItems = ArrayList<BaseItem>()

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)
}
}

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android中的SurfaceView是一个可以在自己的线程中绘制UI的控件,通常用于实现游戏、视频播放等需要高性能绘制的场景。相较于普通ViewSurfaceView的绘制是在一个独立的缓冲区中进行的,这个缓冲区可以在后台线程中进行绘制,然后在主线程中进行显示,这样可以确保UI的流畅性和性能。 使用SurfaceView的步骤如下: 1.定义SurfaceView 在布局文件中定义SurfaceView,例如: ``` <android.view.SurfaceView android:id="@+id/surface_view" android:layout_width="match_parent" android:layout_height="match_parent" /> ``` 2.获取SurfaceHolder 在Activity或者Fragment中获取SurfaceView的SurfaceHolder对象,用于控制SurfaceView的绘制: ``` SurfaceView surfaceView = findViewById(R.id.surface_view); SurfaceHolder holder = surfaceView.getHolder(); ``` 3.实现SurfaceHolder.Callback接口 实现SurfaceHolder.Callback接口,它包含三个方法:surfaceCreated、surfaceChanged和surfaceDestroyed。其中,surfaceCreated在SurfaceView创建时调用,surfaceChanged在SurfaceView绘制区域大小发生变化时调用,surfaceDestroyed在SurfaceView销毁时调用。在这些方法中,我们可以进行SurfaceView的初始化、绘制、销毁等操作。 ``` holder.addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { // 初始化SurfaceView } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // SurfaceView绘制区域大小发生变化 } @Override public void surfaceDestroyed(SurfaceHolder holder) { // 销毁SurfaceView } }); ``` 4.在后台线程中进行绘制 在surfaceCreated方法中,我们可以启动一个后台线程,用于进行SurfaceView的绘制。在这个线程中,我们可以使用Canvas对象进行绘制。在绘制完成后,我们需要调用SurfaceHolder的lockCanvas方法获取到Canvas对象,然后进行绘制,最后调用SurfaceHolder的unlockCanvasAndPost方法提交绘制结果。 ``` Thread drawThread = new Thread(new Runnable() { @Override public void run() { while (true) { Canvas canvas = holder.lockCanvas(); // 绘制操作 holder.unlockCanvasAndPost(canvas); } } }); drawThread.start(); ``` 5.在主线程中更新UI 在surfaceChanged和surfaceDestroyed方法中,我们需要在主线程中更新UI,例如修改控件的可见性、修改控件的属性等操作。可以使用runOnUiThread方法或者Handler来实现。 ``` runOnUiThread(new Runnable() { @Override public void run() { // 在主线程中更新UI } }); ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值