| /* |
| * 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 static com.android.deskclock.AnimatorUtils.getAlphaAnimator; |
| |
| import android.animation.ValueAnimator; |
| import android.view.View; |
| import android.widget.AbsListView; |
| import android.widget.ListView; |
| |
| import androidx.annotation.NonNull; |
| import androidx.recyclerview.widget.RecyclerView; |
| |
| import com.android.deskclock.data.DataModel; |
| import com.android.deskclock.uidata.TabScrollListener; |
| import com.android.deskclock.uidata.UiDataModel; |
| |
| /** |
| * 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. |
| */ |
| @SuppressWarnings("unused") |
| 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} |
| */ |
| @SuppressWarnings("unused") |
| 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(@NonNull 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(boolean scrolledToTop) { |
| updateDropShadow(!scrolledToTop); |
| } |
| } |
| } |