Skip to content

Commit 17caad7

Browse files
タッチイベントの視覚化処理をスレッドセーフに修正
Co-Authored-By: [email protected] <[email protected]>
1 parent 1904022 commit 17caad7

File tree

1 file changed

+66
-28
lines changed

1 file changed

+66
-28
lines changed

examples/sample_kotlin/src/main/java/io/karte/sample_kotlin/TouchVisualizerView.kt

Lines changed: 66 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ import android.content.Context
55
import android.graphics.Canvas
66
import android.graphics.Color
77
import android.graphics.Paint
8+
import android.os.Handler
9+
import android.os.Looper
810
import android.util.AttributeSet
911
import android.view.MotionEvent
10-
import android.view.View
1112
import android.view.animation.DecelerateInterpolator
1213
import android.widget.FrameLayout
13-
import java.util.concurrent.ConcurrentHashMap
14+
import java.util.Collections
15+
import java.util.concurrent.CopyOnWriteArrayList
1416

1517
/**
1618
* タッチイベントを視覚的に表示するカスタムビュー
@@ -22,15 +24,21 @@ class TouchVisualizerView @JvmOverloads constructor(
2224
defStyleAttr: Int = 0
2325
) : FrameLayout(context, attrs, defStyleAttr) {
2426

25-
// タッチポイントを保持するマップ
26-
private val touchPoints = ConcurrentHashMap<Int, TouchPoint>()
27+
// タッチポイントを保持するマップ(スレッドセーフ)
28+
private val touchPoints = Collections.synchronizedMap(HashMap<Int, TouchPoint>())
29+
30+
// 描画用のタッチポイントリスト(スレッドセーフ)
31+
private val drawingPoints = CopyOnWriteArrayList<TouchPoint>()
32+
33+
// UIスレッドでの処理用ハンドラ
34+
private val mainHandler = Handler(Looper.getMainLooper())
2735

2836
// タッチポイントの描画用ペイント
2937
private val paint = Paint().apply {
3038
isAntiAlias = true
3139
color = Color.BLUE
3240
alpha = 128
33-
style = Paint.STYLE.FILL
41+
style = Paint.Style.FILL
3442
}
3543

3644
init {
@@ -50,37 +58,55 @@ class TouchVisualizerView @JvmOverloads constructor(
5058

5159
when (event.actionMasked) {
5260
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> {
53-
// 新しいタッチポイントを追加
54-
val touchPoint = TouchPoint(x, y)
55-
touchPoints[pointerId] = touchPoint
56-
touchPoint.startAnimation()
61+
// 新しいタッチポイントを追加(UIスレッドで実行)
62+
mainHandler.post {
63+
val touchPoint = TouchPoint(x, y)
64+
synchronized(touchPoints) {
65+
touchPoints[pointerId] = touchPoint
66+
// 描画用リストにも追加
67+
drawingPoints.add(touchPoint)
68+
}
69+
touchPoint.startAnimation()
70+
}
5771
}
5872
MotionEvent.ACTION_MOVE -> {
59-
// 既存のポインターすべての位置を更新
60-
for (i in 0 until event.pointerCount) {
61-
val id = event.getPointerId(i)
62-
touchPoints[id]?.apply {
63-
this.x = event.getX(i)
64-
this.y = event.getY(i)
73+
// 既存のポインターすべての位置を更新(UIスレッドで実行)
74+
mainHandler.post {
75+
synchronized(touchPoints) {
76+
for (i in 0 until event.pointerCount) {
77+
val id = event.getPointerId(i)
78+
touchPoints[id]?.apply {
79+
this.x = event.getX(i)
80+
this.y = event.getY(i)
81+
}
82+
}
6583
}
84+
// 再描画を要求
85+
postInvalidateOnAnimation()
6686
}
6787
}
6888
MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP, MotionEvent.ACTION_CANCEL -> {
69-
// タッチポイントをフェードアウト
70-
touchPoints[pointerId]?.startFadeOut()
89+
// タッチポイントをフェードアウト(UIスレッドで実行)
90+
mainHandler.post {
91+
synchronized(touchPoints) {
92+
touchPoints[pointerId]?.startFadeOut()
93+
}
94+
}
7195
}
7296
}
7397

74-
invalidate()
98+
// 再描画を要求
99+
postInvalidateOnAnimation()
100+
75101
// 子ビューにイベントを渡す
76102
return super.dispatchTouchEvent(event)
77103
}
78104

79105
override fun onDraw(canvas: Canvas) {
80106
super.onDraw(canvas)
81107

82-
// すべてのタッチポイントを描画
83-
for (touchPoint in touchPoints.values) {
108+
// スレッドセーフなリストからすべてのタッチポイントを描画
109+
for (touchPoint in drawingPoints) {
84110
paint.alpha = touchPoint.alpha
85111
canvas.drawCircle(touchPoint.x, touchPoint.y, touchPoint.radius, paint)
86112
}
@@ -90,10 +116,10 @@ class TouchVisualizerView @JvmOverloads constructor(
90116
* タッチポイントを表すデータクラス
91117
*/
92118
private inner class TouchPoint(
93-
var x: Float,
94-
var y: Float,
95-
var radius: Float = 0f,
96-
var alpha: Int = 0
119+
@Volatile var x: Float,
120+
@Volatile var y: Float,
121+
@Volatile var radius: Float = 0f,
122+
@Volatile var alpha: Int = 0
97123
) {
98124
private var animator: ValueAnimator? = null
99125
private var fadeOutAnimator: ValueAnimator? = null
@@ -113,7 +139,8 @@ class TouchVisualizerView @JvmOverloads constructor(
113139
addUpdateListener { animation ->
114140
radius = animation.animatedValue as Float
115141
alpha = 128
116-
invalidate()
142+
// UIスレッドで再描画を要求
143+
postInvalidateOnAnimation()
117144
}
118145
start()
119146
}
@@ -127,10 +154,21 @@ class TouchVisualizerView @JvmOverloads constructor(
127154
duration = 500
128155
addUpdateListener { animation ->
129156
alpha = animation.animatedValue as Int
130-
invalidate()
157+
// UIスレッドで再描画を要求
158+
postInvalidateOnAnimation()
159+
131160
if (alpha == 0) {
132-
// アニメーション終了時にマップから削除
133-
touchPoints.values.removeIf { it === this@TouchPoint }
161+
// アニメーション終了時にマップから削除(UIスレッドで実行)
162+
mainHandler.post {
163+
synchronized(touchPoints) {
164+
// IDでの削除
165+
val idToRemove = touchPoints.entries.find { it.value === this@TouchPoint }?.key
166+
idToRemove?.let { touchPoints.remove(it) }
167+
168+
// 描画用リストからも削除
169+
drawingPoints.remove(this@TouchPoint)
170+
}
171+
}
134172
}
135173
}
136174
start()

0 commit comments

Comments
 (0)