11package me.ibrahimsn.particle
22
33import android.content.Context
4- import android.graphics.Canvas
5- import android.graphics.Color
6- import android.graphics.Paint
7- import android.graphics.Path
4+ import android.graphics.*
85import android.util.AttributeSet
96import android.view.SurfaceHolder
107import android.view.SurfaceView
8+ import androidx.annotation.ColorInt
9+ import androidx.annotation.Dimension
1110import kotlin.math.min
1211import kotlin.math.sqrt
1312import kotlin.random.Random
1413
15- class ParticleView : SurfaceView , SurfaceHolder .Callback {
14+ class ParticleView @JvmOverloads constructor(
15+ context : Context ,
16+ attrs : AttributeSet ,
17+ defStyleAttr : Int = R .attr.ParticleViewStyle
18+ ) : SurfaceView(context, attrs, defStyleAttr), SurfaceHolder.Callback {
1619
1720 private val particles = mutableListOf<Particle >()
1821 private var surfaceViewThread: SurfaceViewThread ? = null
19-
20- private var particleCount = 20
21- private var minRadius = 5
22- private var maxRadius = 10
23- private var isLinesEnabled = true
2422 private var hasSurface: Boolean = false
2523 private var hasSetup = false
2624
27- private var background = Color .BLACK
28- private var colorParticles = Color .WHITE
29- private var colorLines = Color .WHITE
3025 private val path = Path ()
3126
27+ // Attribute Defaults
28+ private var _particleCount = 20
29+
30+ @Dimension
31+ private var _particleMinRadius = 5
32+
33+ @Dimension
34+ private var _particleMaxRadius = 10
35+
36+ @ColorInt
37+ private var _particlesBackgroundColor = Color .BLACK
38+
39+ @ColorInt
40+ private var _particleColor = Color .WHITE
41+
42+ @ColorInt
43+ private var _particleLineColor = Color .WHITE
44+
45+ private var _particleLinesEnabled = true
46+
47+ // Core Attributes
48+ var particleCount: Int
49+ get() = _particleCount
50+ set(value) {
51+ _particleCount = when {
52+ value > 50 -> 50
53+ value < 0 -> 0
54+ else -> value
55+ }
56+ }
57+
58+ var particleMinRadius: Int
59+ @Dimension get() = _particleMinRadius
60+ set(@Dimension value) {
61+ _particleMinRadius = when {
62+ value <= 0 -> 1
63+ value >= particleMaxRadius -> 1
64+ else -> value
65+ }
66+ }
67+
68+ var particleMaxRadius: Int
69+ @Dimension get() = _particleMaxRadius
70+ set(@Dimension value) {
71+ _particleMaxRadius = when {
72+ value <= particleMinRadius -> particleMinRadius + 1
73+ else -> value
74+ }
75+ }
76+
77+ var particlesBackgroundColor: Int
78+ @ColorInt get() = _particlesBackgroundColor
79+ set(@ColorInt value) {
80+ _particlesBackgroundColor = value
81+ }
82+
83+ var particleColor: Int
84+ @ColorInt get() = _particleColor
85+ set(@ColorInt value) {
86+ _particleColor = value
87+ paintParticles.color = value
88+ }
89+
90+ var particleLineColor: Int
91+ @ColorInt get() = _particleLineColor
92+ set(@ColorInt value) {
93+ _particleLineColor = value
94+ paintLines.color = value
95+ }
96+
97+ var particleLinesEnabled: Boolean
98+ get() = _particleLinesEnabled
99+ set(value) {
100+ _particleLinesEnabled = value
101+ }
102+
103+ // Paints
32104 private val paintParticles: Paint = Paint ().apply {
33105 isAntiAlias = true
34106 style = Paint .Style .FILL
@@ -37,35 +109,64 @@ class ParticleView : SurfaceView, SurfaceHolder.Callback {
37109
38110 private val paintLines: Paint = Paint ().apply {
39111 isAntiAlias = true
40- style = Paint .Style .STROKE
112+ style = Paint .Style .FILL_AND_STROKE
41113 strokeWidth = 2F
42114 }
43115
44- constructor (context: Context ) : super (context)
45- constructor (context: Context , attrs: AttributeSet ) : super (context, attrs) {
46- val a = context.obtainStyledAttributes(attrs, R .styleable.ParticleView , 0 , 0 )
47-
48- isLinesEnabled = a.getBoolean(R .styleable.ParticleView_lines , isLinesEnabled)
49- particleCount = a.getInt(R .styleable.ParticleView_particleCount , particleCount)
50- minRadius = a.getInt(R .styleable.ParticleView_minParticleRadius , minRadius)
51- maxRadius = a.getInt(R .styleable.ParticleView_maxParticleRadius , maxRadius)
52- colorParticles = a.getColor(R .styleable.ParticleView_particleColor , colorParticles)
53- colorLines = a.getColor(R .styleable.ParticleView_linesColor , colorLines)
54- background = a.getColor(R .styleable.ParticleView_backgroundColor , background)
55- a.recycle()
56-
57- paintParticles.color = colorParticles
58- paintLines.color = colorLines
59-
60- if (particleCount > 50 ) particleCount = 50
61- if (minRadius <= 0 ) minRadius = 1
62- if (maxRadius <= minRadius) maxRadius = minRadius + 1
116+ init {
117+ obtainStyledAttributes(attrs, defStyleAttr)
118+ if (holder != null ) holder.addCallback(this )
119+ hasSurface = false
120+ }
63121
64- if (holder != null ) {
65- holder.addCallback(this )
122+ private fun obtainStyledAttributes (attrs : AttributeSet , defStyleAttr : Int ) {
123+ val typedArray = context.obtainStyledAttributes(
124+ attrs,
125+ R .styleable.ParticleView ,
126+ defStyleAttr,
127+ 0
128+ )
129+
130+ try {
131+ particleCount = typedArray.getInt(
132+ R .styleable.ParticleView_particleCount ,
133+ particleCount
134+ )
135+
136+ particleMinRadius = typedArray.getInt(
137+ R .styleable.ParticleView_particleMinRadius ,
138+ particleMinRadius
139+ )
140+
141+ particleMaxRadius = typedArray.getInt(
142+ R .styleable.ParticleView_particleMaxRadius ,
143+ particleMaxRadius
144+ )
145+
146+ particlesBackgroundColor = typedArray.getColor(
147+ R .styleable.ParticleView_particlesBackgroundColor ,
148+ particlesBackgroundColor
149+ )
150+
151+ particleColor = typedArray.getColor(
152+ R .styleable.ParticleView_particleColor ,
153+ particleColor
154+ )
155+
156+ particleLineColor = typedArray.getColor(
157+ R .styleable.ParticleView_particleLineColor ,
158+ particleLineColor
159+ )
160+
161+ particleLinesEnabled = typedArray.getBoolean(
162+ R .styleable.ParticleView_particleLinesEnabled ,
163+ particleLinesEnabled
164+ )
165+ } catch (e: Exception ) {
166+ e.printStackTrace()
167+ } finally {
168+ typedArray.recycle()
66169 }
67-
68- hasSurface = false
69170 }
70171
71172 override fun surfaceCreated (holder : SurfaceHolder ) {
@@ -100,7 +201,26 @@ class ParticleView : SurfaceView, SurfaceHolder.Callback {
100201 }
101202
102203 override fun surfaceChanged (holder : SurfaceHolder , format : Int , w : Int , h : Int ) {
103- // Ignore
204+ // ignored
205+ }
206+
207+ private fun setupParticles () {
208+ if (! hasSetup) {
209+ hasSetup = true
210+ particles.clear()
211+ for (i in 0 until particleCount) {
212+ particles.add(
213+ Particle (
214+ Random .nextInt(particleMinRadius, particleMaxRadius).toFloat(),
215+ Random .nextInt(0 , width).toFloat(),
216+ Random .nextInt(0 , height).toFloat(),
217+ Random .nextInt(- 2 , 2 ),
218+ Random .nextInt(- 2 , 2 ),
219+ Random .nextInt(150 , 255 )
220+ )
221+ )
222+ }
223+ }
104224 }
105225
106226 private inner class SurfaceViewThread : Thread () {
@@ -109,28 +229,15 @@ class ParticleView : SurfaceView, SurfaceHolder.Callback {
109229 private var canvas: Canvas ? = null
110230
111231 override fun run () {
112- if (! hasSetup) {
113- hasSetup = true
114- for (i in 0 until particleCount) {
115- particles.add(
116- Particle (
117- Random .nextInt(minRadius, maxRadius).toFloat(),
118- Random .nextInt(0 , width).toFloat(),
119- Random .nextInt(0 , height).toFloat(),
120- Random .nextInt(- 2 , 2 ),
121- Random .nextInt(- 2 , 2 ),
122- Random .nextInt(150 , 255 )
123- )
124- )
125- }
126- }
232+ setupParticles()
127233
128234 while (running) {
129235 try {
130236 canvas = holder.lockCanvas()
131237
132238 synchronized (holder) {
133- canvas?.drawColor(background)
239+ // Clear screen every frame
240+ canvas?.drawColor(particlesBackgroundColor, PorterDuff .Mode .CLEAR )
134241
135242 for (i in 0 until particleCount) {
136243 particles[i].x + = particles[i].vx
@@ -149,7 +256,7 @@ class ParticleView : SurfaceView, SurfaceHolder.Callback {
149256 }
150257
151258 canvas?.let {
152- if (isLinesEnabled ) {
259+ if (particleLinesEnabled ) {
153260 for (j in 0 until particleCount) {
154261 if (i != j) {
155262 linkParticles(it, particles[i], particles[j])
@@ -178,8 +285,8 @@ class ParticleView : SurfaceView, SurfaceHolder.Callback {
178285
179286 try {
180287 join()
181- } catch (ignored : InterruptedException ) {
182-
288+ } catch (e : InterruptedException ) {
289+ // ignored
183290 }
184291 }
185292 }
0 commit comments