/*
* Copyright (C) 2016 Google Inc. All Rights Reserved.
*
* 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.google.android.apps.santatracker.launch;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
/**
* Scroll listener that adjusts the RecyclerView after a scroll event to make sure that the
* top list item is fully visible and not obscured.
*/
public class StickyScrollListener extends RecyclerView.OnScrollListener {
private static final String TAG = "StickyScrollListener";
/**
* Cutoff for stickiness. If less than this fraction of the obscured view can be seen, the
* RecyclerView is scrolled to a more visible view. Otherwise, this view is scrolled to be
* fully visible.
*/
private static final float VISIBILITY_CUTOFF = 0.60f;
private static final int UP = 1;
private static final int DOWN = 2;
private int mScrollDirection = 0;
/** LinearLayoutManager controlling the RecyclerView **/
private LinearLayoutManager mManager;
/** Number of columns displayed by the Manager **/
private int mNumColumns = 1;
/** Scroll state of the RecyclerView **/
private int mScrollState = RecyclerView.SCROLL_STATE_IDLE;
/** Position of the first completely visible view **/
private int topVisiblePos;
/** Position of the view above the first completely visible view **/
private int topObscuredPos;
/** Position of the last completely visible view **/
private int bottomVisiblePos;
/** Position of the view below the last completely visible view **/
private int bottomObscuredPos;
/** View above the first completely visible View **/
private View topObscuredView;
/** View below the last completely visible View **/
private View bottomObscuredView;
/** Percent of topObscuredView that is visible **/
private float topObscuredPercentVisible;
/** Percent of bottomObscuredView that is visible **/
private float bottomObscuredPercentVisible;
public StickyScrollListener(LinearLayoutManager manager, int numColumns) {
this.mManager = manager;
this.mNumColumns = numColumns;
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
topVisiblePos = mManager.findFirstCompletelyVisibleItemPosition();
topObscuredPos = topVisiblePos - mNumColumns;
topObscuredView = mManager.findViewByPosition(topObscuredPos);
bottomVisiblePos = mManager.findLastCompletelyVisibleItemPosition();
bottomObscuredPos = bottomVisiblePos + mNumColumns;
bottomObscuredView = mManager.findViewByPosition(bottomObscuredPos);
if (topObscuredView != null) {
// Calculate how many pixels of the obscured view are visible.
float topObscuredPixelsVisible = (topObscuredView.getHeight() + topObscuredView.getY());
// Calculate what percentage of the obscured view is visible.
topObscuredPercentVisible = topObscuredPixelsVisible / topObscuredView.getHeight();
} else {
clearTop();
}
if (bottomObscuredView != null) {
// Same calculation for bottom
float bottomObscuredPixelsVisible = (recyclerView.getHeight() - bottomObscuredView.getY());
bottomObscuredPercentVisible = bottomObscuredPixelsVisible / bottomObscuredView.getHeight();
} else {
clearBottom();
}
// Mark scroll direction
if (dy <= 0) {
mScrollDirection = UP;
} else {
mScrollDirection = DOWN;
}
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
// This detects the end of a non-fling drag
boolean stoppedDragging = mScrollState == RecyclerView.SCROLL_STATE_DRAGGING &&
newState == RecyclerView.SCROLL_STATE_IDLE;
boolean stoppedFlinging = mScrollState == RecyclerView.SCROLL_STATE_SETTLING &&
newState == RecyclerView.SCROLL_STATE_IDLE;
if (stoppedDragging || stoppedFlinging) {
if (mScrollDirection == DOWN) {
if (topObscuredPercentVisible <= VISIBILITY_CUTOFF) {
// Scroll down to the top of the first completely visible view.
float abovePixelsVisible = topObscuredView.getY() + topObscuredView.getHeight();
recyclerView.smoothScrollBy(0, (int) abovePixelsVisible);
} else if (topObscuredPos >= 0) {
// Scroll up to the top of the view above the first completely visible view.
recyclerView.smoothScrollBy(0, (int) topObscuredView.getY());
}
}
}
// Mark the scroll state to avoid duplicate event detection.
mScrollState = newState;
}
private void clearTop() {
topObscuredView = null;
topObscuredPos = -1;
topVisiblePos = -1;
topObscuredPercentVisible = 1.0f;
}
private void clearBottom() {
bottomObscuredView = null;
bottomObscuredPos = -1;
bottomVisiblePos = -1;
bottomObscuredPercentVisible = 1.0f;
}
}