导航组件-共享元素转换根本不起作用

时间:2019-08-27 07:36:09

标签: android android-fragments android-architecture-navigation shared-element-transition

我在项目中使用的是最新的Nav组件(2.2.0-alpha01),但出现了一个似乎无法解决的问题。

我有一个启动动画-没什么大不了的,使用ConstraintLayout的自定义背景延伸到整个屏幕,并且在中间有一个徽标。在初始同步期间,我为自定义动画VectorDrawable动画(我们将其命名为@drawable/logo_animated),该动画使用通用的@drawable/logo作为源,并将动画应用于其组。

为确定动画的正确时间,我创建了以下辅助函数:

fun ImageView.setRepeatingAnimatedVector(
    @DrawableRes animationRes: Int,
    delayMs: Long = 0,
    startDelayMs: Long = 0,
    shouldRunOptional: () -> Boolean = { false },
    optionalRunnable: () -> Unit = {}
) {
    val anim = AnimatedVectorDrawableCompat.create(context, animationRes)?.apply {
        registerAnimationCallback(object : Animatable2Compat.AnimationCallback() {
            override fun onAnimationEnd(drawable: Drawable?) {
                this@setRepeatingAnimatedVector.postDelayed({ if (shouldRunOptional()) optionalRunnable() else start() }, delayMs)
            }
        })
    }
    setImageDrawable(anim)
    postDelayed({ anim?.start() }, startDelayMs)
}

它将AnimatedVectorDrawable作为输入,并将其应用于ImageView。完成动画周期后,将执行lambda(shouldRunOptional)形式的检查。如果返回true,则启动optionalRunnable lambda,否则将重复动画。

有了这个,我可以等待ViewModel完成同步,然后等待动画的结尾在片段之间移动而没有任何怪异。动画本身很短(〜900ms),所以最多用户会延迟一秒钟。

我还使用自定义的NavigationManager组合进行导航。 Manager本身是被注入到ViewModels中的通用调用(例如INavigationManagersplashToLanding())的接口(openDetail(id: UUID)),其中一个额外的接口负责NavComponent的特定位:< / p>

IFragmentNavigator.kt

interface IFragmentNavigator {
    val command: SingleLiveEvent<NavigationCommand>

    var splashLandingExtras: Navigator.Extras?

    fun setSplashLandingTransition(extras: Navigator.Extras) {
        splashLandingExtras = extras
    }

    fun back() {
        navigate(NavigationCommand.Back)
    }

    fun navigate(direction: NavDirections) {
        navigate(NavigationCommand.To(direction))
    }

    fun navigate(navCommand: NavigationCommand) {
        command.postValue(navCommand)
    }
}

该实现只负责属性初始化,然后以下列方式使用:

class FragmentNavigationManager: 
    INavigationManager, IFragmentNavigator by FragmentNavigator() { [...] }

此接口的command属性随后将通过观察者在片段中使用:

open val navigationObserver = Observer<NavigationCommand> {
        when(it) {
            is NavigationCommand.To -> findNavController().navigate(it.directions)
            is NavigationCommand.Back -> findNavController().popBackStack()
            is NavigationCommand.BackTo -> findNavController().popBackStack(it.destinationId, false)
            is NavigationCommand.ToRoot -> TODO()
        }
    }

override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    navigator.command.observe(this, navigationObserver)
}

在FragmentNavigationManager中创建的Directions实例由NavController直接使用。我确保将FragmentNavigator的Extras字段添加到实际的导航调用中:

    override fun splashToLanding() {
        navigate(
            NavigationCommand.To(
                SplashFragmentDirections.actionSplashFragmentToLandingFragment(),
                null, null, splashLandingExtras
            )
        )
    }

当然,在SplashFragment中,我为splashLandingExtras的过渡名称分配了适当的视图:

navigator.splashLandingExtras = FragmentNavigatorExtras(binding.logo to "logo")

在LandingFragment的onCreate方法中,我确实设置了输入和退出动画:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        TransitionInflater.from(context).inflateTransition(android.R.transition.move).let {transition ->
            sharedElementEnterTransition = transition
            sharedElementReturnTransition = transition
        }
    }

布局如下:

splash.xml

<layout 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">

    <data>

        <variable
            name="viewModel"
            type="com.felcana.app.viewmodel.SplashViewModel" />
    </data>

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true">

        <include layout="@layout/background" />

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true">

            <ImageView
                android:id="@+id/logo"
                style="?attr/logoStyle"
                android:transitionName="logo"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

        </androidx.constraintlayout.widget.ConstraintLayout>


    </androidx.coordinatorlayout.widget.CoordinatorLayout>

</layout>

landing.xml

<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <data>

        <variable
            name="viewModel"
            type="com.my.app.viewmodel.LandingViewModel" />
    </data>

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true">

        <include layout="@layout/background" />

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true">

            <ImageView
                android:id="@+id/logo"
                style="?attr/logoStyle"
                android:layout_marginBottom="24dp"
                android:transitionName="logo"
                app:layout_constraintBottom_toTopOf="@id/button_register"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent" />

            <com.google.android.material.button.MaterialButton
                android:id="@+id/button_register"
                style="?attr/flatWhiteButtonStyle"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginStart="24dp"
                android:layout_marginEnd="24dp"
                android:layout_marginBottom="8dp"
                android:onClick="@{() -> viewModel.goToRegister()}"
                android:text="@string/button_register"
                app:layout_constraintBottom_toTopOf="@id/button_login"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent" />

            <com.google.android.material.button.MaterialButton
                android:id="@+id/button_login"
                style="?attr/borderlessWhiteButtonStyle"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="24dp"
                android:onClick="@{() -> viewModel.goToLogin()}"
                android:text="@string/button_login"
                app:layout_constraintBottom_toTopOf="@id/disclaimer"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent" />

            <TextView
                android:id="@+id/disclaimer"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginStart="24dp"
                android:layout_marginEnd="24dp"
                android:layout_marginBottom="32dp"
                android:maxLines="2"
                android:textAlignment="center"
                android:textColor="@color/app_white"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                tools:text="@tools:sample/lorem/random" />

        </androidx.constraintlayout.widget.ConstraintLayout>

    </androidx.coordinatorlayout.widget.CoordinatorLayout>

</layout>

由于某种原因,动画根本无法播放-ImageView只是跳转而没有任何过渡到新位置的动作。

这是怎么回事?根据文档,这应该起作用。我确实尝试过返回更稳定的NavComponent库版本,但无济于事。

1 个答案:

答案 0 :(得分:0)

您是否尝试过推迟LandingFragment::onViewCreated内的enter转换并手动将转换名称设置为视图?

类似这样的东西:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
  // Pause the enter transition
  postponeEnterTransition()

  // Manually apply the transitionName
  logo.transitionName = "logo"

  // Resume the transition
  startPostponedEnterTransition()

  super.onViewCreated(view, savedInstanceState)
}