Skip to content

Commit 1a7be6e

Browse files
Add elastic scale Tutorial for Buttons and FAB
1 parent 8b5cce9 commit 1a7be6e

File tree

6 files changed

+374
-12
lines changed

6 files changed

+374
-12
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ Tutorials about Shared Transitions from Activity to Activity, and from Fragment
4141
| ----------|----------------| --------|
4242
| <img src="./screenshots/transition_chapter1_2.gif"/> | <img src="./screenshots/transition_chapter1_4.gif"/> | <img src="./screenshots/transition_chapter2_5_1.gif"/> |
4343

44-
| Ch2-5/1 Nav Components | Ch2-5/2 Nav Components | Ch2-3 Material Transitions |
44+
| Ch2-5/2 Nav Components | Ch2-5/3 Nav Components | Ch2-6/1 Material Transitions |
4545
| ----------|----------------| --------|
4646
| <img src="./screenshots/transition_chapter2_5_2.gif"/> | <img src="./screenshots/transition_chapter2_5_3.gif"/> | <img src="./screenshots/transition_chapter2_6_1.gif"/> |
4747

@@ -411,6 +411,8 @@ But on the other hand, since the Items are “background-less” themselves, to
411411
<br>
412412
[Motion-Material Design](https://material.io/develop/android/theming/motion)
413413
<br>
414+
[Material Components Android Examples](https://github.com/material-components/material-components-android-examples)
415+
<br>
414416
[Android — Inbox Material Transitions for RecyclerView](https://medium.com/workday-engineering/android-inbox-material-transitions-for-recyclerview-7ae3cb241aed)
415417
<br>
416418
[Plaid App](https://github.com/android/plaid)

Tutorial1-1Basics/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
<activity android:name=".chapter2_animate_views.Activity2_7GradientAnimations" />
3131
<activity android:name=".chapter2_animate_views.Activity2_8CounterAnimation" />
3232
<activity android:name=".chapter2_animate_views.Activity2_9CounterSurfaceView" />
33+
<activity android:name=".chapter3_physics.Activity3_5ElasticScale" />
3334

3435
<activity android:name=".chapter3_physics.Activity3_1PhysicsBasics" />
3536
<activity android:name=".chapter3_physics.Activity3_2ScaleAndChainedAnimations" />

Tutorial1-1Basics/src/main/java/com/example/tutorial1_1basics/MainActivity.kt

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,23 @@
11
package com.example.tutorial1_1basics
22

33
import android.content.Intent
4-
import androidx.appcompat.app.AppCompatActivity
54
import android.os.Bundle
65
import android.view.View
6+
import androidx.appcompat.app.AppCompatActivity
77
import androidx.databinding.DataBindingUtil
88
import androidx.recyclerview.widget.DividerItemDecoration
99
import androidx.recyclerview.widget.LinearLayoutManager
10+
import com.example.tutorial1_1basics.adapter_chapter_selection.BaseAdapter
11+
import com.example.tutorial1_1basics.adapter_chapter_selection.ChapterSelectionAdapter
12+
import com.example.tutorial1_1basics.adapter_chapter_selection.model.ActivityClassModel
1013
import com.example.tutorial1_1basics.chapter1_basics.Activity1_1Basics
1114
import com.example.tutorial1_1basics.chapter1_basics.Activity1_2AnimatorInflater
1215
import com.example.tutorial1_1basics.chapter1_basics.Activity1_3TranslationVsPosition
16+
import com.example.tutorial1_1basics.chapter1_basics.Activity1_4RotationTranslationPosition
1317
import com.example.tutorial1_1basics.chapter2_animate_views.*
14-
15-
import com.example.tutorial1_1basics.adapter_chapter_selection.BaseAdapter
16-
import com.example.tutorial1_1basics.adapter_chapter_selection.ChapterSelectionAdapter
18+
import com.example.tutorial1_1basics.chapter3_physics.*
1719
import com.example.tutorial1_1basics.databinding.ActivityMainBinding
18-
import com.example.tutorial1_1basics.adapter_chapter_selection.model.ActivityClassModel
19-
import com.example.tutorial1_1basics.chapter1_basics.Activity1_4RotationTranslationPosition
20-
import com.example.tutorial1_1basics.chapter3_physics.Activity3_1PhysicsBasics
21-
import com.example.tutorial1_1basics.chapter3_physics.Activity3_2ScaleAndChainedAnimations
22-
import com.example.tutorial1_1basics.chapter3_physics.Activity3_3FlingAnimation
23-
import com.example.tutorial1_1basics.chapter3_physics.Activity3_4BNV_TabLayoutPhysics
24-
import java.util.ArrayList
20+
import java.util.*
2521

2622
class MainActivity : AppCompatActivity(), BaseAdapter.OnRecyclerViewItemClickListener {
2723

@@ -157,6 +153,7 @@ class MainActivity : AppCompatActivity(), BaseAdapter.OnRecyclerViewItemClickLis
157153
)
158154
)
159155

156+
160157
activityClassModels.add(
161158
ActivityClassModel(
162159
Activity3_1PhysicsBasics::class.java,
@@ -184,6 +181,13 @@ class MainActivity : AppCompatActivity(), BaseAdapter.OnRecyclerViewItemClickLis
184181
getString(R.string.activity3_4)
185182
)
186183
)
184+
185+
activityClassModels.add(
186+
ActivityClassModel(
187+
Activity3_5ElasticScale::class.java,
188+
getString(R.string.activity3_5)
189+
)
190+
)
187191
}
188192

189193
@Override
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
package com.example.tutorial1_1basics.chapter3_physics
2+
3+
import android.animation.Animator
4+
import android.animation.AnimatorListenerAdapter
5+
import android.annotation.SuppressLint
6+
import android.os.Bundle
7+
import android.view.MotionEvent
8+
import android.view.View
9+
import android.view.ViewPropertyAnimator
10+
import android.view.animation.AccelerateDecelerateInterpolator
11+
import android.view.animation.Interpolator
12+
import android.widget.Button
13+
import androidx.appcompat.app.AppCompatActivity
14+
import androidx.dynamicanimation.animation.DynamicAnimation
15+
import androidx.dynamicanimation.animation.SpringAnimation
16+
import androidx.dynamicanimation.animation.SpringForce
17+
import androidx.interpolator.view.animation.FastOutLinearInInterpolator
18+
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
19+
import com.example.tutorial1_1basics.R
20+
import com.google.android.material.floatingactionbutton.FloatingActionButton
21+
22+
class Activity3_5ElasticScale : AppCompatActivity() {
23+
24+
override fun onCreate(savedInstanceState: Bundle?) {
25+
super.onCreate(savedInstanceState)
26+
setContentView(R.layout.activity3_5elastic_scale)
27+
28+
val buttonScaleDown = findViewById<Button>(R.id.buttonScaleDown)
29+
val buttonScaleUp = findViewById<Button>(R.id.buttonScaleUp)
30+
val buttonScaleDownSpring = findViewById<Button>(R.id.buttonScaleDownSpring)
31+
val buttonScaleUpSpring = findViewById<Button>(R.id.buttonScaleUpSpring)
32+
val buttonElastic = findViewById<Button>(R.id.buttonElastic)
33+
34+
val fab = findViewById<FloatingActionButton>(R.id.floatingActionButton)
35+
36+
buttonScaleDown.setOnClickListener {
37+
38+
val animator = buttonScaleDown.animate()
39+
.scaleX(.9f)
40+
.scaleY(.9f)
41+
.setDuration(200)
42+
.setInterpolator(FastOutLinearInInterpolator())
43+
.setListener(object : AnimatorListenerAdapter() {
44+
45+
override fun onAnimationEnd(animation: Animator?) {
46+
buttonScaleDown.scaleX = 1f
47+
buttonScaleDown.scaleY = 1f
48+
}
49+
50+
override fun onAnimationCancel(animation: Animator?) {
51+
buttonScaleDown.scaleX = 1f
52+
buttonScaleDown.scaleY = 1f
53+
}
54+
55+
})
56+
// .withEndAction {
57+
// buttonScaleDown.scaleX = 1f
58+
// buttonScaleDown.scaleY = 1f
59+
// }
60+
61+
}
62+
63+
buttonScaleUp.setOnClickListener {
64+
65+
val animator = buttonScaleUp.animate()
66+
.scaleX(1.1f)
67+
.scaleY(1.1f)
68+
.setDuration(200)
69+
.setInterpolator(AccelerateDecelerateInterpolator())
70+
.setListener(object : AnimatorListenerAdapter() {
71+
72+
override fun onAnimationEnd(animation: Animator?) {
73+
buttonScaleUp.scaleX = 1f
74+
buttonScaleUp.scaleY = 1f
75+
}
76+
77+
override fun onAnimationCancel(animation: Animator?) {
78+
buttonScaleUp.scaleX = 1f
79+
buttonScaleUp.scaleY = 1f
80+
}
81+
})
82+
}
83+
84+
buttonScaleDownSpring.setOnClickListener {
85+
86+
buttonScaleDownSpring.scaleX = .9f
87+
buttonScaleDownSpring.scaleY = .9f
88+
89+
val springForce = SpringForce().apply {
90+
dampingRatio = SpringForce.DAMPING_RATIO_HIGH_BOUNCY
91+
stiffness - SpringForce.STIFFNESS_VERY_LOW
92+
}
93+
94+
val animationScaleX =
95+
SpringAnimation(buttonScaleDownSpring, DynamicAnimation.SCALE_X).apply {
96+
spring = springForce
97+
}
98+
val animationScaleY =
99+
SpringAnimation(buttonScaleDownSpring, DynamicAnimation.SCALE_Y).apply {
100+
spring = springForce
101+
}
102+
103+
val finalPosition = 1f
104+
animationScaleX.animateToFinalPosition(finalPosition)
105+
animationScaleY.animateToFinalPosition(finalPosition)
106+
}
107+
108+
buttonScaleUpSpring.setOnClickListener {
109+
110+
buttonScaleUpSpring.scaleX = 1.1f
111+
buttonScaleUpSpring.scaleY = 1.1f
112+
113+
val springForce = SpringForce().apply {
114+
dampingRatio = SpringForce.DAMPING_RATIO_HIGH_BOUNCY
115+
stiffness - SpringForce.STIFFNESS_VERY_LOW
116+
}
117+
118+
val animationScaleX =
119+
SpringAnimation(buttonScaleUpSpring, DynamicAnimation.SCALE_X).apply {
120+
spring = springForce
121+
}
122+
val animationScaleY =
123+
SpringAnimation(buttonScaleUpSpring, DynamicAnimation.SCALE_Y).apply {
124+
spring = springForce
125+
}
126+
127+
val finalPosition = 1f
128+
animationScaleX.animateToFinalPosition(finalPosition)
129+
animationScaleY.animateToFinalPosition(finalPosition)
130+
}
131+
132+
buttonElastic.setElasticTouchListener(scaleBy = .3f, duration = 300)
133+
134+
fab.setElasticTouchListener(duration = 300)
135+
136+
}
137+
}
138+
139+
fun View.elasticAnimation(
140+
grow: Boolean = false,
141+
scaleBy: Float = 0.1f,
142+
duration: Long = 200,
143+
interpolator: Interpolator = LinearOutSlowInInterpolator()
144+
) {
145+
146+
val initialScaleX = scaleX
147+
val initialScaleY = scaleY
148+
149+
150+
val viewPropertyAnimator = animate()
151+
152+
if (grow) {
153+
viewPropertyAnimator.scaleX(initialScaleX + scaleBy)
154+
viewPropertyAnimator.scaleY(initialScaleY + scaleBy)
155+
} else {
156+
viewPropertyAnimator.scaleX(initialScaleX - scaleBy)
157+
viewPropertyAnimator.scaleY(initialScaleY - scaleBy)
158+
}
159+
160+
viewPropertyAnimator
161+
.setDuration(duration)
162+
.setInterpolator(interpolator)
163+
.setListener(object : AnimatorListenerAdapter() {
164+
165+
override fun onAnimationEnd(animation: Animator?) {
166+
animationEnd()
167+
}
168+
169+
override fun onAnimationCancel(animation: Animator?) {
170+
animationEnd()
171+
}
172+
173+
private fun animationEnd() {
174+
scaleX = initialScaleX
175+
scaleY = initialScaleY
176+
viewPropertyAnimator.setListener(null)
177+
}
178+
179+
})
180+
}
181+
182+
@SuppressLint("ClickableViewAccessibility")
183+
fun View.setElasticTouchListener(
184+
grow: Boolean = false,
185+
scaleBy: Float = 0.1f,
186+
duration: Long = 200,
187+
interpolator: Interpolator = LinearOutSlowInInterpolator()
188+
) {
189+
val initialScaleX = scaleX
190+
val initialScaleY = scaleY
191+
192+
val scaleStartX = if (grow) {
193+
initialScaleX + scaleBy
194+
} else {
195+
initialScaleX - scaleBy
196+
}
197+
198+
val scaleStartY = if (grow) {
199+
initialScaleY + scaleBy
200+
} else {
201+
initialScaleY - scaleBy
202+
}
203+
204+
setOnTouchListener { _, event ->
205+
206+
var startPropertyAnimator: ViewPropertyAnimator? = null
207+
208+
when (event.actionMasked) {
209+
210+
MotionEvent.ACTION_DOWN -> {
211+
212+
startPropertyAnimator = animate()
213+
214+
startPropertyAnimator
215+
.setDuration(duration)
216+
.setInterpolator(interpolator)
217+
.scaleX(scaleStartX)
218+
.scaleY(scaleStartY)
219+
}
220+
221+
MotionEvent.ACTION_UP -> {
222+
223+
startPropertyAnimator?.cancel()
224+
225+
val viewPropertyAnimator = animate()
226+
227+
viewPropertyAnimator
228+
.setDuration(duration)
229+
.setInterpolator(interpolator)
230+
.scaleX(initialScaleX)
231+
.scaleY(initialScaleY)
232+
.setListener(object : AnimatorListenerAdapter() {
233+
234+
override fun onAnimationEnd(animation: Animator?) {
235+
animationEnd()
236+
}
237+
238+
override fun onAnimationCancel(animation: Animator?) {
239+
animationEnd()
240+
}
241+
242+
private fun animationEnd() {
243+
scaleX = initialScaleX
244+
scaleY = initialScaleY
245+
viewPropertyAnimator.setListener(null)
246+
}
247+
})
248+
}
249+
}
250+
251+
true
252+
}
253+
}

0 commit comments

Comments
 (0)