Skip to content

Commit 72c81d1

Browse files
add transitions that extend Visibility
1 parent cc59441 commit 72c81d1

22 files changed

+920
-132
lines changed

README.md

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ Tutorials about animations in Android such as ObjectAnimators, ValueAnimators, t
33
gradient animations, AnimationDrawables, AnimatedVectorDrawables with states, physics animations,
44
fragment transitions and image to ViewPager transitions and more.
55

6+
[![Android Weekly #437](https://androidweekly.net/issues/issue-437/badge)](https://androidweekly.net/issues/issue-437)
7+
[![Kotlin Version](https://img.shields.io/badge/kotlin-1.4.0-blue.svg)](https://kotlinlang.org)
8+
[![API](https://img.shields.io/badge/API-21%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=21)
9+
610
## Overview
711

812
* [Tutorial1-1Basics](https://github.com/SmartToolFactory/Animation-Tutorials/tree/master/Tutorial1-1Basics)
@@ -308,7 +312,7 @@ findNavController().navigate(direction, extras)
308312
* 🔥🔥🔥 With transitions, it's required for start and end values to be different from each other to call
309313
```createAnimator``` method.
310314

311-
To make sure that it gets called, either set **captureStartValues**
315+
- To make sure that **ENTER** or **REENTER** transitions start, either set **captureStartValues**
312316
and **captureEndValues** manually, or in fragment transitioned to, create **setEnterSharedElementCallback**
313317
and override **onSharedElementStart** and **onSharedElementEnd** methods and set properties of
314318
objects that are not shared in both fragments.
@@ -317,9 +321,9 @@ objects that are not shared in both fragments.
317321
of ***Transition*** start and end point to same value.
318322

319323
#### Note:
320-
🔥🔥🔥 In tutorial 2-4 and 2-5, having same background color for both fragments causing second fragment's **ENTER TRANSITION(CircularReveal and Slide.BOTTOM)** to not work
324+
🔥🔥🔥 In tutorial 2-4 and 2-5, having same background color for both fragments causing second fragment's **ENTER TRANSITION(CircularReveal and Slide.BOTTOM)** to not work.
321325
So when using **Transitions** that extend ```Visiblity``` class such as Slide, or Fade be careful about background color.
322-
Either set callback and set start and end properties for scene with
326+
Either set callback and set start and end properties for starging and ending scenes with
323327

324328
```
325329
setEnterSharedElementCallback(object : SharedElementCallback() {
@@ -352,20 +356,15 @@ Either set callback and set start and end properties for scene with
352356
}
353357
```
354358

355-
or use **custom transitions** that extend either ```Transition``` or ```Visibility```
359+
or use **custom transitions** that extend either ```Transition``` or ```Visibility``` and force value changes.
356360

357-
*** ⚠️ Transitions that extend ```Visibility``` such as ```Slide```, ```Fade```, or ```Explode``` depends on ***visibility*** of the view. If
358-
visibility is changed from ```View.INVISIBLE``` to ```View.VISIBLE``` ```onAppear``` method of ```Visibility class is called, if visibility changes
359-
opposite ```onDisappear``` method is called. With scene's actual visibility change, or manual visibility change it's possible to play transitions
361+
* ⚠️ Transitions that extend ```Visibility``` such as ```Slide```, ```Fade```, or ```Explode``` depends on ***visibility*** of the view. If
362+
visibility is changed from ```View.INVISIBLE``` to ```View.VISIBLE``` ```onAppear``` method of ```Visibility``` class is called, if visibility changes
363+
```View.VISIBLE``` to ```View.INVISIBLE``` ```onDisappear``` method is called. With difference between visibility of starting and ending scenes, or manual visibility change it's possible to play transitions
360364
from backwards.
361365

362-
### ‼️ Attention
363-
I wasn't able to create exit and return transitions using custom transitions with classes neither
364-
extend ```Transition``` nor ```Visibility```. Transitions such as Fade, Slide or Explode
365-
that extend ```Visibility``` work, but when i extend ```Visibility``` and do some custom transitions
366-
it does not work for **exitTransition** for first, **returnTransition** for second fragment.
367-
368-
If you figure out a solution feel free to send a PR🤩😍
366+
* ⚠️ When current transition is **EXIT** or **RETURN** transition ```captureEndValues``` is not called, because of this use a transition that extends ```Visibility``` for ```exitTransition``` and ```returnTransition``` to start,
367+
and be aware that Animator from ```onDisAppear``` is called while current transition is exit or return.
369368

370369
### Resources and References
371370

Tutorial3-1Transitions/src/main/java/com/smarttoolfactory/tutorial3_1transitions/chapter2_fragment_transitions/Fragment2_2LifeCycleFirst.kt

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.smarttoolfactory.tutorial3_1transitions.chapter2_fragment_transition
22

33
import android.graphics.Color
44
import android.os.Bundle
5+
import android.view.Gravity
56
import android.view.LayoutInflater
67
import android.view.View
78
import android.view.ViewGroup
@@ -12,11 +13,10 @@ import androidx.constraintlayout.widget.ConstraintLayout
1213
import androidx.core.app.SharedElementCallback
1314
import androidx.fragment.app.Fragment
1415
import androidx.fragment.app.activityViewModels
15-
import androidx.transition.Fade
16+
import androidx.transition.Slide
1617
import androidx.transition.Transition
1718
import androidx.transition.TransitionSet
1819
import com.smarttoolfactory.tutorial3_1transitions.R
19-
import com.smarttoolfactory.tutorial3_1transitions.transition.ScaleTransition
2020
import com.smarttoolfactory.tutorial3_1transitions.transition.TextColorTransition
2121
import com.smarttoolfactory.tutorial3_1transitions.transition.TransitionXAdapter
2222

@@ -63,13 +63,12 @@ class Fragment2_2LifeCycleFirst : Fragment() {
6363
allowEnterTransitionOverlap = false
6464
allowReturnTransitionOverlap = false
6565

66+
val transitionSet = TransitionSet()
6667

67-
val slide = Fade()
68+
val slide = Slide(Gravity.TOP)
6869
.apply {
69-
duration = 30
70-
startDelay = 470
70+
duration = 2000
7171
}
72-
val transitionSet = TransitionSet()
7372

7473
val textTransition =
7574
TextColorTransition(tvEnterTransition.currentTextColor, Color.MAGENTA, true)
@@ -79,16 +78,8 @@ class Fragment2_2LifeCycleFirst : Fragment() {
7978
duration = 500
8079
}
8180

82-
val scaleTransition = ScaleTransition(1f, 1f, 0.95f, 0.95f, true)
83-
.apply {
84-
addTarget(view)
85-
debugMode = true
86-
duration = 500
87-
}
88-
8981
transitionSet.addTransition(slide)
9082
transitionSet.addTransition(textTransition)
91-
transitionSet.addTransition(scaleTransition)
9283

9384
exitTransition = transitionSet
9485

Tutorial3-1Transitions/src/main/java/com/smarttoolfactory/tutorial3_1transitions/chapter2_fragment_transitions/Fragment2_5MagazineDetailAlt.kt

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ import kotlin.math.hypot
3333

3434
class Fragment2_5MagazineDetailAlt : Fragment() {
3535

36+
var isEntering = true
37+
3638
lateinit var magazineModel: MagazineModel
3739

3840
override fun onCreate(savedInstanceState: Bundle?) {
@@ -68,7 +70,7 @@ class Fragment2_5MagazineDetailAlt : Fragment() {
6870

6971
private fun setUpSharedElementTransition(view: View) {
7072

71-
// allowEnterTransitionOverlap = false
73+
// allowReturnTransitionOverlap = false
7274

7375
setSharedElementCallback(view)
7476

@@ -96,12 +98,20 @@ class Fragment2_5MagazineDetailAlt : Fragment() {
9698
* Overriding onSharedElementStart, and onSharedElementEnd
9799
* creates start and end values for transitions that extend ```Visibility```.
98100
* Without this start and end values are same for view background.
101+
*
102+
* ```setEnterSharedElementCallback``` methods are called
103+
* both when entering and returning from this fragment.
104+
*
105+
* **isEntering** is used to change start and end visibility of items for
106+
* transitions before this fragment appears or disappears.
99107
*/
100108
private fun setSharedElementCallback(view: View) {
101109

102110
val viewImageBackground = view.findViewById<View>(R.id.viewImageBackground)
103111
val recyclerView = view.findViewById<RecyclerView>(R.id.recyclerView)
104112

113+
isEntering = true
114+
105115
viewImageBackground.visibility = View.INVISIBLE
106116
recyclerView.visibility = View.INVISIBLE
107117

@@ -117,8 +127,13 @@ class Fragment2_5MagazineDetailAlt : Fragment() {
117127
sharedElements,
118128
sharedElementSnapshots
119129
)
120-
viewImageBackground.visibility = View.INVISIBLE
121-
recyclerView.visibility = View.INVISIBLE
130+
if (isEntering) {
131+
viewImageBackground.visibility = View.INVISIBLE
132+
recyclerView.visibility = View.INVISIBLE
133+
}else {
134+
viewImageBackground.visibility = View.VISIBLE
135+
recyclerView.visibility = View.VISIBLE
136+
}
122137
}
123138

124139
override fun onSharedElementEnd(
@@ -129,6 +144,16 @@ class Fragment2_5MagazineDetailAlt : Fragment() {
129144
super.onSharedElementEnd(sharedElementNames, sharedElements, sharedElementSnapshots)
130145
viewImageBackground.visibility = View.VISIBLE
131146
recyclerView.visibility = View.VISIBLE
147+
148+
if (isEntering) {
149+
viewImageBackground.visibility = View.VISIBLE
150+
recyclerView.visibility = View.VISIBLE
151+
}else {
152+
viewImageBackground.visibility = View.INVISIBLE
153+
recyclerView.visibility = View.INVISIBLE
154+
}
155+
156+
isEntering = false
132157
}
133158

134159
})

Tutorial3-1Transitions/src/main/java/com/smarttoolfactory/tutorial3_1transitions/chapter2_fragment_transitions/Fragment2_5ToolbarDetail.kt

Lines changed: 69 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import android.view.View
77
import android.view.ViewGroup
88
import android.view.animation.AccelerateDecelerateInterpolator
99
import android.view.animation.AnimationUtils
10+
import android.view.animation.OvershootInterpolator
1011
import android.widget.ImageView
1112
import android.widget.TextView
1213
import androidx.core.view.doOnNextLayout
@@ -18,17 +19,22 @@ import androidx.transition.Slide
1819
import androidx.transition.Transition
1920
import androidx.transition.TransitionInflater
2021
import androidx.transition.TransitionSet
22+
import com.google.android.material.appbar.AppBarLayout
23+
import com.google.android.material.appbar.CollapsingToolbarLayout
2124
import com.smarttoolfactory.tutorial3_1transitions.R
2225
import com.smarttoolfactory.tutorial3_1transitions.adapter.SingleViewBinderListAdapter
2326
import com.smarttoolfactory.tutorial3_1transitions.adapter.model.MagazineModel
2427
import com.smarttoolfactory.tutorial3_1transitions.adapter.model.Post
2528
import com.smarttoolfactory.tutorial3_1transitions.adapter.model.PostCardModel
2629
import com.smarttoolfactory.tutorial3_1transitions.adapter.viewholder.ItemBinder
2730
import com.smarttoolfactory.tutorial3_1transitions.adapter.viewholder.PostCardViewBinder
31+
import com.smarttoolfactory.tutorial3_1transitions.transition.ScaleTransition
32+
import com.smarttoolfactory.tutorial3_1transitions.transition.TransitionXAdapter
2833
import com.smarttoolfactory.tutorial3_1transitions.transition.visibility.ForcedCircularReveal
29-
import com.smarttoolfactory.tutorial3_1transitions.transition.visibility.ForcedSlide
34+
import com.smarttoolfactory.tutorial3_1transitions.transition.visibility.ScaleChange
3035
import java.util.*
3136
import kotlin.collections.ArrayList
37+
import kotlin.math.abs
3238
import kotlin.math.hypot
3339

3440
/**
@@ -73,7 +79,7 @@ class Fragment2_5ToolbarDetail : Fragment() {
7379

7480
private fun setUpSharedElementTransition(view: View) {
7581

76-
// allowEnterTransitionOverlap = false
82+
allowReturnTransitionOverlap = false
7783

7884
/*
7985
🔥 Set sharedElementReturnTransition, because both
@@ -103,14 +109,24 @@ class Fragment2_5ToolbarDetail : Fragment() {
103109

104110
val transitionSetEnter = TransitionSet()
105111

106-
val slideFromBottom = ForcedSlide(Gravity.BOTTOM, View.INVISIBLE, View.VISIBLE)
112+
val scaleX = .8f
113+
val scaleY = .8f
114+
115+
recyclerView.scaleX = 0f
116+
recyclerView.scaleY = 0f
117+
recyclerView.requestLayout()
118+
119+
val endRadiusRV = hypot(
120+
recyclerView.width.toDouble(),
121+
recyclerView.height.toDouble()
122+
).toFloat()
123+
124+
val scaleAnimation = ScaleTransition(scaleX, scaleY, 1f, 1f, true)
107125
.apply {
108-
interpolator = AnimationUtils.loadInterpolator(
109-
requireContext(),
110-
android.R.interpolator.linear_out_slow_in
111-
)
112-
startDelay = 400
126+
interpolator = OvershootInterpolator()
127+
startDelay = 500
113128
duration = 600
129+
debugMode = true
114130
addTarget(recyclerView)
115131
}
116132

@@ -119,8 +135,9 @@ class Fragment2_5ToolbarDetail : Fragment() {
119135
viewImageBackground.height.toDouble()
120136
).toFloat()
121137

122-
123-
val circularReveal = ForcedCircularReveal(View.INVISIBLE, View.VISIBLE, true)
138+
val circularReveal = ForcedCircularReveal(
139+
View.INVISIBLE, View.VISIBLE, true
140+
)
124141
.apply {
125142
addTarget(viewImageBackground)
126143
setStartRadius(0f)
@@ -129,7 +146,7 @@ class Fragment2_5ToolbarDetail : Fragment() {
129146
duration = 700
130147
}
131148

132-
transitionSetEnter.addTransition(slideFromBottom)
149+
transitionSetEnter.addTransition(scaleAnimation)
133150
transitionSetEnter.addTransition(circularReveal)
134151

135152
return transitionSetEnter
@@ -147,22 +164,41 @@ class Fragment2_5ToolbarDetail : Fragment() {
147164
requireContext(),
148165
android.R.interpolator.linear_out_slow_in
149166
)
150-
duration = 900
167+
duration = 500
151168
addTarget(viewTop)
152169
}
153170

154171

155-
val slideToBottom = Slide(Gravity.BOTTOM).apply {
156-
interpolator = AnimationUtils.loadInterpolator(
157-
requireContext(),
158-
android.R.interpolator.linear_out_slow_in
159-
)
160-
duration = 900
161-
addTarget(recyclerView)
162-
}
172+
/*
173+
🔥 Values are in reverse order because this Transition will call Animator from
174+
onDisappear.
175+
*/
176+
val scaleRV =
177+
ScaleChange(0.95f, 0.95f, 1f, 1f)
178+
.apply {
179+
interpolator = AnimationUtils.loadInterpolator(
180+
requireContext(),
181+
android.R.interpolator.linear_out_slow_in
182+
)
183+
duration = 300
184+
debugMode = true
185+
addTarget(recyclerView)
186+
}.addListener(object : TransitionXAdapter() {
187+
override fun onTransitionEnd(transition: Transition) {
188+
super.onTransitionEnd(transition)
189+
recyclerView.visibility = View.INVISIBLE
190+
}
191+
})
192+
193+
val slide = Slide(Gravity.END)
194+
.apply {
195+
startDelay = 400
196+
duration = 400
197+
}
163198

164199
transitionSetReturn.addTransition(slideToTop)
165-
transitionSetReturn.addTransition(slideToBottom)
200+
transitionSetReturn.addTransition(scaleRV)
201+
transitionSetReturn.addTransition(slide)
166202

167203
return transitionSetReturn
168204
}
@@ -191,23 +227,23 @@ class Fragment2_5ToolbarDetail : Fragment() {
191227

192228
listAdapter.submitList(generateMockPosts())
193229

194-
view?.doOnNextLayout {
230+
view.doOnNextLayout {
195231
(it.parent as? ViewGroup)?.doOnPreDraw {
196232
startPostponedEnterTransition()
197233
}
198234
}
199235

200-
// val appbar = view.findViewById<AppBarLayout>(R.id.appbar)
201-
// val toolbar = view.findViewById<Toolbar>(R.id.toolbar)
202-
// appbar.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { appBarLayout, verticalOffset ->
203-
//
204-
// //Check if the view is collapsed
205-
// if (abs(verticalOffset) >= appbar.totalScrollRange) {
206-
// toolbar.title = "Collapsed"
207-
// } else {
208-
// toolbar.title = ""
209-
// }
210-
// })
236+
val appbar = view.findViewById<AppBarLayout>(R.id.appbar)
237+
val collapsingToolbar = view.findViewById<CollapsingToolbarLayout>(R.id.collapsingToolbar)
238+
appbar.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { appBarLayout, verticalOffset ->
239+
240+
//Check if the view is collapsed
241+
if (abs(verticalOffset) >= appbar.totalScrollRange) {
242+
collapsingToolbar.title = "Collapsed"
243+
} else {
244+
collapsingToolbar.title = ""
245+
}
246+
})
211247
}
212248

213249
private fun generateMockPosts(): List<PostCardModel> {

0 commit comments

Comments
 (0)