DropShadowScrollListener.java

/*
 * Copyright 2018 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 androidx.car.utils;

import android.animation.ValueAnimator;
import android.util.Log;
import android.view.View;

import androidx.annotation.RestrictTo;
import androidx.car.R;
import androidx.car.widget.PagedListView;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

/**
 * A helper class that that listens for scrolls on a {@link PagedListView} and adds a drop shadow
 * to a view that is passed to it if the {@code PagedListView} has been scrolled to a position that
 * is not the top.
 *
 * <p>Note: this class expects that the {@code PagedListView} it is attached to is using a
 * {@link LinearLayoutManager}.
 *
 * @hide
 */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class DropShadowScrollListener extends PagedListView.OnScrollListener {
    private static final String TAG = "DropShadowScrollListener";
    private static final int ANIMATION_DURATION_MS = 100;

    private final View mElevationView;
    private final float mElevation;

    public DropShadowScrollListener(View elevationView) {
        mElevationView = elevationView;
        mElevation = elevationView.getResources().getDimension(
               R.dimen.car_list_dialog_title_elevation);
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);

        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        if (layoutManager == null) {
            return;
        }

        // This class is only used internally for lists that are expected to be using a
        // LinearLayoutManager.
        if (!(layoutManager instanceof LinearLayoutManager)) {
            Log.e(TAG, "Using a LayoutManager that is not a LinearLayoutManager. Class is: "
                    + layoutManager);
            return;
        }

        // If we're at the top, then remove the elevation. Otherwise, add it.
        if (((LinearLayoutManager) layoutManager).findFirstCompletelyVisibleItemPosition() == 0) {
            removeElevationWithAnimation();
        } else if (mElevationView.getElevation() != mElevation) {
            // Note that elevation can be added without any animation because the list
            // scroll will hide the fact that it pops in.
            mElevationView.setElevation(mElevation);
        }
    }

    /** Animates the removal of elevation from {@link #mElevationView}. */
    private void removeElevationWithAnimation() {
        ValueAnimator elevationAnimator =
                ValueAnimator.ofFloat(mElevationView.getElevation(), 0f);
        elevationAnimator
                .setDuration(ANIMATION_DURATION_MS)
                .addUpdateListener(animation -> mElevationView.setElevation(
                        (float) animation.getAnimatedValue()));
        elevationAnimator.start();
    }

}