From 2ec9df27b8a12558fbef6b526f43c680530b96ff Mon Sep 17 00:00:00 2001 From: andhie Date: Sun, 23 Sep 2018 03:09:03 +0800 Subject: [PATCH 1/7] Hero image transition --- .../apps/sunflower/PlantDetailFragment.kt | 39 +++++++++++++ .../apps/sunflower/PlantListFragment.kt | 7 +++ .../apps/sunflower/adapters/PlantAdapter.kt | 24 +++++--- .../adapters/PlantDetailBindingAdapters.kt | 7 ++- .../apps/sunflower/utilities/MoveViews.kt | 57 +++++++++++++++++++ .../main/res/layout/fragment_plant_detail.xml | 7 ++- app/src/main/res/layout/list_item_plant.xml | 5 +- app/src/main/res/navigation/nav_garden.xml | 6 +- 8 files changed, 135 insertions(+), 17 deletions(-) create mode 100644 app/src/main/java/com/google/samples/apps/sunflower/utilities/MoveViews.kt diff --git a/app/src/main/java/com/google/samples/apps/sunflower/PlantDetailFragment.kt b/app/src/main/java/com/google/samples/apps/sunflower/PlantDetailFragment.kt index 9115ef5c9..61e153311 100644 --- a/app/src/main/java/com/google/samples/apps/sunflower/PlantDetailFragment.kt +++ b/app/src/main/java/com/google/samples/apps/sunflower/PlantDetailFragment.kt @@ -17,6 +17,7 @@ package com.google.samples.apps.sunflower import android.content.Intent +import android.graphics.drawable.Drawable import android.os.Build import android.os.Bundle import android.view.LayoutInflater @@ -26,13 +27,20 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.core.app.ShareCompat +import androidx.core.view.ViewCompat import androidx.databinding.DataBindingUtil import androidx.fragment.app.Fragment +import androidx.interpolator.view.animation.FastOutSlowInInterpolator import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.engine.GlideException +import com.bumptech.glide.request.RequestListener +import com.bumptech.glide.request.target.Target import com.google.android.material.snackbar.Snackbar import com.google.samples.apps.sunflower.databinding.FragmentPlantDetailBinding import com.google.samples.apps.sunflower.utilities.InjectorUtils +import com.google.samples.apps.sunflower.utilities.MoveViews import com.google.samples.apps.sunflower.viewmodels.PlantDetailViewModel /** @@ -61,6 +69,9 @@ class PlantDetailFragment : Fragment() { plantDetailViewModel.addPlantToGarden() Snackbar.make(view, R.string.added_plant_to_garden, Snackbar.LENGTH_LONG).show() } + + ViewCompat.setTransitionName(detailImage, plantId) + requestListener = imageListener } plantDetailViewModel.plant.observe(this, Observer { plant -> @@ -71,6 +82,11 @@ class PlantDetailFragment : Fragment() { } }) + postponeEnterTransition() // wait for Glide callback to start transition + sharedElementEnterTransition = MoveViews().apply { + interpolator = FastOutSlowInInterpolator() // Material standard easing + } + setHasOptionsMenu(true) return binding.root @@ -105,4 +121,27 @@ class PlantDetailFragment : Fragment() { else -> super.onOptionsItemSelected(item) } } + + val imageListener = object : RequestListener { + override fun onLoadFailed( + e: GlideException?, + model: Any?, + target: Target?, + isFirstResource: Boolean + ): Boolean { + startPostponedEnterTransition() + return false + } + + override fun onResourceReady( + resource: Drawable?, + model: Any?, + target: Target?, + dataSource: DataSource?, + isFirstResource: Boolean + ): Boolean { + startPostponedEnterTransition() + return false + } + } } diff --git a/app/src/main/java/com/google/samples/apps/sunflower/PlantListFragment.kt b/app/src/main/java/com/google/samples/apps/sunflower/PlantListFragment.kt index 208d25aed..6c60a82eb 100644 --- a/app/src/main/java/com/google/samples/apps/sunflower/PlantListFragment.kt +++ b/app/src/main/java/com/google/samples/apps/sunflower/PlantListFragment.kt @@ -23,6 +23,7 @@ import android.view.MenuInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup +import androidx.core.view.doOnLayout import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders @@ -50,6 +51,12 @@ class PlantListFragment : Fragment() { binding.plantList.adapter = adapter subscribeUi(adapter) + // wait RecyclerView to layout for detail to list image return animation + postponeEnterTransition() + binding.plantList.doOnLayout { + startPostponedEnterTransition() + } + setHasOptionsMenu(true) return binding.root } diff --git a/app/src/main/java/com/google/samples/apps/sunflower/adapters/PlantAdapter.kt b/app/src/main/java/com/google/samples/apps/sunflower/adapters/PlantAdapter.kt index b2d6c2c3d..09cfc8411 100644 --- a/app/src/main/java/com/google/samples/apps/sunflower/adapters/PlantAdapter.kt +++ b/app/src/main/java/com/google/samples/apps/sunflower/adapters/PlantAdapter.kt @@ -19,7 +19,9 @@ package com.google.samples.apps.sunflower.adapters import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.databinding.DataBindingUtil import androidx.navigation.findNavController +import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.google.samples.apps.sunflower.PlantListFragment @@ -35,8 +37,7 @@ class PlantAdapter : ListAdapter(PlantDiffCallba override fun onBindViewHolder(holder: ViewHolder, position: Int) { val plant = getItem(position) holder.apply { - bind(createOnClickListener(plant.plantId), plant) - itemView.tag = plant + bind(createOnClickListener(), plant) } } @@ -45,10 +46,15 @@ class PlantAdapter : ListAdapter(PlantDiffCallba LayoutInflater.from(parent.context), parent, false)) } - private fun createOnClickListener(plantId: String): View.OnClickListener { - return View.OnClickListener { - val direction = PlantListFragmentDirections.ActionPlantListFragmentToPlantDetailFragment(plantId) - it.findNavController().navigate(direction) + private fun createOnClickListener(): OnPlantItemClickListener { + return object : OnPlantItemClickListener { + override fun onPlantItemClick(rootView: View, plant: Plant) { + val binding = DataBindingUtil.getBinding(rootView) + val navigatorExtras = FragmentNavigatorExtras(binding!!.plantItemImage to plant.plantId) + + val direction = PlantListFragmentDirections.ActionPlantListFragmentToPlantDetailFragment(plant.plantId) + rootView.findNavController().navigate(direction, navigatorExtras) + } } } @@ -56,7 +62,7 @@ class PlantAdapter : ListAdapter(PlantDiffCallba private val binding: ListItemPlantBinding ) : RecyclerView.ViewHolder(binding.root) { - fun bind(listener: View.OnClickListener, item: Plant) { + fun bind(listener: OnPlantItemClickListener, item: Plant) { binding.apply { clickListener = listener plant = item @@ -64,4 +70,8 @@ class PlantAdapter : ListAdapter(PlantDiffCallba } } } + + interface OnPlantItemClickListener { + fun onPlantItemClick(rootView: View, plant: Plant) + } } \ No newline at end of file diff --git a/app/src/main/java/com/google/samples/apps/sunflower/adapters/PlantDetailBindingAdapters.kt b/app/src/main/java/com/google/samples/apps/sunflower/adapters/PlantDetailBindingAdapters.kt index 327d4c468..d30355b46 100644 --- a/app/src/main/java/com/google/samples/apps/sunflower/adapters/PlantDetailBindingAdapters.kt +++ b/app/src/main/java/com/google/samples/apps/sunflower/adapters/PlantDetailBindingAdapters.kt @@ -16,6 +16,7 @@ package com.google.samples.apps.sunflower.adapters +import android.graphics.drawable.Drawable import android.text.SpannableStringBuilder import android.widget.ImageView import android.widget.TextView @@ -24,15 +25,17 @@ import androidx.core.text.italic import androidx.databinding.BindingAdapter import com.bumptech.glide.Glide import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions +import com.bumptech.glide.request.RequestListener import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.samples.apps.sunflower.R -@BindingAdapter("imageFromUrl") -fun bindImageFromUrl(view: ImageView, imageUrl: String?) { +@BindingAdapter("imageFromUrl", "requestListener", requireAll = false) +fun bindImageFromUrl(view: ImageView, imageUrl: String?, listener: RequestListener?) { if (!imageUrl.isNullOrEmpty()) { Glide.with(view.context) .load(imageUrl) .transition(DrawableTransitionOptions.withCrossFade()) + .listener(listener) .into(view) } } diff --git a/app/src/main/java/com/google/samples/apps/sunflower/utilities/MoveViews.kt b/app/src/main/java/com/google/samples/apps/sunflower/utilities/MoveViews.kt new file mode 100644 index 000000000..c00c0ff54 --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/sunflower/utilities/MoveViews.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.sunflower.utilities + +import android.content.Context +import android.util.AttributeSet +import androidx.transition.ChangeBounds +import androidx.transition.ChangeImageTransform +import androidx.transition.ChangeTransform +import androidx.transition.TransitionSet + +class MoveViews : TransitionSet { + + constructor() { + init() + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init() + } + + private fun init() { + addTransition(ChangeBounds()) + .addTransition(ChangeTransform()) + .addTransition(ChangeImageTransform()) + } +} diff --git a/app/src/main/res/layout/fragment_plant_detail.xml b/app/src/main/res/layout/fragment_plant_detail.xml index e44fbce61..76c27918c 100644 --- a/app/src/main/res/layout/fragment_plant_detail.xml +++ b/app/src/main/res/layout/fragment_plant_detail.xml @@ -22,6 +22,10 @@ + + + app:layout_collapseMode="parallax" + app:requestListener="@{requestListener}" /> + type="com.google.samples.apps.sunflower.adapters.PlantAdapter.OnPlantItemClickListener"/> @@ -30,7 +30,7 @@ + android:onClick="@{v -> clickListener.onPlantItemClick(v, plant)}"> + app:destination="@id/plant_detail_fragment" /> Date: Sun, 23 Sep 2018 13:02:16 +0800 Subject: [PATCH 2/7] Remove dupe copyright header --- .../apps/sunflower/utilities/MoveViews.kt | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/app/src/main/java/com/google/samples/apps/sunflower/utilities/MoveViews.kt b/app/src/main/java/com/google/samples/apps/sunflower/utilities/MoveViews.kt index c00c0ff54..c8796e084 100644 --- a/app/src/main/java/com/google/samples/apps/sunflower/utilities/MoveViews.kt +++ b/app/src/main/java/com/google/samples/apps/sunflower/utilities/MoveViews.kt @@ -14,22 +14,6 @@ * limitations under the License. */ -/* - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package com.google.samples.apps.sunflower.utilities import android.content.Context From f926cc8e61a546f0890353709e593a4d7c7821d1 Mon Sep 17 00:00:00 2001 From: Andhie Date: Wed, 19 Dec 2018 17:00:30 +0800 Subject: [PATCH 3/7] Remove custom clicklistener --- .../apps/sunflower/adapters/PlantAdapter.kt | 29 ++++++++++--------- app/src/main/res/layout/list_item_plant.xml | 4 +-- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/google/samples/apps/sunflower/adapters/PlantAdapter.kt b/app/src/main/java/com/google/samples/apps/sunflower/adapters/PlantAdapter.kt index 09cfc8411..52a7eeebc 100644 --- a/app/src/main/java/com/google/samples/apps/sunflower/adapters/PlantAdapter.kt +++ b/app/src/main/java/com/google/samples/apps/sunflower/adapters/PlantAdapter.kt @@ -37,7 +37,8 @@ class PlantAdapter : ListAdapter(PlantDiffCallba override fun onBindViewHolder(holder: ViewHolder, position: Int) { val plant = getItem(position) holder.apply { - bind(createOnClickListener(), plant) + bind(createOnClickListener(plant.plantId), plant) + itemView.tag = plant } } @@ -46,23 +47,27 @@ class PlantAdapter : ListAdapter(PlantDiffCallba LayoutInflater.from(parent.context), parent, false)) } - private fun createOnClickListener(): OnPlantItemClickListener { - return object : OnPlantItemClickListener { - override fun onPlantItemClick(rootView: View, plant: Plant) { - val binding = DataBindingUtil.getBinding(rootView) - val navigatorExtras = FragmentNavigatorExtras(binding!!.plantItemImage to plant.plantId) + private fun createOnClickListener(plantId: String): View.OnClickListener { + return View.OnClickListener { view -> + val direction = PlantListFragmentDirections + .ActionPlantListFragmentToPlantDetailFragment(plantId) - val direction = PlantListFragmentDirections.ActionPlantListFragmentToPlantDetailFragment(plant.plantId) - rootView.findNavController().navigate(direction, navigatorExtras) + DataBindingUtil.getBinding(view)?.let { + val navigatorExtras = FragmentNavigatorExtras(it.plantItemImage to plantId) + view.findNavController().navigate(direction, navigatorExtras) + + } ?: run { + // fail to getBinding for transition anim. we still proceed to navigate + view.findNavController().navigate(direction) } } } class ViewHolder( - private val binding: ListItemPlantBinding + private val binding: ListItemPlantBinding ) : RecyclerView.ViewHolder(binding.root) { - fun bind(listener: OnPlantItemClickListener, item: Plant) { + fun bind(listener: View.OnClickListener, item: Plant) { binding.apply { clickListener = listener plant = item @@ -70,8 +75,4 @@ class PlantAdapter : ListAdapter(PlantDiffCallba } } } - - interface OnPlantItemClickListener { - fun onPlantItemClick(rootView: View, plant: Plant) - } } \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_plant.xml b/app/src/main/res/layout/list_item_plant.xml index b48f31b91..2ce99374c 100644 --- a/app/src/main/res/layout/list_item_plant.xml +++ b/app/src/main/res/layout/list_item_plant.xml @@ -21,7 +21,7 @@ + type="android.view.View.OnClickListener"/> @@ -30,7 +30,7 @@ + android:onClick="@{clickListener}"> Date: Wed, 19 Dec 2018 17:35:04 +0800 Subject: [PATCH 4/7] spotlessApply --- .../com/google/samples/apps/sunflower/adapters/PlantAdapter.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/com/google/samples/apps/sunflower/adapters/PlantAdapter.kt b/app/src/main/java/com/google/samples/apps/sunflower/adapters/PlantAdapter.kt index 52a7eeebc..c52ec6a5b 100644 --- a/app/src/main/java/com/google/samples/apps/sunflower/adapters/PlantAdapter.kt +++ b/app/src/main/java/com/google/samples/apps/sunflower/adapters/PlantAdapter.kt @@ -55,7 +55,6 @@ class PlantAdapter : ListAdapter(PlantDiffCallba DataBindingUtil.getBinding(view)?.let { val navigatorExtras = FragmentNavigatorExtras(it.plantItemImage to plantId) view.findNavController().navigate(direction, navigatorExtras) - } ?: run { // fail to getBinding for transition anim. we still proceed to navigate view.findNavController().navigate(direction) @@ -64,7 +63,7 @@ class PlantAdapter : ListAdapter(PlantDiffCallba } class ViewHolder( - private val binding: ListItemPlantBinding + private val binding: ListItemPlantBinding ) : RecyclerView.ViewHolder(binding.root) { fun bind(listener: View.OnClickListener, item: Plant) { From 0c7b02c083ec2e43ff1eb6b672681276bae5560a Mon Sep 17 00:00:00 2001 From: Andhie Date: Sun, 23 Dec 2018 11:21:21 +0800 Subject: [PATCH 5/7] Tweak transition anim --- .../apps/sunflower/PlantDetailFragment.kt | 29 ++++++-- .../apps/sunflower/PlantListFragment.kt | 10 +++ .../apps/sunflower/utilities/AnimUtils.kt | 68 +++++++++++++++++++ app/src/main/res/values/integer.xml | 21 ++++++ 4 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/com/google/samples/apps/sunflower/utilities/AnimUtils.kt create mode 100644 app/src/main/res/values/integer.xml diff --git a/app/src/main/java/com/google/samples/apps/sunflower/PlantDetailFragment.kt b/app/src/main/java/com/google/samples/apps/sunflower/PlantDetailFragment.kt index 61e153311..0bb1d06ce 100644 --- a/app/src/main/java/com/google/samples/apps/sunflower/PlantDetailFragment.kt +++ b/app/src/main/java/com/google/samples/apps/sunflower/PlantDetailFragment.kt @@ -30,15 +30,16 @@ import androidx.core.app.ShareCompat import androidx.core.view.ViewCompat import androidx.databinding.DataBindingUtil import androidx.fragment.app.Fragment -import androidx.interpolator.view.animation.FastOutSlowInInterpolator import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders +import androidx.transition.Fade import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.target.Target import com.google.android.material.snackbar.Snackbar import com.google.samples.apps.sunflower.databinding.FragmentPlantDetailBinding +import com.google.samples.apps.sunflower.utilities.AnimUtils import com.google.samples.apps.sunflower.utilities.InjectorUtils import com.google.samples.apps.sunflower.utilities.MoveViews import com.google.samples.apps.sunflower.viewmodels.PlantDetailViewModel @@ -83,9 +84,7 @@ class PlantDetailFragment : Fragment() { }) postponeEnterTransition() // wait for Glide callback to start transition - sharedElementEnterTransition = MoveViews().apply { - interpolator = FastOutSlowInInterpolator() // Material standard easing - } + setupTransition() setHasOptionsMenu(true) @@ -144,4 +143,26 @@ class PlantDetailFragment : Fragment() { return false } } + + private fun setupTransition() { + // Animations when List entering Detail + sharedElementEnterTransition = MoveViews().apply { + interpolator = AnimUtils.getFastOutSlowInInterpolator() + duration = resources.getInteger(R.integer.config_duration_area_large_expand).toLong() + } + enterTransition = Fade().apply { + interpolator = AnimUtils.getLinearOutSlowInInterpolator() + startDelay = resources.getInteger(R.integer.config_duration_area_large_expand).toLong() + } + + // Animations when Detail retuning to List + sharedElementReturnTransition = MoveViews().apply { + interpolator = AnimUtils.getFastOutSlowInInterpolator() + duration = resources.getInteger(R.integer.config_duration_area_large_collapse).toLong() + } + returnTransition = Fade().apply { + interpolator = AnimUtils.getFastOutLinearInInterpolator() + duration = resources.getInteger(R.integer.config_duration_area_small).toLong() + } + } } diff --git a/app/src/main/java/com/google/samples/apps/sunflower/PlantListFragment.kt b/app/src/main/java/com/google/samples/apps/sunflower/PlantListFragment.kt index 6c60a82eb..187f30af8 100644 --- a/app/src/main/java/com/google/samples/apps/sunflower/PlantListFragment.kt +++ b/app/src/main/java/com/google/samples/apps/sunflower/PlantListFragment.kt @@ -27,8 +27,10 @@ import androidx.core.view.doOnLayout import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders +import androidx.transition.Fade import com.google.samples.apps.sunflower.adapters.PlantAdapter import com.google.samples.apps.sunflower.databinding.FragmentPlantListBinding +import com.google.samples.apps.sunflower.utilities.AnimUtils import com.google.samples.apps.sunflower.utilities.InjectorUtils import com.google.samples.apps.sunflower.viewmodels.PlantListViewModel @@ -56,6 +58,7 @@ class PlantListFragment : Fragment() { binding.plantList.doOnLayout { startPostponedEnterTransition() } + setupTransition() setHasOptionsMenu(true) return binding.root @@ -90,4 +93,11 @@ class PlantListFragment : Fragment() { } } } + + private fun setupTransition() { + exitTransition = Fade().apply { + interpolator = AnimUtils.getFastOutSlowInInterpolator() + duration = resources.getInteger(R.integer.config_duration_area_small).toLong() + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/google/samples/apps/sunflower/utilities/AnimUtils.kt b/app/src/main/java/com/google/samples/apps/sunflower/utilities/AnimUtils.kt new file mode 100644 index 000000000..b7f43fb51 --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/sunflower/utilities/AnimUtils.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.sunflower.utilities + +import android.view.animation.Interpolator +import androidx.interpolator.view.animation.FastOutLinearInInterpolator +import androidx.interpolator.view.animation.FastOutSlowInInterpolator +import androidx.interpolator.view.animation.LinearOutSlowInInterpolator + +object AnimUtils { + + private val fastOutSlowIn by lazy { FastOutSlowInInterpolator() } + private val fastOutLinearIn by lazy { FastOutLinearInInterpolator() } + private val linearOutSlowIn by lazy { LinearOutSlowInInterpolator() } + + /** + * Elements that begin and end at rest use standard easing. They speed up quickly + * and slow down gradually, in order to emphasize the end of the transition. + * + * Suitable timing for animating visible Views moving around on screen. + * + * See + * https://material.io/design/motion/speed.html#easing + */ + fun getFastOutSlowInInterpolator(): Interpolator? { + return fastOutSlowIn + } + + /** + * Incoming elements are animated using deceleration easing, which starts a transition + * at peak velocity (the fastest point of an element’s movement) and ends at rest. + * + * Suitable timing for animating Views entering a screen + * + * See + * https://material.io/design/motion/speed.html#easing + */ + fun getFastOutLinearInInterpolator(): Interpolator? { + return fastOutLinearIn + } + + /** + * Elements exiting a screen use acceleration easing, where they start at rest + * and end at peak velocity. + * + * Suitable timing for animating Views exiting a screen + * + * See + * https://material.io/design/motion/speed.html#easing + */ + fun getLinearOutSlowInInterpolator(): Interpolator? { + return linearOutSlowIn + } +} \ No newline at end of file diff --git a/app/src/main/res/values/integer.xml b/app/src/main/res/values/integer.xml new file mode 100644 index 000000000..0e490332e --- /dev/null +++ b/app/src/main/res/values/integer.xml @@ -0,0 +1,21 @@ + + + + + + + 100 + + + 250 + + + 200 + + + 300 + + + 250 + + \ No newline at end of file From 69a748b8a0f8cf9631f3cd1437f02f9f2f3ca59f Mon Sep 17 00:00:00 2001 From: Andhie Date: Sun, 23 Dec 2018 11:21:21 +0800 Subject: [PATCH 6/7] Tweak transition anim --- .../apps/sunflower/PlantDetailFragment.kt | 29 ++++++-- .../apps/sunflower/PlantListFragment.kt | 10 +++ .../apps/sunflower/utilities/AnimUtils.kt | 68 +++++++++++++++++++ app/src/main/res/values/integer.xml | 21 ++++++ 4 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/com/google/samples/apps/sunflower/utilities/AnimUtils.kt create mode 100644 app/src/main/res/values/integer.xml diff --git a/app/src/main/java/com/google/samples/apps/sunflower/PlantDetailFragment.kt b/app/src/main/java/com/google/samples/apps/sunflower/PlantDetailFragment.kt index 61e153311..0bb1d06ce 100644 --- a/app/src/main/java/com/google/samples/apps/sunflower/PlantDetailFragment.kt +++ b/app/src/main/java/com/google/samples/apps/sunflower/PlantDetailFragment.kt @@ -30,15 +30,16 @@ import androidx.core.app.ShareCompat import androidx.core.view.ViewCompat import androidx.databinding.DataBindingUtil import androidx.fragment.app.Fragment -import androidx.interpolator.view.animation.FastOutSlowInInterpolator import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders +import androidx.transition.Fade import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.target.Target import com.google.android.material.snackbar.Snackbar import com.google.samples.apps.sunflower.databinding.FragmentPlantDetailBinding +import com.google.samples.apps.sunflower.utilities.AnimUtils import com.google.samples.apps.sunflower.utilities.InjectorUtils import com.google.samples.apps.sunflower.utilities.MoveViews import com.google.samples.apps.sunflower.viewmodels.PlantDetailViewModel @@ -83,9 +84,7 @@ class PlantDetailFragment : Fragment() { }) postponeEnterTransition() // wait for Glide callback to start transition - sharedElementEnterTransition = MoveViews().apply { - interpolator = FastOutSlowInInterpolator() // Material standard easing - } + setupTransition() setHasOptionsMenu(true) @@ -144,4 +143,26 @@ class PlantDetailFragment : Fragment() { return false } } + + private fun setupTransition() { + // Animations when List entering Detail + sharedElementEnterTransition = MoveViews().apply { + interpolator = AnimUtils.getFastOutSlowInInterpolator() + duration = resources.getInteger(R.integer.config_duration_area_large_expand).toLong() + } + enterTransition = Fade().apply { + interpolator = AnimUtils.getLinearOutSlowInInterpolator() + startDelay = resources.getInteger(R.integer.config_duration_area_large_expand).toLong() + } + + // Animations when Detail retuning to List + sharedElementReturnTransition = MoveViews().apply { + interpolator = AnimUtils.getFastOutSlowInInterpolator() + duration = resources.getInteger(R.integer.config_duration_area_large_collapse).toLong() + } + returnTransition = Fade().apply { + interpolator = AnimUtils.getFastOutLinearInInterpolator() + duration = resources.getInteger(R.integer.config_duration_area_small).toLong() + } + } } diff --git a/app/src/main/java/com/google/samples/apps/sunflower/PlantListFragment.kt b/app/src/main/java/com/google/samples/apps/sunflower/PlantListFragment.kt index 6c60a82eb..187f30af8 100644 --- a/app/src/main/java/com/google/samples/apps/sunflower/PlantListFragment.kt +++ b/app/src/main/java/com/google/samples/apps/sunflower/PlantListFragment.kt @@ -27,8 +27,10 @@ import androidx.core.view.doOnLayout import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders +import androidx.transition.Fade import com.google.samples.apps.sunflower.adapters.PlantAdapter import com.google.samples.apps.sunflower.databinding.FragmentPlantListBinding +import com.google.samples.apps.sunflower.utilities.AnimUtils import com.google.samples.apps.sunflower.utilities.InjectorUtils import com.google.samples.apps.sunflower.viewmodels.PlantListViewModel @@ -56,6 +58,7 @@ class PlantListFragment : Fragment() { binding.plantList.doOnLayout { startPostponedEnterTransition() } + setupTransition() setHasOptionsMenu(true) return binding.root @@ -90,4 +93,11 @@ class PlantListFragment : Fragment() { } } } + + private fun setupTransition() { + exitTransition = Fade().apply { + interpolator = AnimUtils.getFastOutSlowInInterpolator() + duration = resources.getInteger(R.integer.config_duration_area_small).toLong() + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/google/samples/apps/sunflower/utilities/AnimUtils.kt b/app/src/main/java/com/google/samples/apps/sunflower/utilities/AnimUtils.kt new file mode 100644 index 000000000..b7f43fb51 --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/sunflower/utilities/AnimUtils.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.sunflower.utilities + +import android.view.animation.Interpolator +import androidx.interpolator.view.animation.FastOutLinearInInterpolator +import androidx.interpolator.view.animation.FastOutSlowInInterpolator +import androidx.interpolator.view.animation.LinearOutSlowInInterpolator + +object AnimUtils { + + private val fastOutSlowIn by lazy { FastOutSlowInInterpolator() } + private val fastOutLinearIn by lazy { FastOutLinearInInterpolator() } + private val linearOutSlowIn by lazy { LinearOutSlowInInterpolator() } + + /** + * Elements that begin and end at rest use standard easing. They speed up quickly + * and slow down gradually, in order to emphasize the end of the transition. + * + * Suitable timing for animating visible Views moving around on screen. + * + * See + * https://material.io/design/motion/speed.html#easing + */ + fun getFastOutSlowInInterpolator(): Interpolator? { + return fastOutSlowIn + } + + /** + * Incoming elements are animated using deceleration easing, which starts a transition + * at peak velocity (the fastest point of an element’s movement) and ends at rest. + * + * Suitable timing for animating Views entering a screen + * + * See + * https://material.io/design/motion/speed.html#easing + */ + fun getFastOutLinearInInterpolator(): Interpolator? { + return fastOutLinearIn + } + + /** + * Elements exiting a screen use acceleration easing, where they start at rest + * and end at peak velocity. + * + * Suitable timing for animating Views exiting a screen + * + * See + * https://material.io/design/motion/speed.html#easing + */ + fun getLinearOutSlowInInterpolator(): Interpolator? { + return linearOutSlowIn + } +} \ No newline at end of file diff --git a/app/src/main/res/values/integer.xml b/app/src/main/res/values/integer.xml new file mode 100644 index 000000000..0e490332e --- /dev/null +++ b/app/src/main/res/values/integer.xml @@ -0,0 +1,21 @@ + + + + + + + 100 + + + 250 + + + 200 + + + 300 + + + 250 + + \ No newline at end of file From 0eb2ae81daad7db613f520a54c8b61bb1d6ed181 Mon Sep 17 00:00:00 2001 From: Andhie Date: Wed, 16 Jan 2019 12:27:49 +0800 Subject: [PATCH 7/7] spotlessApply --- .../com/google/samples/apps/sunflower/adapters/PlantAdapter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/google/samples/apps/sunflower/adapters/PlantAdapter.kt b/app/src/main/java/com/google/samples/apps/sunflower/adapters/PlantAdapter.kt index 9887bb3a1..23f5e25c4 100644 --- a/app/src/main/java/com/google/samples/apps/sunflower/adapters/PlantAdapter.kt +++ b/app/src/main/java/com/google/samples/apps/sunflower/adapters/PlantAdapter.kt @@ -64,7 +64,7 @@ class PlantAdapter : ListAdapter(PlantDiffCallba } class ViewHolder( - private val binding: ListItemPlantBinding + private val binding: ListItemPlantBinding ) : RecyclerView.ViewHolder(binding.root) { fun bind(listener: View.OnClickListener, item: Plant) {