Skip to content
This repository was archived by the owner on Nov 21, 2024. It is now read-only.

Commit 9048daa

Browse files
committed
Add MaterialContainerTransition
- Changed MaterialContainerTransition to animate shared Views instead of a single bitmap of a View. - Updated ViewExtensions to theme<X> naming Change-Id: I0394876987be9dea00cde923fa64096fedf21b98
1 parent 450e7ca commit 9048daa

17 files changed

+816
-66
lines changed

app/src/main/java/com/materialstudies/reply/ui/compose/ComposeFragment.kt

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import android.transition.Slide
2121
import android.view.LayoutInflater
2222
import android.view.View
2323
import android.view.ViewGroup
24+
import android.view.animation.AccelerateInterpolator
2425
import android.widget.ArrayAdapter
2526
import androidx.fragment.app.Fragment
2627
import androidx.navigation.fragment.findNavController
@@ -33,6 +34,7 @@ import com.materialstudies.reply.data.EmailStore
3334
import com.materialstudies.reply.databinding.ComposeRecipientChipBinding
3435
import com.materialstudies.reply.databinding.FragmentComposeBinding
3536
import com.materialstudies.reply.util.FastOutUltraSlowIn
37+
import com.materialstudies.reply.util.transition.MaterialContainerTransition
3638
import kotlin.LazyThreadSafetyMode.NONE
3739

