• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 
17 package com.android.deskclock
18 
19 import android.animation.ValueAnimator
20 import android.view.View
21 import android.widget.AbsListView
22 import android.widget.ListView
23 import androidx.recyclerview.widget.RecyclerView
24 
25 import com.android.deskclock.data.DataModel
26 import com.android.deskclock.uidata.TabScrollListener
27 import com.android.deskclock.uidata.UiDataModel
28 
29 /**
30  * This controller encapsulates the logic that watches a model for changes to scroll state and
31  * updates the display state of an associated drop shadow. The observable model may take many forms
32  * including ListViews, RecyclerViews and this application's UiDataModel. Each of these models can
33  * indicate when content is scrolled to its top. When the content is scrolled to the top the drop
34  * shadow is hidden and the content appears flush with the app bar. When the content is scrolled
35  * up the drop shadow is displayed making the content appear to scroll below the app bar.
36  */
37 class DropShadowController private constructor(
38     /** The component that displays a drop shadow.  */
39     private val mDropShadowView: View
40 ) {
41     /** Updates [.mDropShadowView] in response to changes in the backing scroll model.  */
42     private val mScrollChangeWatcher = ScrollChangeWatcher()
43 
44     /** Fades the [@mDropShadowView] in/out as scroll state changes.  */
45     private val mDropShadowAnimator: ValueAnimator =
46             AnimatorUtils.getAlphaAnimator(mDropShadowView, 0f, 1f)
47                     .setDuration(UiDataModel.uiDataModel.shortAnimationDuration)
48 
49     /** Tab bar's hairline, which is hidden whenever the drop shadow is displayed.  */
50     private var mHairlineView: View? = null
51 
52     // Supported sources of scroll position include: ListView, RecyclerView and UiDataModel.
53     private var mRecyclerView: RecyclerView? = null
54     private var mUiDataModel: UiDataModel? = null
55     private var mListView: ListView? = null
56 
57     /**
58      * @param dropShadowView to be hidden/shown as `uiDataModel` reports scrolling changes
59      * @param uiDataModel models the vertical scrolling state of the application's selected tab
60      * @param hairlineView at the bottom of the tab bar to be hidden or shown when the drop shadow
61      * is displayed or hidden, respectively.
62      */
63     constructor(
64         dropShadowView: View,
65         uiDataModel: UiDataModel,
66         hairlineView: View
67     ) : this(dropShadowView) {
68         mUiDataModel = uiDataModel
69         mUiDataModel?.addTabScrollListener(mScrollChangeWatcher)
70         mHairlineView = hairlineView
71         updateDropShadow(!uiDataModel.isSelectedTabScrolledToTop)
72     }
73 
74     /**
75      * @param dropShadowView to be hidden/shown as `listView` reports scrolling changes
76      * @param listView a scrollable view that dictates the visibility of `dropShadowView`
77      */
78     constructor(dropShadowView: View, listView: ListView) : this(dropShadowView) {
79         mListView = listView
80         mListView?.setOnScrollListener(mScrollChangeWatcher)
81         updateDropShadow(!Utils.isScrolledToTop(listView))
82     }
83 
84     /**
85      * @param dropShadowView to be hidden/shown as `recyclerView` reports scrolling changes
86      * @param recyclerView a scrollable view that dictates the visibility of `dropShadowView`
87      */
88     constructor(dropShadowView: View, recyclerView: RecyclerView) : this(dropShadowView) {
89         mRecyclerView = recyclerView
90         mRecyclerView?.addOnScrollListener(mScrollChangeWatcher)
91         updateDropShadow(!Utils.isScrolledToTop(recyclerView))
92     }
93 
94     /**
95      * Stop updating the drop shadow in response to scrolling changes. Stop listening to the backing
96      * scrollable entity for changes. This is important to avoid memory leaks.
97      */
stopnull98     fun stop() {
99         when {
100             mRecyclerView != null -> mRecyclerView?.removeOnScrollListener(mScrollChangeWatcher)
101             mListView != null -> mListView?.setOnScrollListener(null)
102             mUiDataModel != null -> mUiDataModel?.removeTabScrollListener(mScrollChangeWatcher)
103         }
104     }
105 
106     /**
107      * @param shouldShowDropShadow `true` indicates the drop shadow should be displayed;
108      * `false` indicates the drop shadow should be hidden
109      */
updateDropShadownull110     private fun updateDropShadow(shouldShowDropShadow: Boolean) {
111         if (!shouldShowDropShadow && mDropShadowView.alpha != 0f) {
112             if (DataModel.dataModel.isApplicationInForeground) {
113                 mDropShadowAnimator.reverse()
114             } else {
115                 mDropShadowView.alpha = 0f
116             }
117             mHairlineView?.visibility = View.VISIBLE
118         }
119         if (shouldShowDropShadow && mDropShadowView.alpha != 1f) {
120             if (DataModel.dataModel.isApplicationInForeground) {
121                 mDropShadowAnimator.start()
122             } else {
123                 mDropShadowView.alpha = 1f
124             }
125             mHairlineView?.visibility = View.INVISIBLE
126         }
127     }
128 
129     /**
130      * Update the drop shadow as the scrollable entity is scrolled.
131      */
132     private inner class ScrollChangeWatcher
133         : RecyclerView.OnScrollListener(), TabScrollListener, AbsListView.OnScrollListener {
134         // RecyclerView scrolled.
onScrollednull135         override fun onScrolled(view: RecyclerView, dx: Int, dy: Int) {
136             updateDropShadow(!Utils.isScrolledToTop(view))
137         }
138 
139         // ListView scrolled.
onScrollStateChangednull140         override fun onScrollStateChanged(view: AbsListView, scrollState: Int) {
141         }
142 
onScrollnull143         override fun onScroll(
144             view: AbsListView,
145             firstVisibleItem: Int,
146             visibleItemCount: Int,
147             totalItemCount: Int
148         ) {
149             updateDropShadow(!Utils.isScrolledToTop(view))
150         }
151 
152         // UiDataModel reports scroll change.
selectedTabScrollToTopChangednull153         override fun selectedTabScrollToTopChanged(
154             selectedTab: UiDataModel.Tab,
155             scrolledToTop: Boolean
156         ) {
157             updateDropShadow(!scrolledToTop)
158         }
159     }
160 }