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

Commit 49d9ad0

Browse files
committed
Add motion subsystem infra
- Added motion duration resources - Added HomeFragment->EmailFragment transition+shared element transitions Change-Id: Id772af6d89b06d0d52f1eb1e7398b3e72572652d
1 parent 0437f9a commit 49d9ad0

23 files changed

+292
-106
lines changed

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.materialstudies.reply.ui
22

3+
import android.graphics.drawable.AnimatedVectorDrawable
34
import androidx.appcompat.app.AppCompatActivity
45
import android.os.Bundle
56
import android.view.MenuItem
@@ -112,15 +113,17 @@ class MainActivity : AppCompatActivity(),
112113

113114
private fun setBottomAppBarForHome(@MenuRes menuRes: Int) {
114115
binding.run {
115-
fab.setImageResource(R.drawable.ic_edit_on_secondary)
116+
// TODO(hunterstich) Create ASL which animates back instead of resetting.
117+
(fab.drawable as AnimatedVectorDrawable).reset()
118+
fab.setImageState(intArrayOf(android.R.attr.state_activated), false)
116119
bottomAppBar.replaceMenu(menuRes)
117120
bottomAppBarTitle.visibility = View.VISIBLE
118121
}
119122
}
120123

121124
private fun setBottomAppBarForEmail(@MenuRes menuRes: Int) {
122125
binding.run {
123-
fab.setImageResource(R.drawable.ic_reply_all_on_secondary)
126+
(fab.drawable as AnimatedVectorDrawable).start()
124127
bottomAppBar.replaceMenu(menuRes)
125128
bottomAppBarTitle.visibility = View.INVISIBLE
126129
}

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

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.materialstudies.reply.ui.email
22

3+
import android.animation.AnimatorInflater
34
import android.os.Bundle
5+
import android.transition.TransitionInflater
46
import android.view.LayoutInflater
57
import android.view.View
68
import android.view.ViewGroup
@@ -9,12 +11,17 @@ import androidx.navigation.fragment.findNavController
911
import androidx.navigation.fragment.navArgs
1012
import androidx.recyclerview.widget.GridLayoutManager
1113
import com.materialstudies.reply.App
14+
import com.materialstudies.reply.R
1215
import com.materialstudies.reply.data.EmailStore
1316
import com.materialstudies.reply.databinding.FragmentEmailBinding
17+
import com.materialstudies.reply.util.FastOutUltraSlowIn
1418
import kotlin.LazyThreadSafetyMode.NONE
1519

1620
private const val MAX_GRID_SPANS = 3
1721

22+
/**
23+
* A [Fragment] which displays a single, full email.
24+
*/
1825
class EmailFragment : Fragment() {
1926

2027
private lateinit var binding: FragmentEmailBinding
@@ -24,6 +31,11 @@ class EmailFragment : Fragment() {
2431

2532
private val attachmentAdapter = EmailAttachmentGridAdapter(MAX_GRID_SPANS)
2633

34+
override fun onCreate(savedInstanceState: Bundle?) {
35+
super.onCreate(savedInstanceState)
36+
prepareTransitions()
37+
}
38+
2739
override fun onCreateView(
2840
inflater: LayoutInflater,
2941
container: ViewGroup?,
@@ -38,7 +50,7 @@ class EmailFragment : Fragment() {
3850
emailStore = (requireActivity().application as App).emailStore
3951

4052
binding.navigationIcon.setOnClickListener {
41-
findNavController().popBackStack()
53+
findNavController().navigateUp()
4254
}
4355

4456
val email = emailStore.get(emailId)
@@ -60,10 +72,28 @@ class EmailFragment : Fragment() {
6072
attachmentRecyclerView.adapter = attachmentAdapter
6173
attachmentAdapter.submitList(email.attachments)
6274
}
75+
76+
startTransitions()
77+
}
78+
79+
private fun prepareTransitions() {
80+
postponeEnterTransition()
81+
sharedElementEnterTransition = TransitionInflater.from(context)
82+
.inflateTransition(R.transition.email_card_shared_element_transition).apply {
83+
interpolator = FastOutUltraSlowIn()
84+
}
6385
}
6486

87+
private fun startTransitions() {
88+
binding.executePendingBindings()
89+
startPostponedEnterTransition()
90+
AnimatorInflater.loadAnimator(requireContext(), R.animator.alpha_in).apply {
91+
setTarget(binding.emailCardView)
92+
start()
93+
}
94+
}
6595

6696
private fun showError() {
67-
// TODO: Show error finding email
97+
// Do nothing
6898
}
6999
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.materialstudies.reply.ui.home
22

33
import android.view.LayoutInflater
4+
import android.view.View
45
import android.view.ViewGroup
56
import androidx.recyclerview.widget.ListAdapter
67
import com.materialstudies.reply.data.Email
@@ -15,7 +16,7 @@ class EmailAdapter(
1516
) : ListAdapter<Email, EmailViewHolder>(EmailDiffCallback) {
1617

1718
interface EmailAdapterListener {
18-
fun onEmailClicked(email: Email)
19+
fun onEmailClicked(cardView: View, email: Email)
1920
fun onEmailLongPressed(email: Email): Boolean
2021
fun onEmailStarChanged(email: Email, newValue: Boolean)
2122
fun onEmailArchived(email: Email)

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

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,39 @@
11
package com.materialstudies.reply.ui.home
22

33
import android.os.Bundle
4+
import android.transition.TransitionInflater
45
import android.view.LayoutInflater
56
import android.view.View
67
import android.view.ViewGroup
8+
import androidx.core.view.doOnPreDraw
79
import androidx.fragment.app.Fragment
810
import androidx.lifecycle.Observer
11+
import androidx.navigation.fragment.FragmentNavigatorExtras
912
import androidx.navigation.fragment.findNavController
1013
import com.materialstudies.reply.App
1114
import com.materialstudies.reply.R
1215
import com.materialstudies.reply.data.Email
1316
import com.materialstudies.reply.data.EmailStore
1417
import com.materialstudies.reply.databinding.FragmentHomeBinding
1518
import com.materialstudies.reply.ui.MenuBottomSheetDialogFragment
16-
import com.materialstudies.reply.ui.email.EmailFragmentArgs
1719

20+
/**
21+
* A [Fragment] that displays a list of emails.
22+
*/
1823
class HomeFragment : Fragment(), EmailAdapter.EmailAdapterListener {
1924

2025
private lateinit var binding: FragmentHomeBinding
2126

2227
private val emailAdapter = EmailAdapter(this)
2328
private lateinit var emailStore: EmailStore
2429

30+
override fun onCreate(savedInstanceState: Bundle?) {
31+
super.onCreate(savedInstanceState)
32+
// Fade content out when we navigate to a different screen.
33+
exitTransition = TransitionInflater.from(requireContext())
34+
.inflateTransition(R.transition.fade_transition)
35+
}
36+
2537
override fun onCreateView(
2638
inflater: LayoutInflater,
2739
container: ViewGroup?,
@@ -33,6 +45,10 @@ class HomeFragment : Fragment(), EmailAdapter.EmailAdapterListener {
3345

3446
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
3547
super.onViewCreated(view, savedInstanceState)
48+
// Postpone enter transitions to allow shared element transitions to run.
49+
// https://github.com/googlesamples/android-architecture-components/issues/495
50+
postponeEnterTransition()
51+
view.doOnPreDraw { startPostponedEnterTransition() }
3652

3753
emailStore = (requireActivity().application as App).emailStore
3854

@@ -43,15 +59,15 @@ class HomeFragment : Fragment(), EmailAdapter.EmailAdapterListener {
4359
})
4460
}
4561

46-
override fun onEmailClicked(email: Email) {
47-
findNavController().navigate(
48-
HomeFragmentDirections.actionHomeFragmentToEmailFragment(email.id)
49-
)
62+
override fun onEmailClicked(cardView: View, email: Email) {
63+
val extras = FragmentNavigatorExtras(cardView to cardView.transitionName)
64+
val directions = HomeFragmentDirections.actionHomeFragmentToEmailFragment(email.id)
65+
findNavController().navigate(directions, extras)
5066
}
5167

5268
override fun onEmailLongPressed(email: Email): Boolean {
5369
MenuBottomSheetDialogFragment(R.menu.email_bottom_sheet_menu) {
54-
// TODO: Handle on menu item clicks
70+
// Do nothing.
5571
true
5672
}.show(requireFragmentManager(), null)
5773

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.materialstudies.reply.util
2+
3+
import android.view.animation.Interpolator
4+
import androidx.core.view.animation.PathInterpolatorCompat
5+
6+
/**
7+
* A custom Interpolator that dramatically slows as an animation end, avoiding sudden motion
8+
* stops for large moving components (ie. shared element cards).
9+
*/
10+
class FastOutUltraSlowIn : Interpolator {
11+
12+
private val pathInterpolator = PathInterpolatorCompat.create(
13+
0.185F,
14+
0.770F,
15+
0.135F,
16+
0.975F
17+
)
18+
19+
override fun getInterpolation(fraction: Float): Float {
20+
return pathInterpolator.getInterpolation(fraction)
21+
}
22+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ fun TextView.setTextAppearanceCompat(context: Context, resId: Int) {
2222
* Helper method to get the MaterialShapeDrawable background of MaterialCardView. This should be
2323
* fixed in a future update to Material Components.
2424
*
25-
* TODO: Remove once fix lands.
25+
* TODO(https://issuetracker.google.com/issues/135604742) Remove once fix lands.
2626
*/
2727
val MaterialCardView.backgroundShapeDrawable: MaterialShapeDrawable
2828
get() = (this.background as InsetDrawable).drawable as MaterialShapeDrawable
@@ -31,7 +31,7 @@ val MaterialCardView.backgroundShapeDrawable: MaterialShapeDrawable
3131
* Helper method to get the MaterialShapeDrawable foreground of MaterialCardView. This should be
3232
* fixed in a future update to Material Components.
3333
*
34-
* TODO: Remove once fix lands.
34+
* TODO(https://issuetracker.google.com/issues/135604742) Remove once fix lands.
3535
*/
3636
val MaterialCardView.foregroundShapeDrawable: MaterialShapeDrawable
3737
get() = (((this.foreground as InsetDrawable).drawable as LayerDrawable)

app/src/main/res/anim/bottom_sheet_slide_in.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<set xmlns:android="http://schemas.android.com/apk/res/android"
3-
android:duration="@integer/modal_bottom_sheet_show_duration"
4-
android:interpolator="@android:anim/accelerate_decelerate_interpolator">
3+
android:duration="@integer/reply_motion_short_duration"
4+
android:interpolator="@android:interpolator/fast_out_slow_in">
55

66
<translate
77
android:fromYDelta="20%p"

app/src/main/res/anim/bottom_sheet_slide_out.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<set xmlns:android="http://schemas.android.com/apk/res/android"
3-
android:duration="@integer/modal_bottom_sheet_hide_duration"
4-
android:interpolator="@android:anim/accelerate_interpolator">
3+
android:duration="@integer/reply_motion_short_duration"
4+
android:interpolator="@android:interpolator/fast_out_slow_in">
55

66
<translate
77
android:fromYDelta="0"
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<set xmlns:android="http://schemas.android.com/apk/res/android">
3+
<objectAnimator
4+
android:propertyName="alpha"
5+
android:duration="@integer/reply_motion_short_duration"
6+
android:interpolator="@android:interpolator/fast_out_slow_in"
7+
android:valueFrom="0F"
8+
android:valueTo="1F"/>
9+
</set>

app/src/main/res/animator/fab_hide.xml

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,21 @@
44
android:propertyName="scale"
55
android:valueFrom="1"
66
android:valueTo="0"
7-
android:duration="@integer/fab_hide_duration"
8-
android:startOffset="0"
9-
android:repeatCount="0"
7+
android:duration="@integer/reply_motion_micro_duration"
108
android:repeatMode="restart"
11-
android:interpolator="@interpolator/mtrl_fast_out_slow_in"/>
9+
android:interpolator="@android:interpolator/fast_out_slow_in"/>
1210
<objectAnimator
1311
android:propertyName="iconScale"
1412
android:valueFrom="1"
1513
android:valueTo="0"
16-
android:duration="@integer/fab_hide_duration"
17-
android:startOffset="0"
18-
android:repeatCount="0"
14+
android:duration="@integer/reply_motion_micro_duration"
1915
android:repeatMode="restart"
20-
android:interpolator="@interpolator/mtrl_fast_out_slow_in"/>
16+
android:interpolator="@android:interpolator/fast_out_slow_in"/>
2117
<objectAnimator
2218
android:propertyName="opacity"
2319
android:valueFrom="1"
2420
android:valueTo="0"
25-
android:duration="@integer/fab_hide_duration"
26-
android:startOffset="0"
27-
android:repeatCount="0"
21+
android:duration="@integer/reply_motion_micro_duration"
2822
android:repeatMode="restart"
29-
android:interpolator="@interpolator/mtrl_fast_out_slow_in"/>
23+
android:interpolator="@android:interpolator/fast_out_slow_in"/>
3024
</set>

0 commit comments

Comments
 (0)