3840
/**
@@ -54,10 +56,7 @@ class ComposeFragment : Fragment() {
5456

5557
override fun onCreate(savedInstanceState: Bundle?) {
5658
super.onCreate(savedInstanceState)
57-
enterTransition = Slide().apply {
58-
duration = resources.getInteger(R.integer.reply_motion_default_duration).toLong()
59-
interpolator = FastOutUltraSlowIn()
60-
}
59+
prepareTransitions()
6160
}
6261

6362
override fun onCreateView(
@@ -82,6 +81,8 @@ class ComposeFragment : Fragment() {
8281
AccountStore.getAllUserAccounts().map { it.email }
8382
)
8483
}
84+
85+
startTransitions()
8586
}
8687

8788
/**
@@ -99,4 +100,31 @@ class ComposeFragment : Fragment() {
99100
addView(chipBinding.root)
100101
}
101102
}
103+
104+
private fun prepareTransitions() {
105+
postponeEnterTransition()
106+
}
107+
108+
private fun startTransitions() {
109+
binding.executePendingBindings()
110+
// Delay creating the enterTransition until after we have inflated this Fragment's binding
111+
// and are able to access the view to be transitioned to.
112+
enterTransition = MaterialContainerTransition(
113+
correctForZOrdering = true
114+
).apply {
115+
// Manually add the Views to be shared since this is not a standard Fragment to Fragment
116+
// shared element transition.
117+
setSharedElementViews(
118+
requireActivity().findViewById(R.id.fab),
119+
binding.emailCardView
120+
)
121+
duration = resources.getInteger(R.integer.reply_motion_default_duration).toLong()
122+
interpolator = FastOutUltraSlowIn()
123+
}
124+
returnTransition = Slide().apply {
125+
duration = resources.getInteger(R.integer.reply_motion_micro_duration).toLong()
126+
interpolator = AccelerateInterpolator()
127+
}
128+
startPostponedEnterTransition()
129+
}
102130
}

app/src/main/java/com/materialstudies/reply/ui/email/EmailFragment.kt

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@
1616

1717
package com.materialstudies.reply.ui.email
1818

19-
import android.animation.AnimatorInflater
2019
import android.os.Bundle
21-
import android.transition.TransitionInflater
2220
import android.view.LayoutInflater
2321
import android.view.View
2422
import android.view.ViewGroup
@@ -30,6 +28,7 @@ import com.materialstudies.reply.R
3028
import com.materialstudies.reply.data.EmailStore
3129
import com.materialstudies.reply.databinding.FragmentEmailBinding
3230
import com.materialstudies.reply.util.FastOutUltraSlowIn
31+
import com.materialstudies.reply.util.transition.MaterialContainerTransition
3332
import kotlin.LazyThreadSafetyMode.NONE
3433

3534
private const val MAX_GRID_SPANS = 3
@@ -39,7 +38,6 @@ private const val MAX_GRID_SPANS = 3
3938
*/
4039
class EmailFragment : Fragment() {
4140

42-
4341
private val args: EmailFragmentArgs by navArgs()
4442
private val emailId: Long by lazy(NONE) { args.emailId }
4543

@@ -92,19 +90,26 @@ class EmailFragment : Fragment() {
9290

9391
private fun prepareTransitions() {
9492
postponeEnterTransition()
95-
sharedElementEnterTransition = TransitionInflater.from(context)
96-
.inflateTransition(R.transition.email_card_shared_element_transition).apply {
97-
interpolator = FastOutUltraSlowIn()
98-
}
93+
94+
sharedElementEnterTransition = MaterialContainerTransition(
95+
R.id.nested_scroll_view,
96+
correctForZOrdering = true
97+
).apply {
98+
duration = resources.getInteger(R.integer.reply_motion_default_duration).toLong()
99+
interpolator = FastOutUltraSlowIn()
100+
}
101+
sharedElementReturnTransition = MaterialContainerTransition(
102+
R.id.recycler_view,
103+
correctForZOrdering = true
104+
).apply {
105+
duration = resources.getInteger(R.integer.reply_motion_default_duration).toLong()
106+
interpolator = FastOutUltraSlowIn()
107+
}
99108
}
100109

101110
private fun startTransitions() {
102111
binding.executePendingBindings()
103112
startPostponedEnterTransition()
104-
AnimatorInflater.loadAnimator(requireContext(), R.animator.alpha_in).apply {
105-
setTarget(binding.emailCardView)
106-
start()
107-
}
108113
}
109114

110115
private fun showError() {

app/src/main/java/com/materialstudies/reply/ui/home/EmailSwipeActionDrawable.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import androidx.annotation.ColorInt
2929
import androidx.appcompat.content.res.AppCompatResources
3030
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
3131
import com.materialstudies.reply.R
32-
import com.materialstudies.reply.util.getColorFromAttr
32+
import com.materialstudies.reply.util.themeColor
3333
import com.materialstudies.reply.util.lerp
3434
import com.materialstudies.reply.util.lerpArgb
3535
import kotlin.math.abs
@@ -45,7 +45,7 @@ import kotlin.math.hypot
4545
class EmailSwipeActionDrawable(context: Context) : Drawable() {
4646

4747
private val circlePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
48-
color = context.getColorFromAttr(R.attr.colorSecondary)
48+
color = context.themeColor(R.attr.colorSecondary)
4949
style = Paint.Style.FILL
5050
}
5151

@@ -62,8 +62,8 @@ class EmailSwipeActionDrawable(context: Context) : Drawable() {
6262
private val iconIntrinsicWidth = icon.intrinsicWidth
6363
private val iconIntrinsicHeight = icon.intrinsicHeight
6464

65-
@ColorInt private val iconTint = context.getColorFromAttr(R.attr.colorOnBackground)
66-
@ColorInt private val iconTintActive = context.getColorFromAttr(R.attr.colorOnSecondary)
65+
@ColorInt private val iconTint = context.themeColor(R.attr.colorOnBackground)
66+
@ColorInt private val iconTintActive = context.themeColor(R.attr.colorOnSecondary)
6767

6868
// Amount that we should 'overshoot' the icon's scale by when animating.
6969
private val iconMaxScaleAddition = 0.5F

app/src/main/java/com/materialstudies/reply/ui/home/EmailViewHolder.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ import com.materialstudies.reply.R
2222
import com.materialstudies.reply.data.Email
2323
import com.materialstudies.reply.databinding.EmailItemLayoutBinding
2424
import com.materialstudies.reply.ui.common.EmailAttachmentAdapter
25-
import com.materialstudies.reply.util.getStyleIdFromAttr
25+
import com.materialstudies.reply.util.themeStyle
2626
import com.materialstudies.reply.util.setTextAppearanceCompat
27+
import kotlin.math.abs
2728

2829
class EmailViewHolder(
2930
private val binding: EmailItemLayoutBinding,
@@ -50,7 +51,7 @@ class EmailViewHolder(
5051
binding.root.isActivated = email.isStarred
5152

5253
// Set the subject's TextAppearance
53-
val textAppearance = binding.subjectTextView.context.getStyleIdFromAttr(
54+
val textAppearance = binding.subjectTextView.context.themeStyle(
5455
if (email.isImportant) {
5556
R.attr.textAppearanceHeadline4
5657
} else {
@@ -87,7 +88,7 @@ class EmailViewHolder(
8788

8889
// Animate the top left corner radius of the email card as swipe happens.
8990
val interpolation = (currentSwipePercentage / swipeThreshold).coerceIn(0F, 1F)
90-
val adjustedInterpolation = Math.abs((if (isStarred) 1F else 0F) - interpolation)
91+
val adjustedInterpolation = abs((if (isStarred) 1F else 0F) - interpolation)
9192
binding.cardView.progress = adjustedInterpolation
9293

9394
// Start the background animation once the threshold is met.
@@ -104,5 +105,4 @@ class EmailViewHolder(
104105
val email = binding.email ?: return
105106
binding.listener?.onEmailStarChanged(email, !email.isStarred)
106107
}
107-
108108
}

app/src/main/java/com/materialstudies/reply/ui/home/HomeFragment.kt

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
package com.materialstudies.reply.ui.home
1818

1919
import android.os.Bundle
20-
import android.transition.Fade
21-
import android.transition.TransitionInflater
2220
import android.view.LayoutInflater
2321
import android.view.View
2422
import android.view.ViewGroup
@@ -33,7 +31,6 @@ import com.materialstudies.reply.data.Email
3331
import com.materialstudies.reply.data.EmailStore
3432
import com.materialstudies.reply.databinding.FragmentHomeBinding
3533
import com.materialstudies.reply.ui.MenuBottomSheetDialogFragment
36-
import com.materialstudies.reply.util.FastOutUltraSlowIn
3734

3835
/**
3936
* A [Fragment] that displays a list of emails.
@@ -44,15 +41,6 @@ class HomeFragment : Fragment(), EmailAdapter.EmailAdapterListener {
4441

4542
private val emailAdapter = EmailAdapter(this)
4643

47-
override fun onCreate(savedInstanceState: Bundle?) {
48-
super.onCreate(savedInstanceState)
49-
// Fade content out when we navigate to a different screen.
50-
exitTransition = Fade().apply {
51-
duration = resources.getInteger(R.integer.reply_motion_default_duration).toLong()
52-
interpolator = FastOutUltraSlowIn()
53-
}
54-
}
55-
5644
override fun onCreateView(
5745
inflater: LayoutInflater,
5846
container: ViewGroup?,

app/src/main/java/com/materialstudies/reply/ui/nav/BottomNavDrawerFragment.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import com.materialstudies.reply.data.Account
3737
import com.materialstudies.reply.data.AccountStore
3838
import com.materialstudies.reply.databinding.FragmentBottomNavDrawerBinding
3939
import com.materialstudies.reply.util.FastOutUltraSlowIn
40-
import com.materialstudies.reply.util.getColorFromAttr
40+
import com.materialstudies.reply.util.themeColor
4141
import com.materialstudies.reply.util.lerp
4242
import kotlin.LazyThreadSafetyMode.NONE
4343
import kotlin.math.abs
@@ -91,7 +91,7 @@ class BottomNavDrawerFragment :
9191
0
9292
).apply {
9393
fillColor = ColorStateList.valueOf(
94-
requireContext().getColorFromAttr(R.attr.colorBrandedVariantSurface)
94+
requireContext().themeColor(R.attr.colorBrandedVariantSurface)
9595
)
9696
elevation = resources.getDimension(R.dimen.plane_08)
9797
initializeElevationOverlay(requireContext())
@@ -106,7 +106,7 @@ class BottomNavDrawerFragment :
106106
0
107107
).apply {
108108
fillColor = ColorStateList.valueOf(
109-
requireContext().getColorFromAttr(R.attr.colorBrandedSurface)
109+
requireContext().themeColor(R.attr.colorBrandedSurface)
110110
)
111111
elevation = resources.getDimension(R.dimen.plane_16)
112112
shadowCompatibilityMode = MaterialShapeDrawable.SHADOW_COMPAT_MODE_NEVER

app/src/main/java/com/materialstudies/reply/util/AnimationUtils.kt

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package com.materialstudies.reply.util
1919
import androidx.annotation.ColorInt
2020
import androidx.annotation.FloatRange
2121
import com.google.android.material.animation.ArgbEvaluatorCompat
22+
import kotlin.math.roundToInt
2223

2324
/**
2425
* Linearly interpolate between two values
@@ -31,6 +32,17 @@ fun lerp(
3132
return startValue + fraction * (endValue - startValue)
3233
}
3334

35+
/**
36+
* Linearly interpolate between two values
37+
*/
38+
fun lerp(
39+
startValue: Int,
40+
endValue: Int,
41+
@FloatRange(from = 0.0, fromInclusive = true, to = 1.0, toInclusive = true) fraction: Float
42+
): Int {
43+
return (startValue + fraction * (endValue - startValue)).roundToInt()
44+
}
45+
3446
/**
3547
* Linearly interpolate between two values when the fraction is in a given range.
3648
*/
@@ -52,6 +64,93 @@ fun lerp(
5264
return lerp(startValue, endValue, (fraction - startFraction) / (endFraction - startFraction))
5365
}
5466

67+
/**
68+
* Linearly interpolate between two values when the fraction is in a given range.
69+
*/
70+
fun lerp(
71+
startValue: Int,
72+
endValue: Int,
73+
@FloatRange(
74+
from = 0.0,
75+
fromInclusive = true,
76+
to = 1.0,
77+
toInclusive = false
78+
) startFraction: Float,
79+
@FloatRange(from = 0.0, fromInclusive = false, to = 1.0, toInclusive = true) endFraction: Float,
80+
@FloatRange(from = 0.0, fromInclusive = true, to = 1.0, toInclusive = true) fraction: Float
81+
): Int {
82+
if (fraction < startFraction) return startValue
83+
if (fraction > endFraction) return endValue
84+
85+
return lerp(startValue, endValue, (fraction - startFraction) / (endFraction - startFraction))
86+
}
87+
88+
/**
89+
* Linearly interpolate between two [CornerRounding]s when the fraction is in a given range.
90+
*/
91+
fun lerp(
92+
startValue: CornerRounding,
93+
endValue: CornerRounding,
94+
@FloatRange(
95+
from = 0.0,
96+
fromInclusive = true,
97+
to = 1.0,
98+
toInclusive = false
99+
) startFraction: Float,
100+
@FloatRange(from = 0.0, fromInclusive = false, to = 1.0, toInclusive = true) endFraction: Float,
101+
@FloatRange(from = 0.0, fromInclusive = true, to = 1.0, toInclusive = true) fraction: Float
102+
): CornerRounding {
103+
if (fraction < startFraction) return startValue
104+
if (fraction > endFraction) return endValue
105+
106+
return CornerRounding(
107+
lerp(
108+
startValue.topLeftRadius,
109+
endValue.topLeftRadius,
110+
startFraction,
111+
endFraction,
112+
fraction
113+
),
114+
lerp(
115+
startValue.topRightRadius,
116+
endValue.topRightRadius,
117+
startFraction,
118+
endFraction,
119+
fraction
120+
),
121+
lerp(
122+
startValue.bottomRightRadius,
123+
endValue.bottomRightRadius,
124+
startFraction,
125+
endFraction,
126+
fraction
127+
),
128+
lerp(
129+
startValue.bottomLeftRadius,
130+
endValue.bottomLeftRadius,
131+
startFraction,
132+
endFraction,
133+
fraction
134+
)
135+
)
136+
}
137+
138+
/**
139+
* Linearly interpolate between two colors when the fraction is in a given range.
140+
*/
141+
@ColorInt
142+
fun lerpArgb(
143+
@ColorInt startColor: Int,
144+
@ColorInt endColor: Int,
145+
@FloatRange(from = 0.0, fromInclusive = true, to = 1.0, toInclusive = true) fraction: Float
146+
): Int {
147+
return ArgbEvaluatorCompat.getInstance().evaluate(
148+
fraction,
149+
startColor,
150+
endColor
151+
)
152+
}
153+
55154
/**
56155
* Linearly interpolate between two colors when the fraction is in a given range.
57156
*/

0 commit comments

Comments
 (0)