소스 검색

Replace Resume FAB reveal animation with container transform (#6250)

Ivan Iskandar 3 년 전
부모
커밋
bdef2cfdfb

+ 1 - 1
app/build.gradle.kts

@@ -244,7 +244,7 @@ dependencies {
     implementation("com.github.tachiyomiorg:DirectionalViewPager:1.0.0") {
         exclude(group = "androidx.viewpager", module = "viewpager")
     }
-    implementation("dev.chrisbanes.insetter:insetter:0.6.0")
+    implementation("dev.chrisbanes.insetter:insetter:0.6.1")
 
     // Conductor
     val conductorVersion = "3.0.0"

+ 8 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt

@@ -8,6 +8,7 @@ import android.os.Build
 import android.os.Bundle
 import android.view.Gravity
 import android.view.ViewGroup
+import android.view.Window
 import android.widget.Toast
 import androidx.appcompat.view.ActionMode
 import androidx.core.animation.doOnEnd
@@ -28,6 +29,7 @@ import com.bluelinelabs.conductor.ControllerChangeHandler
 import com.bluelinelabs.conductor.Router
 import com.google.android.material.appbar.AppBarLayout
 import com.google.android.material.navigation.NavigationBarView
+import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback
 import dev.chrisbanes.insetter.applyInsetter
 import eu.kanade.tachiyomi.BuildConfig
 import eu.kanade.tachiyomi.Migrations
@@ -99,6 +101,11 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
         // Prevent splash screen showing up on configuration changes
         val splashScreen = if (savedInstanceState == null) installSplashScreen() else null
 
+        // Set up shared element transition and disable overlay so views don't show above system bars
+        window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
+        setExitSharedElementCallback(MaterialContainerTransformSharedElementCallback())
+        window.sharedElementsUseOverlay = false
+
         super.onCreate(savedInstanceState)
 
         val didMigration = if (savedInstanceState == null) Migrations.upgrade(preferences) else false
@@ -117,6 +124,7 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
         // Draw edge-to-edge
         WindowCompat.setDecorFitsSystemWindows(window, false)
         binding.fabLayout.rootFab.applyInsetter {
+            ignoreVisibility(true)
             type(navigationBars = true) {
                 margin()
             }

+ 14 - 33
app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt

@@ -1,8 +1,7 @@
 package eu.kanade.tachiyomi.ui.manga
 
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
 import android.app.Activity
+import android.app.ActivityOptions
 import android.content.Context
 import android.content.Intent
 import android.graphics.Bitmap
@@ -90,7 +89,6 @@ import eu.kanade.tachiyomi.util.storage.getUriCompat
 import eu.kanade.tachiyomi.util.system.logcat
 import eu.kanade.tachiyomi.util.system.toShareIntent
 import eu.kanade.tachiyomi.util.system.toast
-import eu.kanade.tachiyomi.util.view.getCoordinates
 import eu.kanade.tachiyomi.util.view.shrinkOnScroll
 import eu.kanade.tachiyomi.util.view.snack
 import eu.kanade.tachiyomi.widget.materialdialogs.QuadStateTextView
@@ -369,18 +367,7 @@ class MangaController :
         fab.setOnClickListener {
             val item = presenter.getNextUnreadChapter()
             if (item != null) {
-                // Get coordinates and start animation
-                actionFab?.getCoordinates()?.let { coordinates ->
-                    binding.revealView.showRevealEffect(
-                        coordinates.x,
-                        coordinates.y,
-                        object : AnimatorListenerAdapter() {
-                            override fun onAnimationStart(animation: Animator?) {
-                                openChapter(item.chapter, true)
-                            }
-                        }
-                    )
-                }
+                openChapter(item.chapter, it)
             }
         }
     }
@@ -413,20 +400,6 @@ class MangaController :
         super.onDestroyView(view)
     }
 
-    override fun onActivityResumed(activity: Activity) {
-        if (view == null) return
-
-        // Check if animation view is visible
-        if (binding.revealView.isVisible) {
-            // Show the unreveal effect
-            actionFab?.getCoordinates()?.let { coordinates ->
-                binding.revealView.hideRevealEffect(coordinates.x, coordinates.y, 1920)
-            }
-        }
-
-        super.onActivityResumed(activity)
-    }
-
     override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
         inflater.inflate(R.menu.manga, menu)
     }
@@ -914,13 +887,21 @@ class MangaController :
         }
     }
 
