2个RecyclerViews之间的Android共享元素转换

Posted

技术标签:

【中文标题】2个RecyclerViews之间的Android共享元素转换【英文标题】:Android shared element transition between 2 RecyclerViews 【发布时间】:2019-01-20 16:31:41 【问题描述】:

我在 2 个活动(MainActivityDetailActivity)中的 2 个 RecyclerView 项目之间使用默认共享元素转换。从MainActivityDetailActivity 的动画效果很好,但是如果用户滚动到DetailActivity 中的新项目,则重新输入动画会将项目移到顶部。我根据需要修改了android Developers Blog 上共享的示例。这是我的代码的Github Link。我还尝试在 DetailActivity 上禁用退出动画,并尝试将退出动画更改为仅淡入淡出,但这几乎就像根本不尊重退出动画一样。

这是一个视频演示(可以在最后几秒钟看到问题):

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_
    android:layout_
    android:animateLayoutChanges="false"
    >

    <View android:id="@+id/otherContent"
        android:layout_
        android:layout_
        android:background="@android:color/holo_green_light"
        />

    <android.support.v7.widget.RecyclerView
        android:id="@+id/itemGallery"
        android:layout_
        android:layout_
        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_
    android:layout_
    android:background="@android:color/black"
    android:animateLayoutChanges="false"
    >
    <android.support.v7.widget.RecyclerView
        android:id="@+id/detailGallery"
        android:layout_
        android:layout_
        android:orientation="horizontal"
        app:layoutManager="android.support.v7.widget.LinearLayoutManager" />

</FrameLayout>

【问题讨论】:

我不知道我是否错过了它,但我找不到当您从 DetailActivity 返回 MainActivity 时将 SharedElement 更改为新项目的代码。我希望这是有道理的 刚刚被审查删除了我的答案 - 尽管它可能是正确的答案 - 只是因为我在那里要求提供更多细节,我不会再浪费时间了...... @ArchieG.Quiñones:抱歉,我无法关注。您介意发布答案吗?伪代码也会有所帮助。在 onBackPressed() 我无权访问共享元素。 @MartinZeitler 不确定哪些审核政策阻止了您。如果您可以在评论中询问,我可以添加更多详细信息。您是否可以将您的想法概括为要点并在 cmets 中分享链接?如果可行,我可以呼吁 SO 模组接受您的回答。 如果你把这个项目的代码分享给我,我会更好地帮助你。我认为这里的问题是,从详细活动返回到 MainActivity 后,您必须将共享元素转换重新设置为正确的项目。 【参考方案1】:

请检查GithubLink上的共享元素转换代码

SharedElementTransition-master.zip 是来自您的源代码的更新代码,Transition 在两个 RecyclerView 之间工作。

android-gallery-master.zip 是另一个在 RecyclerView 和 ViewPager 之间进行转换的代码。

希望它对你有用。我会尽快添加解释。

【讨论】:

谢谢!重新进入动画现在完美。从 MainActivity 到 DetailActivity 的过渡看起来不像从 Detail 到 Main 的返回那么顺利。正常吗?如果您能帮助解决这个问题并添加一些关于您所做更改的解释,那就太好了,因为我没有看到提交历史记录。我现在将答案标记为已接受,但希望您能尽快更新答案! @Jaguar 我会尽快检查 MainActivity 到 DetailActivity 的过渡部分,并会尽快提供解释。过渡在第二个演示中运行得非常顺利,即 RecyclerView 和 ViewPager 之间的过渡。看看它,请保持您的 Internet 上的 bz 图像是从其中的 URL 加载的。您也可以使用该代码作为替代解决方案。很高兴为您提供帮助。 不幸的是,我不能使用 ViewPager,因为它是一个照片应用程序,我可能有 100 张图片 - 否则 Google 使用 ViewPager 的示例(我在我的问题中发布)是一个很好的例子。跨度> 好吧。然后我将再次检查 Enter Transition 代码,使其与 exit 一样顺畅。 @Jaguar 请在 MainActivity.kt 中添加 window.exitTransition = null 并在 setContentView 下面的 DetailActivity.kt 中添加 window.enterTransition = null; 并检查 Enter Transition 是否正常工作或不。如果您仍然遇到问题,请告诉我。

以上是关于2个RecyclerViews之间的Android共享元素转换的主要内容,如果未能解决你的问题,请参考以下文章

两个recyclerviews的Android ItemTouchHelper

ViewPager 中的 Recyclerviews

带有recyclerviews的碎片需要很长时间才能加载

Android:以编程方式在片段中添加多个 RecyclerView

在滚动之前,RecyclerView项目有时无法正确显示(仅限Genymotion平板电脑)

Android Stackoverflow错误:ViewGroup.jumpDrawablesToCurrentState