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}