網站首頁 編程語言 正文
前言
很久沒寫代碼了,忙工作、忙朋友、人也懶了,最近重新調整自己,對技術還是要有熱情,要熱情的話還是用自定義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不為空時稍稍改變下其他粒子的位置,就可以達到趨向效果。
粒子連線
這里我沒有想到什么好辦法,直接兩兩計算,并對合適距離的粒子進行連線。
原文鏈接:https://blog.csdn.net/lfq88/article/details/128942639
相關推薦
- 2022-10-19 Pandas如何對Categorical類型字段數據統計實戰案例_python
- 2022-12-12 Android?Google?AutoService框架使用詳解_Android
- 2022-12-15 網站壓力測試工具-ab工具apache?bench使用過程_服務器其它
- 2022-11-23 Python?os.listdir與os.walk實現獲取路徑詳解_python
- 2022-09-06 C語言單鏈表遍歷與求和示例解讀_C 語言
- 2022-02-25 C++構造函數的初始化列表詳解_C 語言
- 2022-05-15 實例詳解Python的進程,線程和協程_python
- 2023-03-29 Python之二維正態分布采樣置信橢圓繪制_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支