Jetpack导航中的共享元素从RecyclerView过渡到详细片段

时间:2018-10-18 12:34:56

标签: android kotlin android-animation android-transitions android-jetpack

我正在尝试使用片段之间共享元素的简单动画进行过渡。在第一个片段中,我在RecyclerView中具有元素,在第二个片段中-顶部完全相同的元素(在单独的xml布局中定义,列表元素也是这种类型)在视图的其余部分中。我为bindViewHolder和目标片段的onCreateView中的所有元素提供了各种transitionNames,我正在阅读它们并将它们设置为我想要进行过渡的元素。无论如何动画是没有发生的,我没有其他想法。在下面,我将源片段和目标片段以及列表适配器中的代码片段放入其中:

ListAdapter:

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    val item = list[position]
    ViewCompat.setTransitionName(holder.view, item.id)
    holder.view.setOnClickListener {
        listener?.onItemSelected(item, holder.view)
    }
    ...
}

interface interactionListener {
    fun onItemSelected(item: ItemData, view: View)
}

ListFragment(来源):

override fun onItemSelected(item: ItemData, view: View) {
    val action = ListFragmentDirections.itemDetailAction(item.id)
    val extras = FragmentNavigatorExtras(view to view.transitionName)
    val data = Bundle()
    data.putString("itemId", item.id)
    findNavController().navigate(action.actionId, data, null, extras)
}

SourceFragmentLayout:

<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
    android:id="@+id/pullToRefresh"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:listitem="@layout/item_overview_row" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

DetailFragment(目标):

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    val rootView = inflater.inflate(R.layout.fragment_detail, container, false)

    val itemId = ItemDetailFragmentArgs.fromBundle(arguments).itemId

    (rootView.findViewById(R.id.includeDetails) as View).transitionName = itemId

    sharedElementEnterTransition = ChangeBounds().apply {
        duration = 750
    }
    sharedElementReturnTransition= ChangeBounds().apply {
        duration = 750
    }
    return rootView
}

DetailFragmentLayout:

<include
android:id="@+id/includeDetails"
layout="@layout/item_overview_row"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

ItemOverviewRowLayout(该元素作为项目包含在recyclerView中,并在目标片段中作为标题):

<androidx.cardview.widget.CardView 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="wrap_content"
android:clickable="true"
android:focusable="true"
android:foreground="?android:attr/selectableItemBackground"
android:orientation="vertical" >

我还使用Jetpack导航制作了另一个应用程序,共享了相同的元素,并使用了相同的layout.xml描述了这些元素,并且由于我没有从recyclerView过渡到目标片段而可以工作。也许我在这里错了,将transitionName设置为目标片段中的找到的视图?我不知道该怎么做,因为由于recyclerView项目的原因,目标包含布局的ID应该是唯一的。

2 个答案:

答案 0 :(得分:4)

好的,我发现输入带有共享元素的动画应该是什么样子: 在DetailFragment(Target)中,您应该在onViewCreated开始时运行 postponeEnterTransition()(我的代码可以从onCreateView移到onViewCreated)。现在您有时间用transitionName签名目标视图元素。在结束加载数据和查看之后,您必须运行 startPostponedEnterTransition()。如果不这样做,则ui会冻结,因此您无法在postponeEnterTransition和startPostponedEnterTransition之间进行耗时的操作。

无论如何,现在问题在于返回转换。当然,这是相同的情况-在发布动画之前,您必须重新加载recyclerView。当然,您也可以使用postponeEnterTransition(即使它是返回转换)。就我而言,我的列表由LiveData包装。在源代码片段中,生命周期观察者正在检查数据。还有另一个挑战-如何确定是否加载了数据。从理论上讲,使用recyclerView可以使用有用的内联函数:

inline fun <T : View> T.afterMeasure(crossinline f: T.() -> Unit) {
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
    override fun onGlobalLayout() {
        if (measuredWidth > 0 && measuredHeight > 0) {
            viewTreeObserver.removeOnGlobalLayoutListener(this)
            f()
        }
    }
})

}

...在应用布局管理器和适配器的代码中,您可以像这样使用它:

recyclerView.afterMeasure { startPostponedEnterTransition() }

应该确定返回动画何时开始工作(您必须确保recyclerView项目中的transitionNames是否正确,以便过渡可以具有目标视图项目)

答案 1 :(得分:0)

从回答来看,使用ViewTreeObserver相当消耗资源。并且还有很多过程要做。因此,我建议您使用 doOnPreDraw ,而不要在测量完recyclerView之后再等待。代码实现将在下面这样。

recyclerView.doOnPreDraw {
    startPostponedEnterTransition()
}