-    fun openChapter(chapter: Chapter, hasAnimation: Boolean = false) {
+    private fun openChapter(chapter: Chapter, sharedElement: View? = null) {
         val activity = activity ?: return
         val intent = ReaderActivity.newIntent(activity, presenter.manga, chapter)
-        if (hasAnimation) {
-            intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
+        activity.apply {
+            if (sharedElement != null) {
+                val activityOptions = ActivityOptions.makeSceneTransitionAnimation(
+                    activity,
+                    sharedElement,
+                    ReaderActivity.SHARED_ELEMENT_NAME
+                )
+                startActivity(intent, activityOptions.toBundle())
+            } else {
+                startActivity(intent)
+            }
         }
-        startActivity(intent)
     }
 
     override fun onItemClick(view: View?, position: Int): Boolean {

+ 25 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt

@@ -22,7 +22,9 @@ import android.view.KeyEvent
 import android.view.Menu
 import android.view.MenuItem
 import android.view.MotionEvent
+import android.view.View
 import android.view.View.LAYER_TYPE_HARDWARE
+import android.view.Window
 import android.view.WindowManager
 import android.view.animation.Animation
 import android.view.animation.AnimationUtils
@@ -39,6 +41,8 @@ import androidx.lifecycle.lifecycleScope
 import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
 import com.google.android.material.shape.MaterialShapeDrawable
 import com.google.android.material.slider.Slider
+import com.google.android.material.transition.platform.MaterialContainerTransform
+import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback
 import dev.chrisbanes.insetter.applyInsetter
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.models.Chapter
@@ -105,6 +109,8 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
 
         private const val ENABLED_BUTTON_IMAGE_ALPHA = 255
         private const val DISABLED_BUTTON_IMAGE_ALPHA = 64
+
+        const val SHARED_ELEMENT_NAME = "reader_shared_element_root"
     }
 
     private val preferences: PreferencesHelper by injectLazy()
@@ -150,6 +156,17 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
      */
     override fun onCreate(savedInstanceState: Bundle?) {
         applyAppTheme(preferences)
+
+        // Setup shared element transitions
+        window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
+        findViewById<View>(android.R.id.content).transitionName = SHARED_ELEMENT_NAME
+        setEnterSharedElementCallback(MaterialContainerTransformSharedElementCallback())
+        window.sharedElementEnterTransition = buildContainerTransform(true)
+        window.sharedElementReturnTransition = buildContainerTransform(false)
+
+        // Postpone custom transition until manga ready
+        postponeEnterTransition()
+
         super.onCreate(savedInstanceState)
 
         binding = ReaderActivityBinding.inflate(layoutInflater)
@@ -295,6 +312,12 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
         return handled || super.dispatchGenericMotionEvent(event)
     }
 
+    private fun buildContainerTransform(entering: Boolean): MaterialContainerTransform {
+        return MaterialContainerTransform(this, entering).apply {
+            addTarget(android.R.id.content)
+        }
+    }
+
     /**
      * Initializes the reader menu. It sets up click listeners and the initial visibility.
      */
@@ -613,6 +636,8 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
             }
         }
         binding.readerContainer.addView(loadingIndicator)
+
+        startPostponedEnterTransition()
     }
 
     private fun showReadingModeToast(mode: Int) {

+ 0 - 73
app/src/main/java/eu/kanade/tachiyomi/widget/RevealAnimationView.kt

@@ -1,73 +0,0 @@
-package eu.kanade.tachiyomi.widget
-
-import android.animation.Animator
-import android.content.Context
-import android.util.AttributeSet
-import android.view.View
-import android.view.ViewAnimationUtils
-import androidx.core.animation.doOnEnd
-import androidx.core.view.isInvisible
-import androidx.core.view.isVisible
-
-class RevealAnimationView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
-    View(context, attrs) {
-
-    /**
-     * Hides the animation view with a animation
-     *
-     * @param centerX x starting point
-     * @param centerY y starting point
-     * @param initialRadius size of radius of animation
-     */
-    fun hideRevealEffect(centerX: Int, centerY: Int, initialRadius: Int) {
-        // Make the view visible.
-        this.isVisible = true
-
-        // Create the animation (the final radius is zero).
-        val anim = ViewAnimationUtils.createCircularReveal(
-            this,
-            centerX,
-            centerY,
-            initialRadius.toFloat(),
-            0f
-        )
-
-        // Set duration of animation.
-        anim.duration = 500
-
-        // make the view invisible when the animation is done
-        anim.doOnEnd {
-            [email protected] = true
-        }
-
-        anim.start()
-    }
-
-    /**
-     * Fills the animation view with a animation
-     *
-     * @param centerX x starting point
-     * @param centerY y starting point
-     * @param listener animation listener
-     */
-    fun showRevealEffect(centerX: Int, centerY: Int, listener: Animator.AnimatorListener) {
-        this.isVisible = true
-
-        val height = this.height
-
-        // Create animation
-        val anim = ViewAnimationUtils.createCircularReveal(
-            this,
-            centerX,
-            centerY,
-            0f,
-            height.toFloat()
-        )
-
-        // Set duration of animation
-        anim.duration = 350
-
-        anim.addListener(listener)
-        anim.start()
-    }
-}

+ 0 - 8
app/src/main/res/layout-sw720dp/manga_controller.xml

@@ -6,14 +6,6 @@
     android:layout_height="match_parent"
     android:orientation="vertical">
 
-    <eu.kanade.tachiyomi.widget.RevealAnimationView
-        android:id="@+id/reveal_view"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:background="?attr/colorAccent"
-        android:elevation="5dp"
-        android:visibility="invisible" />
-
     <eu.kanade.tachiyomi.widget.ThemedSwipeRefreshLayout
         android:id="@+id/swipe_refresh"
         android:layout_width="match_parent"

+ 0 - 8
app/src/main/res/layout/manga_controller.xml

@@ -6,14 +6,6 @@
     android:layout_height="match_parent"
     android:orientation="vertical">
 
-    <eu.kanade.tachiyomi.widget.RevealAnimationView
-        android:id="@+id/reveal_view"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:background="?attr/colorAccent"
-        android:elevation="5dp"
-        android:visibility="invisible" />
-
     <eu.kanade.tachiyomi.widget.ThemedSwipeRefreshLayout
         android:id="@+id/swipe_refresh"
         android:layout_width="match_parent"