自定义view实战(12):安卓粒子线条作用
前言
很久没写代码了,忙工作、忙朋友、人也懒了,最近重新调整自己,对技术仍是要有热心,要热心的话仍是用自定义view做游戏风趣,写完这个粒子线条后边我会更新几个小游戏博文及代码,希望读者喜爱。
这个粒子作用的控件是去年写的,写的很差劲,这几天又重构了一下,仍是丑陋的要命,牵强记录下吧。
需求
首要便是看到博客园的粒子线条布景很有意思,就想模仿一下。中心思维如下:
- 1、随机呈现点
- 2、范围内的点连线
- 3、手指按下,加入点,范围内点向手指移动
作用图
作用图便是丑陋,没得说。
代码
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不为空时稍稍改变下其他粒子的方位,就可以达到趋向作用。
粒子连线
这儿我没有想到什么好办法,便是暴力破解,直接两两计算,并对适宜间隔的粒子进行连线。