Skip to content

Commit 90405e7

Browse files
committed
migrate from View to SurfaceView
optimizations
1 parent 8126367 commit 90405e7

File tree

6 files changed

+150
-56
lines changed

6 files changed

+150
-56
lines changed

app/build.gradle

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
apply plugin: 'com.android.library'
1+
apply plugin: 'com.android.application'
22

33
apply plugin: 'kotlin-android'
44

5-
apply plugin: 'kotlin-android-extensions'
6-
75
android {
86
compileSdkVersion 28
97
defaultConfig {

app/src/main/AndroidManifest.xml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3-
package="me.ibrahimsn.particles">
3+
xmlns:tools="http://schemas.android.com/tools" package="me.ibrahimsn.particles">
44

55
<application
66
android:allowBackup="false"
77
android:icon="@mipmap/ic_launcher"
88
android:label="@string/app_name"
99
android:roundIcon="@mipmap/ic_launcher_round"
1010
android:supportsRtl="true"
11-
android:theme="@style/AppTheme">
11+
android:theme="@style/AppTheme"
12+
tools:ignore="GoogleAppIndexingWarning">
1213
<activity android:name=".MainActivity">
1314
<intent-filter>
1415
<action android:name="android.intent.action.MAIN"/>

app/src/main/java/me/ibrahimsn/particles/MainActivity.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,26 @@ package me.ibrahimsn.particles
22

33
import android.support.v7.app.AppCompatActivity
44
import android.os.Bundle
5+
import me.ibrahimsn.particle.ParticleView
56

67
class MainActivity : AppCompatActivity() {
78

9+
private lateinit var particleView: ParticleView
10+
811
override fun onCreate(savedInstanceState: Bundle?) {
912
super.onCreate(savedInstanceState)
1013
setContentView(R.layout.activity_main)
14+
15+
particleView = findViewById(R.id.particleView)
16+
}
17+
18+
override fun onResume() {
19+
super.onResume()
20+
particleView.resume()
21+
}
22+
23+
override fun onPause() {
24+
super.onPause()
25+
particleView.pause()
1126
}
1227
}

app/src/main/res/layout/activity_main.xml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,18 @@
44
xmlns:tools="http://schemas.android.com/tools"
55
android:layout_width="match_parent"
66
android:layout_height="match_parent"
7+
xmlns:app="http://schemas.android.com/apk/res-auto"
8+
android:background="@android:color/black"
79
tools:context=".MainActivity">
810

911
<me.ibrahimsn.particle.ParticleView
12+
android:id="@+id/particleView"
1013
android:layout_width="match_parent"
11-
android:layout_height="match_parent"/>
14+
android:layout_height="match_parent"
15+
app:particleCount="25"
16+
app:particleColor="@android:color/white"
17+
app:backgroundColor="@android:color/holo_red_light"
18+
app:minParticleRadius="3"
19+
app:maxParticleRadius="10"/>
1220

1321
</FrameLayout>

particle/src/main/java/me/ibrahimsn/particle/ParticleView.kt

Lines changed: 121 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,25 @@
11
package me.ibrahimsn.particle
22

33
import 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.*
85
import android.util.AttributeSet
9-
import android.view.View
6+
import android.view.SurfaceHolder
7+
import android.view.SurfaceView
108
import android.view.ViewTreeObserver
119
import kotlin.random.Random
1210

13-
class ParticleView : View {
11+
class ParticleView : SurfaceView, SurfaceHolder.Callback {
1412

15-
private lateinit var particles: Array<Particle>
13+
private lateinit var particles: Array<Particle?>
14+
15+
private var surfaceViewThread: SurfaceViewThread? = null
1616

1717
private var count = 20
1818
private var minRadius = 5
1919
private var maxRadius = 10
20+
private var hasSurface: Boolean = false
2021

22+
private var background = Color.BLACK
2123
private var color = Color.WHITE
2224
private val path = Path()
2325

@@ -28,75 +30,144 @@ class ParticleView : View {
2830
}
2931

3032
constructor(context: Context) : super(context)
31-
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
33+
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
3234
val a = context.obtainStyledAttributes(attrs, R.styleable.ParticleView, 0, 0)
3335

3436
count = a.getInt(R.styleable.ParticleView_particleCount, count)
3537
minRadius = a.getInt(R.styleable.ParticleView_minParticleRadius, minRadius)
3638
maxRadius = a.getInt(R.styleable.ParticleView_maxParticleRadius, maxRadius)
3739
color = a.getColor(R.styleable.ParticleView_particleColor, color)
40+
background = a.getColor(R.styleable.ParticleView_backgroundColor, background)
41+
a.recycle()
3842

43+
particles = arrayOfNulls(count)
3944
paint.color = color
4045

41-
if (count > 50)
42-
count = 50
46+
if (count > 50) count = 50
47+
if (minRadius <= 0) minRadius = 1
48+
if (maxRadius <= minRadius) maxRadius = minRadius + 1
49+
50+
if (holder != null)
51+
holder.addCallback(this)
4352

44-
if (minRadius <= 0)
45-
minRadius = 1
53+
hasSurface = false
54+
}
4655

47-
if (maxRadius <= minRadius)
48-
maxRadius = minRadius + 1
56+
override fun surfaceCreated(holder: SurfaceHolder) {
57+
hasSurface = true
4958

50-
a.recycle()
59+
if (surfaceViewThread == null)
60+
surfaceViewThread = SurfaceViewThread()
61+
62+
surfaceViewThread!!.start()
5163
}
5264

53-
init {
54-
viewTreeObserver.addOnPreDrawListener(object: ViewTreeObserver.OnPreDrawListener {
55-
override fun onPreDraw(): Boolean {
56-
if (viewTreeObserver.isAlive)
57-
viewTreeObserver.removeOnPreDrawListener(this)
65+
fun resume() {
66+
if (surfaceViewThread == null) {
67+
surfaceViewThread = SurfaceViewThread()
5868

59-
val array = arrayOfNulls<Particle>(count)
69+
if (hasSurface)
70+
surfaceViewThread!!.start()
71+
}
72+
}
6073

61-
for (i in 0 until count)
62-
array[i] = Particle(
63-
Random.nextInt(minRadius, maxRadius).toFloat(),
64-
Random.nextInt(0, width).toFloat(),
65-
Random.nextInt(0, height).toFloat(),
66-
Random.nextInt(-2, 2),
67-
Random.nextInt(-2, 2),
68-
Random.nextInt(150, 255))
74+
fun pause() {
75+
if (surfaceViewThread != null) {
76+
surfaceViewThread!!.requestExitAndWait()
77+
surfaceViewThread = null
78+
}
79+
}
6980

70-
particles = array as Array<Particle>
81+
override fun surfaceDestroyed(holder: SurfaceHolder) {
82+
hasSurface = false
7183

72-
return true
73-
}
74-
})
84+
if (surfaceViewThread != null) {
85+
surfaceViewThread!!.requestExitAndWait()
86+
surfaceViewThread = null
87+
}
7588
}
7689

77-
override fun onDraw(canvas: Canvas) {
78-
for (i in 0 until count) {
79-
particles[i].x += particles[i].vx
80-
particles[i].y += particles[i].vy
90+
override fun surfaceChanged(holder: SurfaceHolder, format: Int, w: Int, h: Int) {
8191

82-
if (particles[i].x < 0)
83-
particles[i].x = width.toFloat()
84-
else if (particles[i].x > width)
85-
particles[i].x = 0F
92+
}
93+
94+
private inner class SurfaceViewThread : Thread() {
95+
96+
private var running: Boolean = true
8697

87-
if (particles[i].y < 0)
88-
particles[i].y = height.toFloat()
89-
else if (particles[i].y > height)
90-
particles[i].y = 0F
98+
init {
99+
running = true
91100

92-
for(j in 0 until count)
93-
linkParticles(canvas, particles[i], particles[j])
101+
viewTreeObserver.addOnPreDrawListener(object: ViewTreeObserver.OnPreDrawListener {
102+
override fun onPreDraw(): Boolean {
103+
if (viewTreeObserver.isAlive)
104+
viewTreeObserver.removeOnPreDrawListener(this)
94105

95-
paint.alpha = particles[i].alpha
96-
canvas.drawCircle(particles[i].x, particles[i].y, particles[i].radius, paint)
106+
for (i in 0 until count)
107+
particles[i] = Particle(
108+
Random.nextInt(minRadius, maxRadius).toFloat(),
109+
Random.nextInt(0, width).toFloat(),
110+
Random.nextInt(0, height).toFloat(),
111+
Random.nextInt(-2, 2),
112+
Random.nextInt(-2, 2),
113+
Random.nextInt(150, 255))
114+
115+
return true
116+
}
117+
})
97118
}
98119

99-
postInvalidateDelayed(25)
120+
override fun run() {
121+
while (running) {
122+
var canvas: Canvas? = null
123+
124+
try {
125+
canvas = holder.lockCanvas()
126+
127+
synchronized (holder) {
128+
canvas.drawColor(background)
129+
130+
for (i in 0 until count) {
131+
particles[i]!!.x += particles[i]!!.vx
132+
particles[i]!!.y += particles[i]!!.vy
133+
134+
if (particles[i]!!.x < 0)
135+
particles[i]!!.x = width.toFloat()
136+
else if (particles[i]!!.x > width)
137+
particles[i]!!.x = 0F
138+
139+
if (particles[i]!!.y < 0)
140+
particles[i]!!.y = height.toFloat()
141+
else if (particles[i]!!.y > height)
142+
particles[i]!!.y = 0F
143+
144+
for (j in 0 until count)
145+
linkParticles(canvas, particles[i]!!, particles[j]!!)
146+
147+
paint.alpha = particles[i]!!.alpha
148+
canvas.drawCircle(particles[i]!!.x, particles[i]!!.y, particles[i]!!.radius, paint)
149+
}
150+
}
151+
152+
} catch (e: Exception) {
153+
e.printStackTrace()
154+
} finally {
155+
if (canvas != null) {
156+
holder.unlockCanvasAndPost(canvas)
157+
}
158+
}
159+
}
160+
}
161+
162+
fun requestExitAndWait() {
163+
running = false
164+
165+
try {
166+
join()
167+
} catch (ignored: InterruptedException) {
168+
169+
}
170+
}
100171
}
101172

102173
private fun linkParticles(canvas: Canvas, p1: Particle, p2: Particle) {

particle/src/main/res/values/attrs.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<resources>
33
<declare-styleable name="ParticleView">
4+
<attr name="backgroundColor" format="color" />
45
<attr name="particleColor" format="color" />
56
<attr name="particleCount" format="integer" />
67
<attr name="minParticleRadius" format="integer" />

0 commit comments

Comments
 (0)