1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settingslib.widget;
18 
19 import static androidx.lifecycle.Lifecycle.Event.ON_START;
20 import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
21 
22 import android.app.ActionBar;
23 import android.app.Activity;
24 import android.view.View;
25 
26 import androidx.annotation.VisibleForTesting;
27 import androidx.lifecycle.Lifecycle;
28 import androidx.lifecycle.LifecycleObserver;
29 import androidx.lifecycle.OnLifecycleEvent;
30 
31 /**
32  * UI controller that adds a shadow appear/disappear animation to action bar scroll.
33  */
34 public class ActionBarShadowController implements LifecycleObserver {
35 
36     @VisibleForTesting
37     static final float ELEVATION_HIGH = 8;
38     @VisibleForTesting
39     static final float ELEVATION_LOW = 0;
40 
41     @VisibleForTesting
42     ScrollChangeWatcher mScrollChangeWatcher;
43     private View mScrollView;
44     private boolean mIsScrollWatcherAttached;
45 
46     /**
47      * Wire up the animation to to an {@link Activity}. Shadow will be applied to activity's
48      * action bar.
49      */
attachToView( Activity activity, Lifecycle lifecycle, View scrollView)50     public static ActionBarShadowController attachToView(
51             Activity activity, Lifecycle lifecycle, View scrollView) {
52         return new ActionBarShadowController(activity, lifecycle, scrollView);
53     }
54 
55     /**
56      * Wire up the animation to to a {@link View}. Shadow will be applied to the view.
57      */
attachToView( View anchorView, Lifecycle lifecycle, View scrollView)58     public static ActionBarShadowController attachToView(
59             View anchorView, Lifecycle lifecycle, View scrollView) {
60         return new ActionBarShadowController(anchorView, lifecycle, scrollView);
61     }
62 
ActionBarShadowController(Activity activity, Lifecycle lifecycle, View scrollView)63     private ActionBarShadowController(Activity activity, Lifecycle lifecycle, View scrollView) {
64         mScrollChangeWatcher = new ActionBarShadowController.ScrollChangeWatcher(activity);
65         mScrollView = scrollView;
66         attachScrollWatcher();
67         lifecycle.addObserver(this);
68     }
69 
ActionBarShadowController(View anchorView, Lifecycle lifecycle, View scrollView)70     private ActionBarShadowController(View anchorView, Lifecycle lifecycle, View scrollView) {
71         mScrollChangeWatcher = new ActionBarShadowController.ScrollChangeWatcher(anchorView);
72         mScrollView = scrollView;
73         attachScrollWatcher();
74         lifecycle.addObserver(this);
75     }
76 
77     @OnLifecycleEvent(ON_START)
attachScrollWatcher()78     private void attachScrollWatcher() {
79         if (!mIsScrollWatcherAttached) {
80             mIsScrollWatcherAttached = true;
81             mScrollView.setOnScrollChangeListener(mScrollChangeWatcher);
82             mScrollChangeWatcher.updateDropShadow(mScrollView);
83         }
84     }
85 
86     @OnLifecycleEvent(ON_STOP)
detachScrollWatcher()87     private void detachScrollWatcher() {
88         mScrollView.setOnScrollChangeListener(null);
89         mIsScrollWatcherAttached = false;
90     }
91 
92     /**
93      * Update the drop shadow as the scrollable entity is scrolled.
94      */
95     final class ScrollChangeWatcher implements View.OnScrollChangeListener {
96 
97         private final Activity mActivity;
98         private final View mAnchorView;
99 
ScrollChangeWatcher(Activity activity)100         ScrollChangeWatcher(Activity activity) {
101             mActivity = activity;
102             mAnchorView = null;
103         }
104 
ScrollChangeWatcher(View anchorView)105         ScrollChangeWatcher(View anchorView) {
106             mAnchorView = anchorView;
107             mActivity = null;
108         }
109 
110         @Override
onScrollChange(View view, int scrollX, int scrollY, int oldScrollX, int oldScrollY)111         public void onScrollChange(View view, int scrollX, int scrollY, int oldScrollX,
112                 int oldScrollY) {
113             updateDropShadow(view);
114         }
115 
updateDropShadow(View view)116         public void updateDropShadow(View view) {
117             final boolean shouldShowShadow = view.canScrollVertically(-1);
118             if (mAnchorView != null) {
119                 mAnchorView.setElevation(shouldShowShadow ? ELEVATION_HIGH : ELEVATION_LOW);
120             } else if (mActivity != null) { // activity can become null when running monkey
121                 final ActionBar actionBar = mActivity.getActionBar();
122                 if (actionBar != null) {
123                     actionBar.setElevation(shouldShowShadow ? ELEVATION_HIGH : ELEVATION_LOW);
124                 }
125             }
126         }
127     }
128 }
129