【发布时间】: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