如何使用Android导航组件实现从RecyclerView项到片段的共享过渡元素?

问题描述 投票:8回答:3

我有一个非常简单的案例。我想实现recyclerViewfragment中项目之间的共享元素过渡。我在我的应用程序中使用android导航组件。

[有关developer.android上的共享转换和stackoverflow上的主题的文章,但该解决方案仅适用于位于fragment布局中的视图,该视图开始转换,但不适用于RecyclerView中的项目。在github上也有一个lib,但我不想依靠第三者的lib来自己做。

是否有解决方案?也许应该工作,这只是一个错误?但是我还没有找到任何信息。

代码示例:

转换开始

class TransitionStartFragment: Fragment() {

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    return inflater.inflate(R.layout.fragment_transition_start, container, false)
    }

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    val testData = listOf("one", "two", "three")
    val adapter = TestAdapter(testData, View.OnClickListener { transitionWithTextViewInRecyclerViewItem(it) })
    val recyclerView = view.findViewById<RecyclerView>(R.id.test_list)
    recyclerView.adapter = adapter
    val button = view.findViewById<Button>(R.id.open_transition_end_fragment)
    button.setOnClickListener { transitionWithTextViewInFragment() }
    }

private fun transitionWithTextViewInFragment(){
    val destination = TransitionStartFragmentDirections.openTransitionEndFragment()
    val extras = FragmentNavigatorExtras(transition_start_text to "transitionTextEnd")
    findNavController().navigate(destination, extras)
    }

private fun transitionWithTextViewInRecyclerViewItem(view: View){
    val destination = TransitionStartFragmentDirections.openTransitionEndFragment()
    val extras = FragmentNavigatorExtras(view to "transitionTextEnd")
    findNavController().navigate(destination, extras)
   }

}

布局

<androidx.constraintlayout.widget.ConstraintLayout 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:orientation="vertical">

<TextView
    android:id="@+id/transition_start_text"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="transition"
    android:transitionName="transitionTextStart"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

<Button
    android:id="@+id/open_transition_end_fragment"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintTop_toBottomOf="@id/transition_start_text"
    android:text="open transition end fragment" />

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/test_list"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layout_constraintTop_toBottomOf="@id/open_transition_end_fragment"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />

recyclerView的适配器

class TestAdapter(
    private val items: List<String>,
    private val onItemClickListener: View.OnClickListener
) : RecyclerView.Adapter<TestAdapter.ViewHodler>() {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHodler {
    return ViewHodler(LayoutInflater.from(parent.context).inflate(R.layout.item_test, parent, false))
    }

override fun getItemCount(): Int {
    return items.size
    }

override fun onBindViewHolder(holder: ViewHodler, position: Int) {
    val item = items[position]
    holder.transitionText.text = item
    holder.itemView.setOnClickListener { onItemClickListener.onClick(holder.transitionText) }

    }

class ViewHodler(itemView: View) : RecyclerView.ViewHolder(itemView) {
    val transitionText = itemView.findViewById<TextView>(R.id.item_test_text)
    }
}

在onItemClick中,我在recyclerView中传递textView表单项进行转换

转换结束

class TransitionEndFragment : Fragment() {

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    setUpTransition()
    return inflater.inflate(R.layout.fragment_transition_end, container, false)
    }

private fun setUpTransition(){
    sharedElementEnterTransition = TransitionInflater.from(context).inflateTransition(android.R.transition.move)

    }
}

布局

<androidx.constraintlayout.widget.ConstraintLayout 
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<TextView
    android:id="@+id/transition_end_text"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="transition"
    android:transitionName="transitionTextEnd"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

有趣的transitionWithTextViewInFragment()-具有过渡。

有趣的transitionWithTextViewInRecyclerViewItem(视图:视图)-无过渡。

android android-recyclerview shared-element-transition android-architecture-navigation
3个回答
6
投票

为了解决退货过渡问题,您需要在初始化回收者视图的源片段(带有回收者视图的片段)上添加此行

// your recyclerView
recyclerView.apply {
                ...
                adapter = myAdapter
                postponeEnterTransition()
                viewTreeObserver
                    .addOnPreDrawListener {
                        startPostponedEnterTransition()
                        true
                    }
}

4
投票

这是我的RecyclerView示例,具有片段共享过渡。在我的适配器中,我根据位置为每个项目设置了不同的过渡名称(在我的示例中是ImageView)。

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    val item = items[position]
    holder.itemView.txtView.text=item
    ViewCompat.setTransitionName(holder.itemView.imgViewIcon, "Test_$position")
    holder.setClickListener(object : ViewHolder.ClickListener {
        override fun onClick(v: View, position: Int) {
            when (v.id) {
                R.id.linearLayout -> listener.onClick(item, holder.itemView.imgViewIcon, position)
            }
        }
    })

}

并且当单击项目时,我的接口在源片段中实现:

override fun onClick(text: String, img: ImageView, position: Int) {
    val action = MainFragmentDirections.actionMainFragmentToSecondFragment(text, position)
    val extras = FragmentNavigator.Extras.Builder()
            .addSharedElement(img, ViewCompat.getTransitionName(img)!!)
            .build()
    NavHostFragment.findNavController(this@MainFragment).navigate(action, extras)
}

在我的目标片段中:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    info("onCreate")
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        sharedElementEnterTransition = TransitionInflater.from(context).inflateTransition(android.R.transition.move)
    }
}

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    info("onCreateView")
    return inflater.inflate(R.layout.fragment_second, container, false)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    info("onViewCreated")
    val name=SecondFragmentArgs.fromBundle(arguments).name
    val position=SecondFragmentArgs.fromBundle(arguments).position
    txtViewName.text=name
    ViewCompat.setTransitionName(imgViewSecond, "Test_$position")
}

0
投票

与返回转换时在SO上遇到了同样的问题,但对我来说,问题的根本原因是Navigation当前仅将replace用于片段事务,并且这导致我在起始片段中的回收器每次都重新加载您回击了,这本身就是一个问题。

因此,通过解决第二个问题(根问题),返回过渡开始工作,而没有延迟的动画。对于那些想要在回击时保持初始状态的人,我所做的是:

仅在onCreateView中添加一个简单的支票

private lateinit var binding: FragmentSearchResultsBinding

override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return if (::binding.isInitialized) {
            binding.root
        } else {
            binding = DataBindingUtil.inflate(inflater, R.layout.fragment_search_results, container, false)

            with(binding) {
                //doing some stuff here
                root
            }
        }

所以这里三连胜:没有重画回收站,没有从服务器进行重新提取,并且返回转换也按预期工作。

© www.soinside.com 2019 - 2024. All rights reserved.