New implementation that is commented + easier to understand

This commit is contained in:
mvp76 2016-12-05 18:31:41 +01:00
parent ccf763199f
commit 734741c8f4

View File

@ -7,6 +7,8 @@ import android.support.v7.widget.OrientationHelper;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.view.View; import android.view.View;
import static android.support.v7.widget.RecyclerView.NO_POSITION;
public class LinearLayoutManagerSnapHelper extends LinearSnapHelper { public class LinearLayoutManagerSnapHelper extends LinearSnapHelper {
private View lastSavedTarget; private View lastSavedTarget;
@ -39,55 +41,76 @@ public class LinearLayoutManagerSnapHelper extends LinearSnapHelper {
View snappedView = super.findSnapView(layoutManager); View snappedView = super.findSnapView(layoutManager);
if (snappedView != null && layoutManager.canScrollHorizontally()) { if (snappedView != null && layoutManager.canScrollHorizontally()) {
if (layoutManager instanceof LinearLayoutManager) { 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; lastSavedTarget = null;
int distSnap = super.calculateDistanceToFinalSnap(layoutManager, snappedView)[0]; int centerSnapPosition = orientationHelper.getTotalSpace() / 2;
int firstChild = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition(); int firstChild = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
int lastChild = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition(); int lastChild = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
int idxSnap = -1; int currentSmallestDistance = Integer.MAX_VALUE;
for (int i = firstChild; i <= lastChild; i++) { View currentSmallestDistanceView = null;
View view = ((LinearLayoutManager) layoutManager).findViewByPosition(i);
if (view == snappedView) {
idxSnap = i;
break;
}
}
int snapPositionFirst = orientationHelper.getDecoratedMeasurement(((LinearLayoutManager) layoutManager).findViewByPosition(firstChild)) / 2; int snappedViewIndex = ((LinearLayoutManager)layoutManager).getPosition(snappedView);
int snapPositionLast = orientationHelper.getTotalSpace() - orientationHelper.getDecoratedMeasurement(((LinearLayoutManager) layoutManager).findViewByPosition(lastChild)) / 2; if (snappedViewIndex != NO_POSITION) {
int centerSnapPosition = orientationHelper.getTotalSpace() / 2; 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;
if (idxSnap != -1) {
int currentSmallestDistance = Integer.MAX_VALUE;
View currentSmallestDistanceView = null;
for (int i = firstChild; i <= lastChild; i++) { for (int i = firstChild; i <= lastChild; i++) {
View view = ((LinearLayoutManager) layoutManager).findViewByPosition(i); View view = ((LinearLayoutManager) layoutManager).findViewByPosition(i);
if (i < idxSnap && firstChild == 0) {
int snapPosition = snapPositionFirst + (i - firstChild) * (centerSnapPosition - snapPositionFirst) / (idxSnap - firstChild); // Start by interpolating a snap position for (the center of) this view.
int viewPosition = view.getLeft() + view.getWidth() / 2; //
int dist = snapPosition - viewPosition; int snapPosition;
if (Math.abs(dist) < Math.abs(currentSmallestDistance) || (Math.abs(dist) == Math.abs(currentSmallestDistance) && distSnap > 0)) { if (i == snappedViewIndex) {
currentSmallestDistance = dist; snapPosition = centerSnapPosition;
currentSmallestDistanceView = view; } else if (i > snappedViewIndex) {
} snapPosition = snapPositionLast - (lastChild - i) * (snapPositionLast - centerSnapPosition) / (lastChild - snappedViewIndex);
} else if (i > idxSnap && lastChild == (this.layoutManager.getItemCount() - 1)) { } else {
int snapPosition = snapPositionLast - (lastChild - i) * (snapPositionLast - centerSnapPosition) / (lastChild - idxSnap); snapPosition = snapPositionFirst + (i - firstChild) * (centerSnapPosition - snapPositionFirst) / (snappedViewIndex - firstChild);
int viewPosition = view.getLeft() + view.getWidth() / 2; }
int dist = snapPosition - viewPosition;
if (Math.abs(dist) < Math.abs(currentSmallestDistance) || (Math.abs(dist) == Math.abs(currentSmallestDistance) && distSnap < 0)) { // Get current position of view (center)
currentSmallestDistance = dist; //
currentSmallestDistanceView = view; 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;
} }
} }
if (Math.abs(distSnap) > Math.abs(currentSmallestDistance)) {
snappedView = currentSmallestDistanceView; // Update with best snap candidate
lastSavedTarget = currentSmallestDistanceView; snappedView = currentSmallestDistanceView;
lastSavedDistance = -currentSmallestDistance; lastSavedTarget = currentSmallestDistanceView;
} lastSavedDistance = -currentSmallestDistance;
} }
} }
} }
@ -104,6 +127,8 @@ public class LinearLayoutManagerSnapHelper extends LinearSnapHelper {
@Override @Override
public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView) { public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView) {
if (targetView == lastSavedTarget) { if (targetView == lastSavedTarget) {
// No need to recalc, we already did this when finding the snap candidate
//
int[] out = new int[2]; int[] out = new int[2];
out[0] = lastSavedDistance; out[0] = lastSavedDistance;
out[1] = 0; out[1] = 0;