1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
|
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.deskclock;
import android.animation.ValueAnimator;
import androidx.recyclerview.widget.RecyclerView;
import android.view.View;
import android.widget.AbsListView;
import android.widget.ListView;
import com.android.deskclock.data.DataModel;
import com.android.deskclock.uidata.TabScrollListener;
import com.android.deskclock.uidata.UiDataModel;
import com.android.deskclock.uidata.UiDataModel.Tab;
import static com.android.deskclock.AnimatorUtils.getAlphaAnimator;
/**
* This controller encapsulates the logic that watches a model for changes to scroll state and
* updates the display state of an associated drop shadow. The observable model may take many forms
* including ListViews, RecyclerViews and this application's UiDataModel. Each of these models can
* indicate when content is scrolled to its top. When the content is scrolled to the top the drop
* shadow is hidden and the content appears flush with the app bar. When the content is scrolled
* up the drop shadow is displayed making the content appear to scroll below the app bar.
*/
public final class DropShadowController {
/** Updates {@link #mDropShadowView} in response to changes in the backing scroll model. */
private final ScrollChangeWatcher mScrollChangeWatcher = new ScrollChangeWatcher();
/** Fades the {@link @mDropShadowView} in/out as scroll state changes. */
private final ValueAnimator mDropShadowAnimator;
/** The component that displays a drop shadow. */
private final View mDropShadowView;
/** Tab bar's hairline, which is hidden whenever the drop shadow is displayed. */
private View mHairlineView;
// Supported sources of scroll position include: ListView, RecyclerView and UiDataModel.
private RecyclerView mRecyclerView;
private UiDataModel mUiDataModel;
private ListView mListView;
/**
* @param dropShadowView to be hidden/shown as {@code uiDataModel} reports scrolling changes
* @param uiDataModel models the vertical scrolling state of the application's selected tab
* @param hairlineView at the bottom of the tab bar to be hidden or shown when the drop shadow
* is displayed or hidden, respectively.
*/
public DropShadowController(View dropShadowView, UiDataModel uiDataModel, View hairlineView) {
this(dropShadowView);
mUiDataModel = uiDataModel;
mUiDataModel.addTabScrollListener(mScrollChangeWatcher);
mHairlineView = hairlineView;
updateDropShadow(!uiDataModel.isSelectedTabScrolledToTop());
}
/**
* @param dropShadowView to be hidden/shown as {@code listView} reports scrolling changes
* @param listView a scrollable view that dictates the visibility of {@code dropShadowView}
*/
public DropShadowController(View dropShadowView, ListView listView) {
this(dropShadowView);
mListView = listView;
mListView.setOnScrollListener(mScrollChangeWatcher);
updateDropShadow(!Utils.isScrolledToTop(listView));
}
/**
* @param dropShadowView to be hidden/shown as {@code recyclerView} reports scrolling changes
* @param recyclerView a scrollable view that dictates the visibility of {@code dropShadowView}
*/
public DropShadowController(View dropShadowView, RecyclerView recyclerView) {
this(dropShadowView);
mRecyclerView = recyclerView;
mRecyclerView.addOnScrollListener(mScrollChangeWatcher);
updateDropShadow(!Utils.isScrolledToTop(recyclerView));
}
private DropShadowController(View dropShadowView) {
mDropShadowView = dropShadowView;
mDropShadowAnimator = getAlphaAnimator(mDropShadowView, 0f, 1f)
.setDuration(UiDataModel.getUiDataModel().getShortAnimationDuration());
}
/**
* Stop updating the drop shadow in response to scrolling changes. Stop listening to the backing
* scrollable entity for changes. This is important to avoid memory leaks.
*/
public void stop() {
if (mRecyclerView != null) {
mRecyclerView.removeOnScrollListener(mScrollChangeWatcher);
} else if (mListView != null) {
mListView.setOnScrollListener(null);
} else if (mUiDataModel != null) {
mUiDataModel.removeTabScrollListener(mScrollChangeWatcher);
}
}
/**
* @param shouldShowDropShadow {@code true} indicates the drop shadow should be displayed;
* {@code false} indicates the drop shadow should be hidden
*/
private void updateDropShadow(boolean shouldShowDropShadow) {
if (!shouldShowDropShadow && mDropShadowView.getAlpha() != 0f) {
if (DataModel.getDataModel().isApplicationInForeground()) {
mDropShadowAnimator.reverse();
} else {
mDropShadowView.setAlpha(0f);
}
if (mHairlineView != null) {
mHairlineView.setVisibility(View.VISIBLE);
}
}
if (shouldShowDropShadow && mDropShadowView.getAlpha() != 1f) {
if (DataModel.getDataModel().isApplicationInForeground()) {
mDropShadowAnimator.start();
} else {
mDropShadowView.setAlpha(1f);
}
if (mHairlineView != null) {
mHairlineView.setVisibility(View.INVISIBLE);
}
}
}
/**
* Update the drop shadow as the scrollable entity is scrolled.
*/
private final class ScrollChangeWatcher extends RecyclerView.OnScrollListener
implements TabScrollListener, AbsListView.OnScrollListener {
// RecyclerView scrolled.
@Override
public void onScrolled(RecyclerView view, int dx, int dy) {
updateDropShadow(!Utils.isScrolledToTop(view));
}
// ListView scrolled.
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
updateDropShadow(!Utils.isScrolledToTop(view));
}
// UiDataModel reports scroll change.
public void selectedTabScrollToTopChanged(Tab selectedTab, boolean scrolledToTop) {
updateDropShadow(!scrolledToTop);
}
}
}
|