material-components / material-components-android

Modular and customizable Material Design UI components for Android
Apache License 2.0
16.38k stars 3.08k forks source link

[Motion] Unloaded ImageView when returning from MaterialContainerTransform #1707

Open thibseisel opened 4 years ago

thibseisel commented 4 years ago

Description: I'm using MaterialContainerTransform to transition from Fragment A, to a Fragment B that contains a RecyclerView whose items have ShapeableImageViews. Note: Fragment A is part of a ViewPager2, hosted by Fragment Z.

Transitionning from A to B works fine: transition is postponed until B's RecyclerView display is ready. But when returning from B, we notice that images (that are loaded with Glide) are reset and blank.

material_container_bug

Expected behavior: Images loaded into ImageViews should not be reset ; those should be preserved in the return transition. Also, why is SheapeableImageView showing black rounded corners ? This strange behavior of ShapeableImageView is also noticeable on Android Studio's layout preview (corners are then light grey).

Source code:

In Fragment Z:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
  super.onViewCreated(view, savedInstanceState)
  postponeEnterTransition(1000, TimeUnit.MILLISECONDS)
  // Configure ViewPager adapter...
}

In Fragment A:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
  super.onViewCreated(view, savedInstanceState)
  // Some view initialization code [...]
  viewModel.liveData.observe(viewLifecycleOwner) {
    adapter.submitList(it)
    val fragmentZ = requireParentFragment()
    fragmentZ.requireView().parent?.doOnPreDraw {
      fragmentZ.startPostponedEnterTransition()
    }
  }
}

private fun displayFragmentB() {
  // Root item view from the clicked element in RecyclerView. Has transitionName set in onBindViewHolder
  val itemView = ...
  val sharedElementExtras = FragmentNavigatortExtras(itemView to "some_item_id")
  requireParentFragment().apply {
    exitTransition = Hold().apply {
      duration = 300
      addTarget(R.id.fragment_z)
    }
    // Reset transition set by other navigation events
    reenterTransition = null
  }

  findNavController().navigate(R.id.to_fragment_b, sharedElementExtras)
}

In Fragment B:

override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)

  sharedElementEnterTransition = MaterialContainerTransform().apply {
    drawingViewId = R.id.nav_host_fragment
    duration = 300
    scrimColor = Color.TRANSPARENT
    setAllContainerColors(themeColorSurface)
  }
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
  super.onViewCreated(view, savedInstanceState)

  postponeEnterTransition(500, TimeUnit.MILLISECONDS)
  view.transitionName = "some_item_id"

  viewModel.liveData.observe(viewLifecycleOwner) {
    adapter.submitList(it)
    view.parent?.doOnPreDraw {
      startPostponedEnterTransition()
    }
  }
}

Android API version: 29 (Q)

Material Library version: 1.2.1

Device: Nokia 7.1, but also encountered on emulator.

hunterstich commented 4 years ago

Hey @thibseisel,

Thanks so much for taking the time to file an issue! Couple of questions to help us debug:

  1. Does this also happen when you're using standard ImageViews and not ShapableImageViews?
  2. Is there any way you can also supply a sample that repros the issue so we can test and debug on our end?
thibseisel commented 4 years ago

Hi @hunterstich ,

This also happens with regular ImageViews, not only ShapeableImageViews. But it seems that ShapeableImageViews have an additional issue that's visible when using MaterialContainerTransform.

I created a sample project that's very similar in structure in so that you could easily reproduce. Here is the link: material-motion-bug

thibseisel commented 4 years ago

Hi @hunterstich , Any progress on this issue? Did you manage to reproduce the bug from the linked sample project?

Thanks in advance.

mxalbert1996 commented 3 years ago

This is because you are using Glide to load images and Glide automatically clears the loaded images when the fragment is destroyed, as stated in #1734.

hunterstich commented 3 years ago

@thibseisel - thanks for the sample project.

I looked into this. The only workaround I was able to find is to use Glide's with builder that accepts a Context instead of a Fragment to keep Glide from registering with the Fragment's lifecycle and clearing images when the Fragment is destroyed.

So in the case of your sample, you would change AlbumDetailFragments Glide call to:

            Glide.with(requireContext()).asBitmap()
                .load(it.artworkUrl)
                .into(album_art_view)

This works in my testing. Let me know how it goes for you.

thibseisel commented 3 years ago

Thanks @mxalbert1996 and @hunterstich for your answers!

I just managed to make the return transition work for my use case. I've had to change the Fragment passed to Glide to its associated Activity (fragment.requireActivity()) so that images are not cleared before being captured.

There's still a problem with ShapeableImageView's corners, though. With a ShapeAppereance having 8dp rounded corners, black corners artifacts are visible during return transition. You can see those on the GIF I posted in the original post. This is less noticeable but still there.

mxalbert1996 commented 3 years ago

Tying the image to the activity's lifecycle can prevent the problem but that's essentially memory leak and isn't something that can be recommended IMHO. Is there anything that can be done on the MDC side? Since this problem doesn't happen when using regular androidx transitions and capturing the view after the fragment is destroyed or after the view is detached sounds problematic.

kroegerama commented 3 years ago

Same issue with Coil image loading framework. Anything new regarding this bug?

kenshin171 commented 3 years ago

Thanks @mxalbert1996 and @hunterstich for your answers!

I just managed to make the return transition work for my use case. I've had to change the Fragment passed to Glide to its associated Activity (fragment.requireActivity()) so that images are not cleared before being captured.

There's still a problem with ShapeableImageView's corners, though. With a ShapeAppereance having 8dp rounded corners, black corners artifacts are visible during return transition. You can see those on the GIF I posted in the original post. This is less noticeable but still there.

i have a problem with the shapeableimageview with a corner radius having a back artifacts too. is there any solution or workaround for this?

mxalbert1996 commented 3 years ago

@kenshin171 Using standard ImageView and wrapping it with a MaterialCardView should work I believe.

kenshin171 commented 3 years ago

@mxalbert1996 thanks for the suggestion, black artifacts are gone BUT there is another issue where during transition all cardview corner radii are gone. It should be the same as this issue #1542

Dolfik1 commented 2 years ago

I have the same problem. But in my case I also have the same problem with MaterialCardView

Veeksi commented 2 years ago

I am also having this same issue. I also tested with coil but that doesn't help.