14e7ee09e878178873241846757b178535da3dd76James Lemieux/* 24e7ee09e878178873241846757b178535da3dd76James Lemieux * Copyright (C) 2016 The Android Open Source Project 34e7ee09e878178873241846757b178535da3dd76James Lemieux * 44e7ee09e878178873241846757b178535da3dd76James Lemieux * Licensed under the Apache License, Version 2.0 (the "License"); 54e7ee09e878178873241846757b178535da3dd76James Lemieux * you may not use this file except in compliance with the License. 64e7ee09e878178873241846757b178535da3dd76James Lemieux * You may obtain a copy of the License at 74e7ee09e878178873241846757b178535da3dd76James Lemieux * 84e7ee09e878178873241846757b178535da3dd76James Lemieux * http://www.apache.org/licenses/LICENSE-2.0 94e7ee09e878178873241846757b178535da3dd76James Lemieux * 104e7ee09e878178873241846757b178535da3dd76James Lemieux * Unless required by applicable law or agreed to in writing, software 114e7ee09e878178873241846757b178535da3dd76James Lemieux * distributed under the License is distributed on an "AS IS" BASIS, 124e7ee09e878178873241846757b178535da3dd76James Lemieux * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 134e7ee09e878178873241846757b178535da3dd76James Lemieux * See the License for the specific language governing permissions and 144e7ee09e878178873241846757b178535da3dd76James Lemieux * limitations under the License. 154e7ee09e878178873241846757b178535da3dd76James Lemieux */ 164e7ee09e878178873241846757b178535da3dd76James Lemieux 174e7ee09e878178873241846757b178535da3dd76James Lemieuxpackage com.android.deskclock; 184e7ee09e878178873241846757b178535da3dd76James Lemieux 194e7ee09e878178873241846757b178535da3dd76James Lemieuximport android.animation.ValueAnimator; 204e7ee09e878178873241846757b178535da3dd76James Lemieuximport android.support.v7.widget.RecyclerView; 214e7ee09e878178873241846757b178535da3dd76James Lemieuximport android.view.View; 224e7ee09e878178873241846757b178535da3dd76James Lemieuximport android.widget.AbsListView; 234e7ee09e878178873241846757b178535da3dd76James Lemieuximport android.widget.ListView; 244e7ee09e878178873241846757b178535da3dd76James Lemieux 254e7ee09e878178873241846757b178535da3dd76James Lemieuximport com.android.deskclock.data.DataModel; 264e7ee09e878178873241846757b178535da3dd76James Lemieuximport com.android.deskclock.uidata.TabScrollListener; 274e7ee09e878178873241846757b178535da3dd76James Lemieuximport com.android.deskclock.uidata.UiDataModel; 284e7ee09e878178873241846757b178535da3dd76James Lemieuximport com.android.deskclock.uidata.UiDataModel.Tab; 294e7ee09e878178873241846757b178535da3dd76James Lemieux 304e7ee09e878178873241846757b178535da3dd76James Lemieuximport static com.android.deskclock.AnimatorUtils.getAlphaAnimator; 314e7ee09e878178873241846757b178535da3dd76James Lemieux 324e7ee09e878178873241846757b178535da3dd76James Lemieux/** 334e7ee09e878178873241846757b178535da3dd76James Lemieux * This controller encapsulates the logic that watches a model for changes to scroll state and 344e7ee09e878178873241846757b178535da3dd76James Lemieux * updates the display state of an associated drop shadow. The observable model may take many forms 354e7ee09e878178873241846757b178535da3dd76James Lemieux * including ListViews, RecyclerViews and this application's UiDataModel. Each of these models can 364e7ee09e878178873241846757b178535da3dd76James Lemieux * indicate when content is scrolled to its top. When the content is scrolled to the top the drop 374e7ee09e878178873241846757b178535da3dd76James Lemieux * shadow is hidden and the content appears flush with the app bar. When the content is scrolled 384e7ee09e878178873241846757b178535da3dd76James Lemieux * up the drop shadow is displayed making the content appear to scroll below the app bar. 394e7ee09e878178873241846757b178535da3dd76James Lemieux */ 404e7ee09e878178873241846757b178535da3dd76James Lemieuxpublic final class DropShadowController { 414e7ee09e878178873241846757b178535da3dd76James Lemieux 424e7ee09e878178873241846757b178535da3dd76James Lemieux /** Updates {@link #mDropShadowView} in response to changes in the backing scroll model. */ 434e7ee09e878178873241846757b178535da3dd76James Lemieux private final ScrollChangeWatcher mScrollChangeWatcher = new ScrollChangeWatcher(); 444e7ee09e878178873241846757b178535da3dd76James Lemieux 454e7ee09e878178873241846757b178535da3dd76James Lemieux /** Fades the {@link @mDropShadowView} in/out as scroll state changes. */ 464e7ee09e878178873241846757b178535da3dd76James Lemieux private final ValueAnimator mDropShadowAnimator; 474e7ee09e878178873241846757b178535da3dd76James Lemieux 484e7ee09e878178873241846757b178535da3dd76James Lemieux /** The component that displays a drop shadow. */ 494e7ee09e878178873241846757b178535da3dd76James Lemieux private final View mDropShadowView; 504e7ee09e878178873241846757b178535da3dd76James Lemieux 511d25bc2d696f17c776f2084efaf7737d7158ad20Christine Franks /** Tab bar's hairline, which is hidden whenever the drop shadow is displayed. */ 521d25bc2d696f17c776f2084efaf7737d7158ad20Christine Franks private View mHairlineView; 531d25bc2d696f17c776f2084efaf7737d7158ad20Christine Franks 544e7ee09e878178873241846757b178535da3dd76James Lemieux // Supported sources of scroll position include: ListView, RecyclerView and UiDataModel. 554e7ee09e878178873241846757b178535da3dd76James Lemieux private RecyclerView mRecyclerView; 564e7ee09e878178873241846757b178535da3dd76James Lemieux private UiDataModel mUiDataModel; 574e7ee09e878178873241846757b178535da3dd76James Lemieux private ListView mListView; 584e7ee09e878178873241846757b178535da3dd76James Lemieux 594e7ee09e878178873241846757b178535da3dd76James Lemieux /** 604e7ee09e878178873241846757b178535da3dd76James Lemieux * @param dropShadowView to be hidden/shown as {@code uiDataModel} reports scrolling changes 614e7ee09e878178873241846757b178535da3dd76James Lemieux * @param uiDataModel models the vertical scrolling state of the application's selected tab 621d25bc2d696f17c776f2084efaf7737d7158ad20Christine Franks * @param hairlineView at the bottom of the tab bar to be hidden or shown when the drop shadow 631d25bc2d696f17c776f2084efaf7737d7158ad20Christine Franks * is displayed or hidden, respectively. 644e7ee09e878178873241846757b178535da3dd76James Lemieux */ 651d25bc2d696f17c776f2084efaf7737d7158ad20Christine Franks public DropShadowController(View dropShadowView, UiDataModel uiDataModel, View hairlineView) { 664e7ee09e878178873241846757b178535da3dd76James Lemieux this(dropShadowView); 674e7ee09e878178873241846757b178535da3dd76James Lemieux mUiDataModel = uiDataModel; 684e7ee09e878178873241846757b178535da3dd76James Lemieux mUiDataModel.addTabScrollListener(mScrollChangeWatcher); 691d25bc2d696f17c776f2084efaf7737d7158ad20Christine Franks mHairlineView = hairlineView; 701d25bc2d696f17c776f2084efaf7737d7158ad20Christine Franks updateDropShadow(!uiDataModel.isSelectedTabScrolledToTop()); 714e7ee09e878178873241846757b178535da3dd76James Lemieux } 724e7ee09e878178873241846757b178535da3dd76James Lemieux 734e7ee09e878178873241846757b178535da3dd76James Lemieux /** 744e7ee09e878178873241846757b178535da3dd76James Lemieux * @param dropShadowView to be hidden/shown as {@code listView} reports scrolling changes 754e7ee09e878178873241846757b178535da3dd76James Lemieux * @param listView a scrollable view that dictates the visibility of {@code dropShadowView} 764e7ee09e878178873241846757b178535da3dd76James Lemieux */ 774e7ee09e878178873241846757b178535da3dd76James Lemieux public DropShadowController(View dropShadowView, ListView listView) { 784e7ee09e878178873241846757b178535da3dd76James Lemieux this(dropShadowView); 794e7ee09e878178873241846757b178535da3dd76James Lemieux mListView = listView; 804e7ee09e878178873241846757b178535da3dd76James Lemieux mListView.setOnScrollListener(mScrollChangeWatcher); 811d25bc2d696f17c776f2084efaf7737d7158ad20Christine Franks updateDropShadow(!Utils.isScrolledToTop(listView)); 824e7ee09e878178873241846757b178535da3dd76James Lemieux } 834e7ee09e878178873241846757b178535da3dd76James Lemieux 844e7ee09e878178873241846757b178535da3dd76James Lemieux /** 854e7ee09e878178873241846757b178535da3dd76James Lemieux * @param dropShadowView to be hidden/shown as {@code recyclerView} reports scrolling changes 864e7ee09e878178873241846757b178535da3dd76James Lemieux * @param recyclerView a scrollable view that dictates the visibility of {@code dropShadowView} 874e7ee09e878178873241846757b178535da3dd76James Lemieux */ 884e7ee09e878178873241846757b178535da3dd76James Lemieux public DropShadowController(View dropShadowView, RecyclerView recyclerView) { 894e7ee09e878178873241846757b178535da3dd76James Lemieux this(dropShadowView); 904e7ee09e878178873241846757b178535da3dd76James Lemieux mRecyclerView = recyclerView; 914e7ee09e878178873241846757b178535da3dd76James Lemieux mRecyclerView.addOnScrollListener(mScrollChangeWatcher); 921d25bc2d696f17c776f2084efaf7737d7158ad20Christine Franks updateDropShadow(!Utils.isScrolledToTop(recyclerView)); 934e7ee09e878178873241846757b178535da3dd76James Lemieux } 944e7ee09e878178873241846757b178535da3dd76James Lemieux 954e7ee09e878178873241846757b178535da3dd76James Lemieux private DropShadowController(View dropShadowView) { 964e7ee09e878178873241846757b178535da3dd76James Lemieux mDropShadowView = dropShadowView; 974e7ee09e878178873241846757b178535da3dd76James Lemieux mDropShadowAnimator = getAlphaAnimator(mDropShadowView, 0f, 1f) 984e7ee09e878178873241846757b178535da3dd76James Lemieux .setDuration(UiDataModel.getUiDataModel().getShortAnimationDuration()); 994e7ee09e878178873241846757b178535da3dd76James Lemieux } 1004e7ee09e878178873241846757b178535da3dd76James Lemieux 1014e7ee09e878178873241846757b178535da3dd76James Lemieux /** 1024e7ee09e878178873241846757b178535da3dd76James Lemieux * Stop updating the drop shadow in response to scrolling changes. Stop listening to the backing 1034e7ee09e878178873241846757b178535da3dd76James Lemieux * scrollable entity for changes. This is important to avoid memory leaks. 1044e7ee09e878178873241846757b178535da3dd76James Lemieux */ 1054e7ee09e878178873241846757b178535da3dd76James Lemieux public void stop() { 1064e7ee09e878178873241846757b178535da3dd76James Lemieux if (mRecyclerView != null) { 1074e7ee09e878178873241846757b178535da3dd76James Lemieux mRecyclerView.removeOnScrollListener(mScrollChangeWatcher); 1084e7ee09e878178873241846757b178535da3dd76James Lemieux } else if (mListView != null) { 1094e7ee09e878178873241846757b178535da3dd76James Lemieux mListView.setOnScrollListener(null); 1104e7ee09e878178873241846757b178535da3dd76James Lemieux } else if (mUiDataModel != null) { 1114e7ee09e878178873241846757b178535da3dd76James Lemieux mUiDataModel.removeTabScrollListener(mScrollChangeWatcher); 1124e7ee09e878178873241846757b178535da3dd76James Lemieux } 1134e7ee09e878178873241846757b178535da3dd76James Lemieux } 1144e7ee09e878178873241846757b178535da3dd76James Lemieux 1154e7ee09e878178873241846757b178535da3dd76James Lemieux /** 1161d25bc2d696f17c776f2084efaf7737d7158ad20Christine Franks * @param shouldShowDropShadow {@code true} indicates the drop shadow should be displayed; 1171d25bc2d696f17c776f2084efaf7737d7158ad20Christine Franks * {@code false} indicates the drop shadow should be hidden 1184e7ee09e878178873241846757b178535da3dd76James Lemieux */ 1191d25bc2d696f17c776f2084efaf7737d7158ad20Christine Franks private void updateDropShadow(boolean shouldShowDropShadow) { 1201d25bc2d696f17c776f2084efaf7737d7158ad20Christine Franks if (!shouldShowDropShadow && mDropShadowView.getAlpha() != 0f) { 1214e7ee09e878178873241846757b178535da3dd76James Lemieux if (DataModel.getDataModel().isApplicationInForeground()) { 1224e7ee09e878178873241846757b178535da3dd76James Lemieux mDropShadowAnimator.reverse(); 1234e7ee09e878178873241846757b178535da3dd76James Lemieux } else { 1244e7ee09e878178873241846757b178535da3dd76James Lemieux mDropShadowView.setAlpha(0f); 1254e7ee09e878178873241846757b178535da3dd76James Lemieux } 1261d25bc2d696f17c776f2084efaf7737d7158ad20Christine Franks if (mHairlineView != null) { 1271d25bc2d696f17c776f2084efaf7737d7158ad20Christine Franks mHairlineView.setVisibility(View.VISIBLE); 1281d25bc2d696f17c776f2084efaf7737d7158ad20Christine Franks } 1294e7ee09e878178873241846757b178535da3dd76James Lemieux } 1304e7ee09e878178873241846757b178535da3dd76James Lemieux 1311d25bc2d696f17c776f2084efaf7737d7158ad20Christine Franks if (shouldShowDropShadow && mDropShadowView.getAlpha() != 1f) { 1324e7ee09e878178873241846757b178535da3dd76James Lemieux if (DataModel.getDataModel().isApplicationInForeground()) { 1334e7ee09e878178873241846757b178535da3dd76James Lemieux mDropShadowAnimator.start(); 1344e7ee09e878178873241846757b178535da3dd76James Lemieux } else { 1354e7ee09e878178873241846757b178535da3dd76James Lemieux mDropShadowView.setAlpha(1f); 1364e7ee09e878178873241846757b178535da3dd76James Lemieux } 1371d25bc2d696f17c776f2084efaf7737d7158ad20Christine Franks if (mHairlineView != null) { 1381d25bc2d696f17c776f2084efaf7737d7158ad20Christine Franks mHairlineView.setVisibility(View.INVISIBLE); 1391d25bc2d696f17c776f2084efaf7737d7158ad20Christine Franks } 1404e7ee09e878178873241846757b178535da3dd76James Lemieux } 1414e7ee09e878178873241846757b178535da3dd76James Lemieux } 1424e7ee09e878178873241846757b178535da3dd76James Lemieux 1434e7ee09e878178873241846757b178535da3dd76James Lemieux /** 1444e7ee09e878178873241846757b178535da3dd76James Lemieux * Update the drop shadow as the scrollable entity is scrolled. 1454e7ee09e878178873241846757b178535da3dd76James Lemieux */ 1464e7ee09e878178873241846757b178535da3dd76James Lemieux private final class ScrollChangeWatcher extends RecyclerView.OnScrollListener 1474e7ee09e878178873241846757b178535da3dd76James Lemieux implements TabScrollListener, AbsListView.OnScrollListener { 1484e7ee09e878178873241846757b178535da3dd76James Lemieux 1494e7ee09e878178873241846757b178535da3dd76James Lemieux // RecyclerView scrolled. 1504e7ee09e878178873241846757b178535da3dd76James Lemieux @Override 1514e7ee09e878178873241846757b178535da3dd76James Lemieux public void onScrolled(RecyclerView view, int dx, int dy) { 1521d25bc2d696f17c776f2084efaf7737d7158ad20Christine Franks updateDropShadow(!Utils.isScrolledToTop(view)); 1534e7ee09e878178873241846757b178535da3dd76James Lemieux } 1544e7ee09e878178873241846757b178535da3dd76James Lemieux 1554e7ee09e878178873241846757b178535da3dd76James Lemieux // ListView scrolled. 1564e7ee09e878178873241846757b178535da3dd76James Lemieux @Override 1574e7ee09e878178873241846757b178535da3dd76James Lemieux public void onScrollStateChanged(AbsListView view, int scrollState) {} 1584e7ee09e878178873241846757b178535da3dd76James Lemieux 1594e7ee09e878178873241846757b178535da3dd76James Lemieux @Override 1604e7ee09e878178873241846757b178535da3dd76James Lemieux public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, 1614e7ee09e878178873241846757b178535da3dd76James Lemieux int totalItemCount) { 1621d25bc2d696f17c776f2084efaf7737d7158ad20Christine Franks updateDropShadow(!Utils.isScrolledToTop(view)); 1634e7ee09e878178873241846757b178535da3dd76James Lemieux } 1644e7ee09e878178873241846757b178535da3dd76James Lemieux 1654e7ee09e878178873241846757b178535da3dd76James Lemieux // UiDataModel reports scroll change. 1664e7ee09e878178873241846757b178535da3dd76James Lemieux public void selectedTabScrollToTopChanged(Tab selectedTab, boolean scrolledToTop) { 1671d25bc2d696f17c776f2084efaf7737d7158ad20Christine Franks updateDropShadow(!scrolledToTop); 1684e7ee09e878178873241846757b178535da3dd76James Lemieux } 1694e7ee09e878178873241846757b178535da3dd76James Lemieux } 1704e7ee09e878178873241846757b178535da3dd76James Lemieux}