自定义view实战(12):安卓粒子线条作用

前言

很久没写代码了,忙工作、忙朋友、人也懒了,最近重新调整自己,对技术仍是要有热心,要热心的话仍是用自定义view做游戏风趣,写完这个粒子线条后边我会更新几个小游戏博文及代码,希望读者喜爱。

这个粒子作用的控件是去年写的,写的很差劲,这几天又重构了一下,仍是丑陋的要命,牵强记录下吧。

需求

首要便是看到博客园的粒子线条布景很有意思,就想模仿一下。中心思维如下:

  • 1、随机呈现点
  • 2、范围内的点连线
  • 3、手指按下,加入点,范围内点向手指移动

作用图

作用图便是丑陋,没得说。

自定义view实战(12):安卓粒子线条效果

代码

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import java.lang.ref.WeakReference
import kotlin.math.pow
import kotlin.math.sqrt
/**
 * 模仿博客粒子线条的view
 *
 * 中心思维简易版
 *
 * 1、随机呈现点
 * 2、范围内的点连线
 * 3、手指按下,加入点,范围内点向手指移动
 *
 * @author silence
 * @date 2022-11-09
 *
 */
class ParticleLinesBgView @JvmOverloads constructor(
    context: Context,
    attributeSet: AttributeSet? = null,
    defStyleAttr: Int = 0
): View(context, attributeSet, defStyleAttr){
    companion object{
        // 屏幕刷新时刻,每秒20次
        const val SCREEN_FLUSH_TIME = 50L
        // 新增点的间隔时刻
        const val POINT_ADD_TIME = 200L
        // 粒子存活时刻
        const val POINT_ALIVE_TIME = 18000L
        // 招引的适宜间隔
        const val ATTRACT_LENGTH = 250f
        // 保持的适宜间隔
        const val PROPER_LENGTH = 150f
        // 粒子被招引每次挨近的间隔
        const val POINT_MOVE_LENGTH = 30f
        // 间隔计算公式
        fun getDistance(x1: Float, y1: Float, x2: Float, y2: Float): Float {
            return sqrt(((x1 - x2).toDouble().pow(2.0)
                    + (y1 - y2).toDouble().pow(2.0)).toFloat())
        }
    }
    // 寄存的粒子
    private val mParticles = ArrayList<Particle>(64)
    // 手指按下方位
    private var mTouchParticle: Particle? = null
    // 处理的handler
    private val mHandler = ParticleHandler(this)
    // 画笔
    private val mPaint = Paint().apply {
        color = Color.LTGRAY
        strokeWidth = 3f
        style = Paint.Style.STROKE
        flags = Paint.ANTI_ALIAS_FLAG
    }
    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        // 经过发送音讯给handler完成间隔添加点
        mHandler.removeMessages(0)
        mHandler.sendEmptyMessageDelayed(0, POINT_ADD_TIME)
    }
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        // 制作点和线
        for (i in 0 until mParticles.size) {
            val point = mParticles[i]
            canvas.drawPoint(point.x, point.y, mPaint)
            // 连线
            for (j in (i + 1) until mParticles.size) {
                val another = mParticles[j]
                val distance = getDistance(point.x, point.y, another.x, another.y)
                if (distance <= PROPER_LENGTH) {
                    canvas.drawLine(point.x, point.y, another.x, another.y, mPaint)
                }
            }
        }
        mTouchParticle?.let {
            // 手指按下点与附近连线
            for(point in mParticles) {
                val distance = getDistance(point.x, point.y, it.x, it.y)
                if (distance <= PROPER_LENGTH) {
                    canvas.drawLine(point.x, point.y, it.x, it.y, mPaint)
                }
            }
            // 招引范围显示
            mPaint.color = Color.BLUE
            canvas.drawCircle(it.x, it.y, PROPER_LENGTH, mPaint)
            mPaint.color = Color.LTGRAY
        }
    }
    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent): Boolean {
        when(event.action) {
            MotionEvent.ACTION_DOWN -> {
                mTouchParticle = Particle(event.x, event.y, 0)
            }
            MotionEvent.ACTION_MOVE -> {
                mTouchParticle!!.x = event.x
                mTouchParticle!!.y = event.y
                invalidate()
            }
            MotionEvent.ACTION_UP -> {
                mTouchParticle = null
            }
        }
        return true
    }
    // 粒子
    class Particle(var x: Float, var y: Float, var counter: Int)
    // kotlin主动编译为Java静态类,控件引证使用弱引证
    class ParticleHandler(view: ParticleLinesBgView): Handler(Looper.getMainLooper()){
        // 控件引证
        private val mRef: WeakReference<ParticleLinesBgView> = WeakReference(view)
        // 粒子呈现操控
        private var mPointCounter = 0
        override fun handleMessage(msg: Message) {
            mRef.get()?.let {view->
                // 新增点
                mPointCounter++
                if (mPointCounter == (POINT_ADD_TIME / SCREEN_FLUSH_TIME).toInt()) {
                    // 随机方位
                    val x = (Math.random() * view.width).toFloat()
                    val y = (Math.random() * view.height).toFloat()
                    view.mParticles.add(Particle(x, y, 0))
                    mPointCounter = 0
                }
                val iterator = view.mParticles.iterator()
                while (iterator.hasNext()) {
                    val point = iterator.next()
                    // 移除失活粒子
                    if (point.counter == (POINT_ALIVE_TIME / SCREEN_FLUSH_TIME).toInt()) {
                        iterator.remove()
                    }
                    // 手指按下时,粒子朝适宜的间隔移动
                    view.mTouchParticle?.let {
                        val distance = getDistance(point.x, point.y, it.x, it.y)
                        if(distance in PROPER_LENGTH..ATTRACT_LENGTH) {
                            // 横向挨近
                            if (point.x < it.x) point.x += POINT_MOVE_LENGTH
                            else point.x -= POINT_MOVE_LENGTH
                            // 纵向挨近
                            if (point.y < it.y) point.y += POINT_MOVE_LENGTH
                            else point.y -= POINT_MOVE_LENGTH
                        }else if(distance <= PROPER_LENGTH) {
                            // 横向远离
                            if (point.x < it.x) point.x -= POINT_MOVE_LENGTH
                            else point.x += POINT_MOVE_LENGTH
                            // 纵向远离
                            if (point.y < it.y) point.y -= POINT_MOVE_LENGTH
                            else point.y += POINT_MOVE_LENGTH
                        }
                    }
                }
                // 循环发送
                view.invalidate()
                view.mHandler.sendEmptyMessageDelayed(0, POINT_ADD_TIME)
            }
        }
    }
}

