blob: 23cad28b3592ab6ba8c681093542ff1b6927a9a2 [file] [log] [blame]
/*
* 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);
}
}
}