-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathSwivelCheckView.kt
118 lines (95 loc) · 4.04 KB
/
SwivelCheckView.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package com.example
import android.content.Context
import android.graphics.Camera
import android.support.v4.view.animation.FastOutSlowInInterpolator
import android.util.AttributeSet
import android.view.View
import android.view.animation.Animation
import android.view.animation.Transformation
import android.widget.Checkable
import android.widget.ImageView
import android.widget.ViewFlipper
import com.example.SwivelAnimation.Companion.toChecked
import com.example.SwivelAnimation.Companion.toNotChecked
import kotlinx.android.synthetic.main.merge_swivel_check.view.*
class SwivelCheckView constructor(context: Context, attrs: AttributeSet)
: ViewFlipper(context, attrs), Checkable {
private val calculateCenterX = { width * 0.5f }
private val updateDisplayedSide: (DisplaySide) -> Unit = { setDisplaySide(it) }
private val toCheckedAnimation = toChecked(calculateCenterX, updateDisplayedSide)
private val toNotCheckedAnimation = toNotChecked(calculateCenterX, updateDisplayedSide)
private var isChecked: Boolean = false
init {
View.inflate(context, R.layout.merge_swivel_check, this)
}
fun setChecked(targetState: Boolean, shouldAnimate: Boolean = true) {
if (shouldAnimate) {
setChecked(targetState)
} else {
setDisplaySide(if (targetState) DisplaySide.BACK else DisplaySide.FRONT)
isChecked = targetState
}
}
private fun setDisplaySide(displaySide: DisplaySide) {
if (displayedChild != displaySide.viewFlipperChildIndex) {
displayedChild = displaySide.viewFlipperChildIndex
}
}
override fun setChecked(targetState: Boolean) {
if (targetState == isChecked) {
return
}
if (targetState) {
isChecked = true
startAnimation(toCheckedAnimation)
} else {
isChecked = false
startAnimation(toNotCheckedAnimation)
}
}
override fun isChecked() = isChecked
override fun toggle() = setChecked(!isChecked)
fun imageView(): ImageView = imageView
}
private class SwivelAnimation(
private val rotation: ClosedFloatingPointRange<Float>,
private val calculateCenterX: () -> Float,
private val updateDisplayedSide: (DisplaySide) -> Unit
) : Animation() {
private val camera = Camera()
override fun applyTransformation(interpolatedTime: Float, transformation: Transformation) {
val degrees = rotation.start + (rotation.endInclusive - rotation.start) * interpolatedTime
val matrix = transformation.matrix
val centerX = calculateCenterX()
camera.save()
camera.rotateY(degrees)
camera.getMatrix(matrix)
camera.restore()
matrix.preTranslate(-centerX, 0f)
matrix.postTranslate(centerX, 0f)
val displaySide = if (degrees >= 90) DisplaySide.BACK else DisplaySide.FRONT
updateDisplayedSide(displaySide)
if (displaySide == DisplaySide.BACK) {
matrix.preScale(-1f, 1f, centerX, 0f)
}
}
companion object {
private val ROTATION_TO_CHECKED = 0f..180f
private val ROTATION_TO_NOT_CHECKED = 180f..0f
private const val DURATION_MS = 320L
fun toChecked(calculateCenterX: () -> Float, updateDisplayedSide: (DisplaySide) -> Unit) =
createAnimation(ROTATION_TO_CHECKED, calculateCenterX, updateDisplayedSide)
fun toNotChecked(calculateCenterX: () -> Float, updateDisplayedSide: (DisplaySide) -> Unit) =
createAnimation(ROTATION_TO_NOT_CHECKED, calculateCenterX, updateDisplayedSide)
private fun createAnimation(rotation: ClosedFloatingPointRange<Float>, calculateCenterX: () -> Float, updateDisplayedSide: (DisplaySide) -> Unit): Animation {
return SwivelAnimation(rotation, calculateCenterX, updateDisplayedSide).apply {
duration = DURATION_MS
interpolator = FastOutSlowInInterpolator()
}
}
}
}
private enum class DisplaySide(val viewFlipperChildIndex: Int) {
FRONT(0),
BACK(1)
}