这儿没写onMeasure,留意下不能用wrap-content,布局的话改个黑色布景就行了。

首要问题

下面简单讲讲吧。

粒子

这儿用了个数据类结构了粒子,用了一个ArrayList来寄存,原本想用linkedHashMap来保存并完成下LRU的,成果连线的时分比较复杂,重构的时分直接删了,后边用了一个counter来操控粒子的存活时刻。

逻辑操控

一开始的时分想的比较复杂,完成来弄得自己头疼,后边觉得何不将逻辑和制作别离,在ondraw里边只进行制作不就行了,逻辑经过handler来更新,实践这样在我看来是对的。

我这用了一个Handler合作嵌套循环发送空音讯,完成守时更新作用,每隔一段时刻更新一下逻辑,Handler内部经过弱引证获得view,并对其间的内容修正,修正完成后,经过invalidate动身线程更新。

新增点

Handler会守时更新,只需要在handleMessage里边添加点就行了,为了操控点呈现的频率,我这又引入了操控变量。

粒子生命周期

handleMessage里边会查看粒子是否失活,失活了就经过iterator去移除,移除数组内内容仍是尽量经过iterator去完成吧,特别是for-eacn循环以及for循环内删除多个时,会出错的!

粒子趋向于手指

手指按下时设置mTouchParticle,移动时更新这个mTouchParticle,手指抬起时对mTouchParticle赋空,这样在handleMessage里边只要在mTouchParticle不为空时稍稍改变下其他粒子的方位,就可以达到趋向作用。

粒子连线

这儿我没有想到什么好办法,便是暴力破解,直接两两计算,并对适宜间隔的粒子进行连线。