【问题标题】:Navigation Component - Shared Element Transition not working at all导航组件 - 共享元素转换根本不起作用
【发布时间】:2019-08-27 07:36:09
【问题描述】:

我在我的项目中使用了最新的导航组件 (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 本身是注入到 ViewModel 中的通用调用(例如 splashToLanding()openDetail(id: UUID))的接口 (INavigationManager),并带有一个额外的接口来处理 NavComponent 的特定位:

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>

登陆.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 库,但无济于事。

【问题讨论】:

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


    【解决方案1】:

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

    类似这样的:

    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)
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-06-25
      • 1970-01-01
      • 1970-01-01
      • 2018-09-22
      • 1970-01-01
      • 1970-01-01
      • 2019-10-11
      相关资源
      最近更新 更多