1 /*
2  * Copyright 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package androidx.navigation.ui
17 
18 import android.animation.ObjectAnimator
19 import android.animation.ValueAnimator
20 import android.annotation.SuppressLint
21 import android.content.Context
22 import android.graphics.drawable.Drawable
23 import android.os.Bundle
24 import androidx.annotation.StringRes
25 import androidx.appcompat.graphics.drawable.DrawerArrowDrawable
26 import androidx.navigation.FloatingWindow
27 import androidx.navigation.NavController
28 import androidx.navigation.NavDestination
29 import java.lang.ref.WeakReference
30 
31 /**
32  * The abstract OnDestinationChangedListener for keeping any type of app bar updated. This handles
33  * both updating the title and updating the Up Indicator, transitioning between the drawer icon and
34  * up arrow as needed.
35  */
36 internal abstract class AbstractAppBarOnDestinationChangedListener(
37     private val context: Context,
38     private val configuration: AppBarConfiguration
39 ) : NavController.OnDestinationChangedListener {
40     private val openableLayoutWeakReference =
<lambda>null41         configuration.openableLayout?.run { WeakReference(this) }
42     private var arrowDrawable: DrawerArrowDrawable? = null
43     private var animator: ValueAnimator? = null
44 
setTitlenull45     protected abstract fun setTitle(title: CharSequence?)
46 
47     protected abstract fun setNavigationIcon(icon: Drawable?, @StringRes contentDescription: Int)
48 
49     override fun onDestinationChanged(
50         controller: NavController,
51         destination: NavDestination,
52         arguments: Bundle?
53     ) {
54         if (destination is FloatingWindow) {
55             return
56         }
57         val openableLayout = openableLayoutWeakReference?.get()
58         if (openableLayoutWeakReference != null && openableLayout == null) {
59             controller.removeOnDestinationChangedListener(this)
60             return
61         }
62 
63         val label = destination.fillInLabel(context, arguments)
64         if (label != null) {
65             setTitle(label)
66         }
67 
68         val isTopLevelDestination = configuration.isTopLevelDestination(destination)
69         if (openableLayout == null && isTopLevelDestination) {
70             setNavigationIcon(null, 0)
71         } else {
72             setActionBarUpIndicator(openableLayout != null && isTopLevelDestination)
73         }
74     }
75 
76     @SuppressLint("ObjectAnimatorBinding")
setActionBarUpIndicatornull77     private fun setActionBarUpIndicator(showAsDrawerIndicator: Boolean) {
78         val (arrow, animate) =
79             arrowDrawable?.run { this to true }
80                 ?: DrawerArrowDrawable(context).also { arrowDrawable = it } to false
81 
82         setNavigationIcon(
83             arrow,
84             if (showAsDrawerIndicator) R.string.nav_app_bar_open_drawer_description
85             else R.string.nav_app_bar_navigate_up_description
86         )
87 
88         val endValue = if (showAsDrawerIndicator) 0f else 1f
89         if (animate) {
90             val startValue = arrow.progress
91             animator?.cancel()
92             animator = ObjectAnimator.ofFloat(arrow, "progress", startValue, endValue)
93             (animator as ObjectAnimator).start()
94         } else {
95             arrow.progress = endValue
96         }
97     }
98 }
99