Skip to content

Commit 9217c75

Browse files
committed
Add Material motion transitions to Reply
MDC 1.2.0-alpha05 adds support for the Material motion system. This change updates Reply to use the container transform provided by the Material Android library when moving between the email list and email details page and when clicking on the FAB to compose a new email.
1 parent 374b9c8 commit 9217c75

File tree

9 files changed

+42
-668
lines changed

9 files changed

+42
-668
lines changed

Reply/app/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ dependencies {
6565
implementation "com.github.bumptech.glide:glide:4.9.0"
6666

6767
// Material Components
68-
implementation 'com.google.android.material:material:1.1.0-beta01'
68+
implementation 'com.google.android.material:material:1.2.0-alpha05'
6969

7070
// Testing
7171
testImplementation 'junit:junit:4.12'

Reply/app/src/main/java/com/materialstudies/reply/ui/MainActivity.kt

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@
1616

1717
package com.materialstudies.reply.ui
1818

19+
import android.animation.Animator
1920
import android.os.Bundle
2021
import android.view.MenuItem
2122
import android.view.View
2223
import androidx.annotation.MenuRes
2324
import androidx.appcompat.app.AppCompatActivity
2425
import androidx.appcompat.app.AppCompatDelegate
2526
import androidx.appcompat.widget.Toolbar
26-
import androidx.core.content.ContextCompat
2727
import androidx.navigation.NavController
2828
import androidx.navigation.NavDestination
2929
import androidx.navigation.findNavController
@@ -179,10 +179,24 @@ class MainActivity : AppCompatActivity(),
179179
private fun setBottomAppBarForCompose() {
180180
binding.run {
181181
bottomAppBar.performHide()
182-
fab.hide()
183-
// Hide the BottomAppBar to avoid it showing above the keyboard
184-
// when composing a new email.
185-
bottomAppBar.visibility = View.GONE
182+
// Get a handle on the animator that hides the bottom app bar so we can wait to hide
183+
// the fab and bottom app bar until after it's exit animation finishes.
184+
bottomAppBar.animate().setListener(object : Animator.AnimatorListener {
185+
var isCanceled = false
186+
override fun onAnimationRepeat(animation: Animator?) { }
187+
override fun onAnimationEnd(animation: Animator?) {
188+
if (isCanceled) return
189+
190+
// Hide the BottomAppBar to avoid it showing above the keyboard
191+
// when composing a new email.
192+
bottomAppBar.visibility = View.GONE
193+
fab.visibility = View.INVISIBLE
194+
}
195+
override fun onAnimationCancel(animation: Animator?) {
196+
isCanceled = true
197+
}
198+
override fun onAnimationStart(animation: Animator?) { }
199+
})
186200
}
187201
}
188202

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

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,21 @@ 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
2524
import android.widget.ArrayAdapter
25+
import androidx.core.transition.addListener
2626
import androidx.fragment.app.Fragment
2727
import androidx.navigation.fragment.findNavController
2828
import androidx.navigation.fragment.navArgs
29+
import com.google.android.material.transition.MaterialContainerTransform
2930
import com.materialstudies.reply.R
3031
import com.materialstudies.reply.data.Account
3132
import com.materialstudies.reply.data.AccountStore
3233
import com.materialstudies.reply.data.Email
3334
import com.materialstudies.reply.data.EmailStore
3435
import com.materialstudies.reply.databinding.ComposeRecipientChipBinding
3536
import com.materialstudies.reply.databinding.FragmentComposeBinding
37+
import com.materialstudies.reply.ui.MainActivity
3638
import com.materialstudies.reply.util.themeInterpolator
37-
import com.materialstudies.reply.util.transition.MaterialContainerTransition
3839
import kotlin.LazyThreadSafetyMode.NONE
3940

4041
/**
@@ -109,15 +110,11 @@ class ComposeFragment : Fragment() {
109110
binding.executePendingBindings()
110111
// Delay creating the enterTransition until after we have inflated this Fragment's binding
111112
// and are able to access the view to be transitioned to.
112-
enterTransition = MaterialContainerTransition(
113-
correctForZOrdering = true
114-
).apply {
113+
enterTransition = MaterialContainerTransform(requireContext()).apply {
115114
// Manually add the Views to be shared since this is not a standard Fragment to Fragment
116115
// shared element transition.
117-
setSharedElementViews(
118-
requireActivity().findViewById(R.id.fab),
119-
binding.emailCardView
120-
)
116+
startView = requireActivity().findViewById(R.id.fab)
117+
endView = binding.emailCardView
121118
duration = resources.getInteger(R.integer.reply_motion_default_large).toLong()
122119
interpolator = requireContext().themeInterpolator(R.attr.motionInterpolatorPersistent)
123120
}

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

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ import androidx.fragment.app.Fragment
2424
import androidx.navigation.fragment.findNavController
2525
import androidx.navigation.fragment.navArgs
2626
import androidx.recyclerview.widget.GridLayoutManager
27+
import com.google.android.material.transition.MaterialContainerTransform
2728
import com.materialstudies.reply.R
2829
import com.materialstudies.reply.data.EmailStore
2930
import com.materialstudies.reply.databinding.FragmentEmailBinding
3031
import com.materialstudies.reply.util.themeInterpolator
31-
import com.materialstudies.reply.util.transition.MaterialContainerTransition
3232
import kotlin.LazyThreadSafetyMode.NONE
3333

3434
private const val MAX_GRID_SPANS = 3
@@ -91,17 +91,16 @@ class EmailFragment : Fragment() {
9191
private fun prepareTransitions() {
9292
postponeEnterTransition()
9393

94-
sharedElementEnterTransition = MaterialContainerTransition(
95-
R.id.nested_scroll_view,
96-
correctForZOrdering = true
97-
).apply {
94+
sharedElementEnterTransition = MaterialContainerTransform(requireContext()).apply {
95+
// Scope the transition to a view in the hierarchy so we know it will be added under
96+
// the bottom app bar but over the Hold transition from the exiting HomeFragment.
97+
drawingViewId = R.id.nav_host_fragment
9898
duration = resources.getInteger(R.integer.reply_motion_default_large).toLong()
9999
interpolator = requireContext().themeInterpolator(R.attr.motionInterpolatorPersistent)
100100
}
101-
sharedElementReturnTransition = MaterialContainerTransition(
102-
R.id.recycler_view,
103-
correctForZOrdering = true
104-
).apply {
101+
sharedElementReturnTransition = MaterialContainerTransform(requireContext()).apply {
102+
// Again, scope the return transition so it is added below the bottom app bar.
103+
drawingViewId = R.id.recycler_view
105104
duration = resources.getInteger(R.integer.reply_motion_default_large).toLong()
106105
interpolator = requireContext().themeInterpolator(R.attr.motionInterpolatorPersistent)
107106
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import androidx.lifecycle.observe
2626
import androidx.navigation.fragment.FragmentNavigatorExtras
2727
import androidx.navigation.fragment.findNavController
2828
import androidx.recyclerview.widget.ItemTouchHelper
29+
import com.google.android.material.transition.Hold
2930
import com.materialstudies.reply.R
3031
import com.materialstudies.reply.data.Email
3132
import com.materialstudies.reply.data.EmailStore
@@ -41,6 +42,13 @@ class HomeFragment : Fragment(), EmailAdapter.EmailAdapterListener {
4142

4243
private val emailAdapter = EmailAdapter(this)
4344

45+
override fun onCreate(savedInstanceState: Bundle?) {
46+
super.onCreate(savedInstanceState)
47+
exitTransition = Hold().apply {
48+
duration = resources.getInteger(R.integer.reply_motion_default_large).toLong()
49+
}
50+
}
51+
4452
override fun onCreateView(
4553
inflater: LayoutInflater,
4654
container: ViewGroup?,

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

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -85,56 +85,6 @@ fun lerp(
8585
return lerp(startValue, endValue, (fraction - startFraction) / (endFraction - startFraction))
8686
}
8787

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-
13888
/**
13989
* Linearly interpolate between two colors when the fraction is in a given range.
14090
*/

Reply/app/src/main/java/com/materialstudies/reply/util/GraphicsExtensions.kt

Lines changed: 0 additions & 63 deletions
This file was deleted.

Reply/app/src/main/java/com/materialstudies/reply/util/ViewExtensions.kt

Lines changed: 0 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -40,81 +40,3 @@ fun TextView.setTextAppearanceCompat(context: Context, resId: Int) {
4040
setTextAppearance(context, resId)
4141
}
4242
}
43-
44-
/**
45-
* Search this view and any children for a [ColorDrawable] `background` and return it's `color`,
46-
* else return `colorSurface`.
47-
*/
48-
@ColorInt
49-
fun View.descendantBackgroundColor(): Int {
50-
val bg = backgroundColor()
51-
if (bg != null) {
52-
return bg
53-
} else if (this is ViewGroup) {
54-
forEach {
55-
val childBg = descendantBackgroundColorOrNull()
56-
if (childBg != null) {
57-
return childBg
58-
}
59-
}
60-
}
61-
return context.themeColor(R.attr.colorSurface)
62-
}
63-
64-
@ColorInt
65-
private fun View.descendantBackgroundColorOrNull(): Int? {
66-
val bg = backgroundColor()
67-
if (bg != null) {
68-
return bg
69-
} else if (this is ViewGroup) {
70-
forEach {
71-
val childBg = backgroundColor()
72-
if (childBg != null) {
73-
return childBg
74-
}
75-
}
76-
}
77-
return null
78-
}
79-
80-
/**
81-
* Check if this [View]'s `background` is a [ColorDrawable] and if so, return it's `color`,
82-
* otherwise `null`.
83-
*/
84-
@ColorInt
85-
fun View.backgroundColor(): Int? {
86-
val bg = background
87-
if (bg is ColorDrawable) {
88-
return bg.color
89-
} else {
90-
val tint = backgroundTintList?.defaultColor
91-
if (tint != null && tint != -1) return tint
92-
}
93-
return null
94-
}
95-
96-
/**
97-
* Walk up from a [View] looking for an ancestor with a given `id`.
98-
*/
99-
fun View.findAncestorById(@IdRes ancestorId: Int): View {
100-
return when {
101-
id == ancestorId -> this
102-
parent is View -> (parent as View).findAncestorById(ancestorId)
103-
else -> throw IllegalArgumentException("$ancestorId not a valid ancestor")
104-
}
105-
}
106-
107-
/**
108-
* A copy of the KTX method, adding the ability to add extra padding the bottom of the [Bitmap];
109-
* useful when it will be used in a [android.graphics.BitmapShader] with
110-
* a [android.graphics.Shader.TileMode.CLAMP][CLAMP tile mode].
111-
*/
112-
fun View.drawToBitmap(@Px extraPaddingBottom: Int = 0): Bitmap {
113-
check(ViewCompat.isLaidOut(this)) {
114-
"View needs to be laid out before calling drawToBitmap()"
115-
}
116-
return Bitmap.createBitmap(width, height + extraPaddingBottom, ARGB_8888).applyCanvas {
117-
translate(-scrollX.toFloat(), -scrollY.toFloat())
118-
draw(this)
119-
}
120-
}

0 commit comments

Comments
 (0)