Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 934d626c authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Allow ActionBarShadowController to attach to Views."

parents 199b8ca1 de5b5850
Loading
Loading
Loading
Loading
+19 −24
Original line number Diff line number Diff line
@@ -27,7 +27,6 @@ import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.recyclerview.widget.RecyclerView;

/**
 * UI controller that adds a shadow appear/disappear animation to action bar scroll.
@@ -41,40 +40,36 @@ public class ActionBarShadowController implements LifecycleObserver {

    @VisibleForTesting
    ScrollChangeWatcher mScrollChangeWatcher;
    private RecyclerView mRecyclerView;
    private View mScrollView;
    private boolean mIsScrollWatcherAttached;

    /**
     * Wire up the animation to to an {@link Activity}. Shadow will be applied to activity's
     * action bar.
     */
    public static ActionBarShadowController attachToRecyclerView(
            Activity activity, Lifecycle lifecycle, RecyclerView recyclerView) {
        return new ActionBarShadowController(activity, lifecycle, recyclerView);
    public static ActionBarShadowController attachToView(
            Activity activity, Lifecycle lifecycle, View scrollView) {
        return new ActionBarShadowController(activity, lifecycle, scrollView);
    }

    /**
     * Wire up the animation to to a {@link View}. Shadow will be applied to the view.
     */
    public static ActionBarShadowController attachToRecyclerView(
            View anchorView, Lifecycle lifecycle, RecyclerView recyclerView) {
        return new ActionBarShadowController(anchorView, lifecycle, recyclerView);
    public static ActionBarShadowController attachToView(
            View anchorView, Lifecycle lifecycle, View scrollView) {
        return new ActionBarShadowController(anchorView, lifecycle, scrollView);
    }

    private ActionBarShadowController(Activity activity, Lifecycle lifecycle,
            RecyclerView recyclerView) {
        mScrollChangeWatcher =
                new ActionBarShadowController.ScrollChangeWatcher(activity);
        mRecyclerView = recyclerView;
    private ActionBarShadowController(Activity activity, Lifecycle lifecycle, View scrollView) {
        mScrollChangeWatcher = new ActionBarShadowController.ScrollChangeWatcher(activity);
        mScrollView = scrollView;
        attachScrollWatcher();
        lifecycle.addObserver(this);
    }

    private ActionBarShadowController(View anchorView, Lifecycle lifecycle,
            RecyclerView recyclerView) {
        mScrollChangeWatcher =
                new ActionBarShadowController.ScrollChangeWatcher(anchorView);
        mRecyclerView = recyclerView;
    private ActionBarShadowController(View anchorView, Lifecycle lifecycle, View scrollView) {
        mScrollChangeWatcher = new ActionBarShadowController.ScrollChangeWatcher(anchorView);
        mScrollView = scrollView;
        attachScrollWatcher();
        lifecycle.addObserver(this);
    }
@@ -83,21 +78,21 @@ public class ActionBarShadowController implements LifecycleObserver {
    private void attachScrollWatcher() {
        if (!mIsScrollWatcherAttached) {
            mIsScrollWatcherAttached = true;
            mRecyclerView.addOnScrollListener(mScrollChangeWatcher);
            mScrollChangeWatcher.updateDropShadow(mRecyclerView);
            mScrollView.setOnScrollChangeListener(mScrollChangeWatcher);
            mScrollChangeWatcher.updateDropShadow(mScrollView);
        }
    }

    @OnLifecycleEvent(ON_STOP)
    private void detachScrollWatcher() {
        mRecyclerView.removeOnScrollListener(mScrollChangeWatcher);
        mScrollView.setOnScrollChangeListener(null);
        mIsScrollWatcherAttached = false;
    }

    /**
     * Update the drop shadow as the scrollable entity is scrolled.
     */
    final class ScrollChangeWatcher extends RecyclerView.OnScrollListener {
    final class ScrollChangeWatcher implements View.OnScrollChangeListener {

        private final Activity mActivity;
        private final View mAnchorView;
@@ -112,9 +107,9 @@ public class ActionBarShadowController implements LifecycleObserver {
            mActivity = null;
        }

        // RecyclerView scrolled.
        @Override
        public void onScrolled(RecyclerView view, int dx, int dy) {
        public void onScrollChange(View view, int scrollX, int scrollY, int oldScrollX,
                int oldScrollY) {
            updateDropShadow(view);
        }

+24 −12
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -49,6 +50,8 @@ public class ActionBarShadowControllerTest {
    @Mock
    private RecyclerView mRecyclerView;
    @Mock
    private View mScrollView;
    @Mock
    private Activity mActivity;
    @Mock
    private ActionBar mActionBar;
@@ -66,51 +69,60 @@ public class ActionBarShadowControllerTest {
    }

    @Test
    public void attachToRecyclerView_shouldAddScrollWatcherAndUpdateActionBar() {
    public void attachToView_shouldAddScrollWatcherAndUpdateActionBar() {
        when(mRecyclerView.canScrollVertically(-1)).thenReturn(false);

        ActionBarShadowController.attachToRecyclerView(mActivity, mLifecycle, mRecyclerView);
        ActionBarShadowController.attachToView(mActivity, mLifecycle, mRecyclerView);

        verify(mActionBar).setElevation(ActionBarShadowController.ELEVATION_LOW);
    }

    @Test
    public void attachToView_scrollView_shouldAddScrollWatcherAndUpdateActionBar() {
        when(mScrollView.canScrollVertically(-1)).thenReturn(false);

        ActionBarShadowController.attachToView(mActivity, mLifecycle, mScrollView);

        verify(mActionBar).setElevation(ActionBarShadowController.ELEVATION_LOW);
    }

    @Test
    public void attachToRecyclerView_customViewAsActionBar_shouldUpdateElevationOnScroll() {
    public void attachToView_customViewAsActionBar_shouldUpdateElevationOnScroll() {
        // Setup
        mView.setElevation(50);
        when(mRecyclerView.canScrollVertically(-1)).thenReturn(false);
        final ActionBarShadowController controller =
                ActionBarShadowController.attachToRecyclerView(mView, mLifecycle, mRecyclerView);
                ActionBarShadowController.attachToView(mView, mLifecycle, mRecyclerView);
        assertThat(mView.getElevation()).isEqualTo(ActionBarShadowController.ELEVATION_LOW);

        // Scroll
        when(mRecyclerView.canScrollVertically(-1)).thenReturn(true);
        controller.mScrollChangeWatcher.onScrolled(mRecyclerView, 10 /* dx */, 10 /* dy */);
        controller.mScrollChangeWatcher.onScrollChange(mRecyclerView, 10, 10, 0, 0);
        assertThat(mView.getElevation()).isEqualTo(ActionBarShadowController.ELEVATION_HIGH);
    }

    @Test
    public void attachToRecyclerView_lifecycleChange_shouldAttachDetach() {
        ActionBarShadowController.attachToRecyclerView(mActivity, mLifecycle, mRecyclerView);
    public void attachToView_lifecycleChange_shouldAttachDetach() {
        ActionBarShadowController.attachToView(mActivity, mLifecycle, mRecyclerView);

        verify(mRecyclerView).addOnScrollListener(any());
        verify(mRecyclerView).setOnScrollChangeListener(any());

        mLifecycle.handleLifecycleEvent(ON_START);
        mLifecycle.handleLifecycleEvent(ON_STOP);
        verify(mRecyclerView).removeOnScrollListener(any());
        verify(mRecyclerView).setOnScrollChangeListener(isNull());

        mLifecycle.handleLifecycleEvent(ON_START);
        verify(mRecyclerView, times(2)).addOnScrollListener(any());
        verify(mRecyclerView, times(3)).setOnScrollChangeListener(any());
    }

    @Test
    public void onScrolled_nullAnchorViewAndActivity_shouldNotCrash() {
        final Activity activity = null;
        final ActionBarShadowController controller =
                ActionBarShadowController.attachToRecyclerView(activity, mLifecycle, mRecyclerView);
                ActionBarShadowController.attachToView(activity, mLifecycle, mRecyclerView);

        // Scroll
        controller.mScrollChangeWatcher.onScrolled(mRecyclerView, 10 /* dx */, 10 /* dy */);
        controller.mScrollChangeWatcher.onScrollChange(mRecyclerView, 10, 10, 0, 0);
        // no crash
    }
}