package org.fdroid.fdroid.views;
import android.support.annotation.NonNull;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.LinearSnapHelper;
import android.support.v7.widget.OrientationHelper;
import android.support.v7.widget.RecyclerView;
import android.view.View;
public class LinearLayoutManagerSnapHelper extends LinearSnapHelper {
private View lastSavedTarget;
private int lastSavedDistance;
public interface LinearSnapHelperListener {
/**
* Tells the listener that we have selected a view to snap to.
* @param view The selected view (may be null)
* @param position Adapter position of the snapped to view (or NO_POSITION if none)
*/
void onSnappedToView(View view, int position);
}
private final LinearLayoutManager layoutManager;
private final OrientationHelper orientationHelper;
private LinearSnapHelperListener listener;
public LinearLayoutManagerSnapHelper(LinearLayoutManager layoutManager) {
this.layoutManager = layoutManager;
this.orientationHelper = OrientationHelper.createHorizontalHelper(this.layoutManager);
}
public void setLinearSnapHelperListener(LinearSnapHelperListener listener) {
this.listener = listener;
}
@Override
public View findSnapView(RecyclerView.LayoutManager layoutManager) {
View snappedView = super.findSnapView(layoutManager);
if (snappedView != null && layoutManager.canScrollHorizontally()) {
if (layoutManager instanceof LinearLayoutManager) {
// The super class implementation will always try to snap the center of a view to the
// center of the screen. This is desired behavior, but will result in that the first
// and last item will never be fully visible (unless in the special case when they all
// fit on the screen)
//
// We handle this by checking if the first (and/or the last) item is visible, and compare
// the distance it would take to "snap" this item to the screen edge to the distance
// needed to snap the "snappedView" to the center of the screen. We always go for the
// smallest distance, e.g. the closest snap position.
//
// To further complicate this, we might have intermediate views in the range 1..idxSnap
// (and correspondingly idxsnap+1..idxLast-1) that will never be "snapped to". We
// interpolate the "snap position" for these views (between center screen and screen edge)
// and then calculate the snap distance for them, again selecting the smallest of them all.
lastSavedTarget = null;
int centerSnapPosition = orientationHelper.getTotalSpace() / 2;
int firstChild = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
int lastChild = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
int currentSmallestDistance = Integer.MAX_VALUE;
View currentSmallestDistanceView = null;
int snappedViewIndex = ((LinearLayoutManager) layoutManager).getPosition(snappedView);
if (snappedViewIndex != RecyclerView.NO_POSITION) {
int snapPositionFirst = orientationHelper.getDecoratedMeasurement(((LinearLayoutManager) layoutManager).findViewByPosition(firstChild)) / 2;
int snapPositionLast = orientationHelper.getTotalSpace() - orientationHelper.getDecoratedMeasurement(((LinearLayoutManager) layoutManager).findViewByPosition(lastChild)) / 2;
// If first item not on screen, ignore views 0..snappedViewIndex-1
if (firstChild != 0) {
firstChild = snappedViewIndex;
}
// If last item not on screen, ignore views snappedViewIndex+1..N
if (lastChild != this.layoutManager.getItemCount() - 1) {
lastChild = snappedViewIndex;
}
for (int i = firstChild; i <= lastChild; i++) {
View view = ((LinearLayoutManager) layoutManager).findViewByPosition(i);
// Start by interpolating a snap position for (the center of) this view.
//
int snapPosition;
if (i == snappedViewIndex) {
snapPosition = centerSnapPosition;
} else if (i > snappedViewIndex) {
snapPosition = snapPositionLast - (lastChild - i) * (snapPositionLast - centerSnapPosition) / (lastChild - snappedViewIndex);
} else {
snapPosition = snapPositionFirst + (i - firstChild) * (centerSnapPosition - snapPositionFirst) / (snappedViewIndex - firstChild);
}
// Get current position of view (center)
//
int viewPosition = view.getLeft() + view.getWidth() / 2;
// Calculate distance and compare to current best candidate
//
int dist = snapPosition - viewPosition;
if (Math.abs(dist) < Math.abs(currentSmallestDistance) || (Math.abs(dist) == Math.abs(currentSmallestDistance))) {
currentSmallestDistance = dist;
currentSmallestDistanceView = view;
}
}
// Update with best snap candidate
snappedView = currentSmallestDistanceView;
lastSavedTarget = currentSmallestDistanceView;
lastSavedDistance = -currentSmallestDistance;
}
}
}
if (listener != null) {
int snappedPosition = 0;
if (snappedView != null) {
snappedPosition = this.layoutManager.getPosition(snappedView);
}
listener.onSnappedToView(snappedView, snappedPosition);
}
return snappedView;
}
@Override
public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView) {
if (targetView == lastSavedTarget) {
// No need to recalc, we already did this when finding the snap candidate
//
int[] out = new int[2];
out[0] = lastSavedDistance;
out[1] = 0;
return out;
}
return super.calculateDistanceToFinalSnap(layoutManager, targetView);
}
}