I am using default shared element transitions between 2 RecyclerView
items in 2 activities (MainActivity
and DetailActivity
). Animation from MainActivity
to DetailActivity
is working fine, but if user has scrolled to new item in the DetailActivity
, then reenter animation shifts the item to top. I modified the sample shared on Android Developers Blog for my needs. Here's the Github Link to my code. I have also tried disabling exit animations on the DetailActivity
, and tried changing exit animations to fade only, but it's almost like exit animations are not being respected at all.
Here's a video demo (issue can be seen in the last couple of seconds):
MainActivity:
class MainActivity : AppCompatActivity(), ListImageAdapter.ListImageClickListener {
private lateinit var imageData: ImageData
private lateinit var listImageAdapter: ListImageAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupGallery()
prepareTransitions()
}
@SuppressLint("RestrictedApi")
override fun onListImageClick(position: Int, imageView: ImageView) {
val intent = Intent(this, DetailActivity::class.java)
intent.putExtra("IMAGE_DATA", imageData)
val activityOptions = ActivityOptions.makeSceneTransitionAnimation(this, imageView,
ViewCompat.getTransitionName(imageView))
startActivityForResult(intent, 101, activityOptions.toBundle())
}
override fun onActivityReenter(resultCode: Int, data: Intent?) {
data?.let { intent ->
if (intent.hasExtra("IMAGE_DATA")) {
imageData = intent.getParcelableExtra("IMAGE_DATA")
listImageAdapter.images = imageData.images
val position = imageData.images.indexOfFirst { it.selected }
itemGallery.scrollToPosition(position)
}
}
super.onActivityReenter(resultCode, data)
}
private fun setupGallery() {
imageData = ImageData(getGalleryItems())
val snapHelper = PagerSnapHelper()
snapHelper.attachToRecyclerView(itemGallery)
listImageAdapter = ListImageAdapter(imageData.images, this)
itemGallery.adapter = listImageAdapter
itemGallery.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
val selectedView = snapHelper.findSnapView(itemGallery.layoutManager)
selectedView?.let {
val selectedPosition = itemGallery.layoutManager?.getPosition(selectedView)
selectedPosition?.let { onMediumGalleryItemHighlighted(selectedPosition) }
}
}
}
})
}
private fun onMediumGalleryItemHighlighted(position: Int) {
imageData.images = imageData.images.mapIndexed { index, galleryItem ->
when {
index == position -> galleryItem.copy(selected = true)
galleryItem.selected -> galleryItem.copy(selected = false)
else -> galleryItem
}
}
}
private fun prepareTransitions() {
setExitSharedElementCallback(
object : SharedElementCallback() {
override fun onMapSharedElements(names: List<String>?, sharedElements: MutableMap<String, View>?) {
val selectedPosition = imageData.images.indexOfFirst { it.selected }
val selectedViewHolder = itemGallery
.findViewHolderForAdapterPosition(selectedPosition)
if (selectedViewHolder?.itemView == null) {
return
}
sharedElements!![names!![0]] = selectedViewHolder.itemView.findViewById(R.id.listItemImage)
}
})
}
private fun getGalleryItems(): List<Image> {
return listOf(
Image(R.drawable.cat, true),
Image(R.drawable.lion, false),
Image(R.drawable.tortoise, false)
)
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="false"
>
<View android:id="@+id/otherContent"
android:layout_width="match_parent"
android:layout_height="256dp"
android:background="@android:color/holo_green_light"
/>
<android.support.v7.widget.RecyclerView
android:id="@+id/itemGallery"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:layout_below="@+id/otherContent"
app:layoutManager="android.support.v7.widget.LinearLayoutManager" />
</RelativeLayout>
DetailActivity:
class DetailActivity : AppCompatActivity() {
private lateinit var detailImageAdapter: DetailImageAdapter
private lateinit var imageData: ImageData
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_detail)
imageData = intent.extras.getParcelable("IMAGE_DATA")
initViews()
prepareTransitions()
resetScrolledPosition()
}
private fun initViews() {
val snapHelper = PagerSnapHelper()
snapHelper.attachToRecyclerView(detailGallery)
detailImageAdapter = DetailImageAdapter(imageData.images)
detailGallery.adapter = detailImageAdapter
detailGallery.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
val selectedView = snapHelper.findSnapView(detailGallery.layoutManager)
selectedView?.let {
val selectedPosition = detailGallery.layoutManager?.getPosition(selectedView)
selectedPosition?.let { onItemSelected(selectedPosition) }
}
}
}
})
}
private fun resetScrolledPosition() {
val position = imageData.images.indexOfFirst { it.selected }
imageData.images = imageData.images.mapIndexed { index, galleryItem ->
when {
index == position -> {
galleryItem.copy(selected = true)
}
galleryItem.selected -> galleryItem.copy(selected = false)
else -> galleryItem
}
}
detailImageAdapter.images = imageData.images
detailGallery.scrollToPosition(position)
supportStartPostponedEnterTransition()
}
private fun onItemSelected(position: Int) {
imageData.images = imageData.images.mapIndexed { index, galleryItem ->
when {
index == position -> galleryItem.copy(selected = true)
galleryItem.selected -> galleryItem.copy(selected = false)
else -> galleryItem
}
}
}
override fun onBackPressed() {
var resultIntent = Intent()
resultIntent = resultIntent.putExtra("IMAGE_DATA", imageData)
setResult(Activity.RESULT_OK, resultIntent)
super.onBackPressed()
}
private fun prepareTransitions() {
setEnterSharedElementCallback(
object : SharedElementCallback() {
override fun onMapSharedElements(names: List<String>?, sharedElements: MutableMap<String, View>?) {
val selectedPosition = imageData.images.indexOfFirst { it.selected }
val selectedViewHolder = detailGallery.findViewHolderForAdapterPosition(selectedPosition)
if (selectedViewHolder?.itemView == null) {
return
}
sharedElements!![names!![0]] = selectedViewHolder.itemView.findViewById(R.id.detailItemImage)
}
})
}
}
activity_detail.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"
android:animateLayoutChanges="false"
>
<android.support.v7.widget.RecyclerView
android:id="@+id/detailGallery"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
app:layoutManager="android.support.v7.widget.LinearLayoutManager" />
</FrameLayout>
RelativeLayout
is at fault. the policy might be not to ask in answers for clarification, and no placeholder answers - while one cannot even prepare/save drafts of answers, in order not to post an answer and improve it later. – Archivist