Star
You must be signed in to star a gist
A fix for https://github.com/rubensousa/RecyclerViewSnap/ when used in RTL carousels. See https://github.com/rubensousa/RecyclerViewSnap/issues/1
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.support.annotation.NonNull; | |
import android.support.annotation.Nullable; | |
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.support.v7.widget.RecyclerView.LayoutManager; | |
import android.view.Gravity; | |
import android.view.View; | |
import com.example.AppState; | |
/** | |
* A {@link LinearSnapHelper} that allows you to snap to a certain edge of the {@link RecyclerView}, | |
* as opposed to the default center-snapping provided by {@link LinearSnapHelper}. | |
* | |
* Inspired by https://github.com/rubensousa/RecyclerViewSnap/ | |
*/ | |
public class GravitySnapHelper extends LinearSnapHelper { | |
@Nullable private OrientationHelper verticalHelper; | |
@Nullable private OrientationHelper horizontalHelper; | |
private int gravity; | |
//TODO Google's OrientationHelper seems to be broken in RTL carousels. Check if these flags are still needed when updating support lib. | |
private boolean isRTL = AppState.getAppComponent().localeManager().isCurrentLocaleRTL(); | |
private boolean isGravityHorizontal; | |
private boolean isFlingingInPositiveDirection; | |
/** | |
* Constructor | |
* | |
* @param gravity The {@link Gravity} specifies the edge of the {@link RecyclerView} that items should snap to. | |
*/ | |
public GravitySnapHelper(int gravity) { | |
switch (gravity) { | |
case Gravity.LEFT: | |
case Gravity.START: | |
this.gravity = Gravity.START; | |
this.isGravityHorizontal = true; | |
break; | |
case Gravity.RIGHT: | |
case Gravity.END: | |
this.gravity = Gravity.END; | |
this.isGravityHorizontal = true; | |
break; | |
case Gravity.TOP: | |
case Gravity.BOTTOM: | |
this.gravity = gravity; | |
break; | |
default: | |
throw new IllegalArgumentException("Illegal Gravity passed to constructor."); | |
} | |
} | |
@Override | |
@Nullable | |
public int[] calculateDistanceToFinalSnap(@NonNull LayoutManager layoutManager, @NonNull View targetView) { | |
int[] out = new int[2]; | |
if (layoutManager.canScrollHorizontally()) { | |
if (gravity == Gravity.START) { | |
out[0] = distanceToStart(targetView, getHorizontalHelper(layoutManager)); | |
} else { // END | |
out[0] = distanceToEnd(targetView, getHorizontalHelper(layoutManager)); | |
} | |
} else { | |
out[0] = 0; | |
} | |
if (layoutManager.canScrollVertically()) { | |
if (gravity == Gravity.TOP) { | |
out[1] = distanceToStart(targetView, getVerticalHelper(layoutManager)); | |
} else { // BOTTOM | |
out[1] = distanceToEnd(targetView, getVerticalHelper(layoutManager)); | |
} | |
} else { | |
out[1] = 0; | |
} | |
return out; | |
} | |
@Override | |
@Nullable | |
public View findSnapView(LayoutManager layoutManager) { | |
if (layoutManager instanceof LinearLayoutManager) { | |
switch (gravity) { | |
case Gravity.START: | |
return findStartView(layoutManager, getHorizontalHelper(layoutManager)); | |
case Gravity.TOP: | |
return findStartView(layoutManager, getVerticalHelper(layoutManager)); | |
case Gravity.END: | |
return findEndView(layoutManager, getHorizontalHelper(layoutManager)); | |
case Gravity.BOTTOM: | |
return findEndView(layoutManager, getVerticalHelper(layoutManager)); | |
default: | |
break; | |
} | |
} | |
return super.findSnapView(layoutManager); | |
} | |
@Override | |
public int findTargetSnapPosition(LayoutManager layoutManager, int velocityX, int velocityY) { | |
isFlingingInPositiveDirection = isRTL ? Math.max(velocityX, velocityY) < 0 : Math.max(velocityX, velocityY) > 0; | |
return super.findTargetSnapPosition(layoutManager, velocityX, velocityY); | |
} | |
/** | |
* Calculates the passed view's distance from the start of this parent. | |
*/ | |
private int distanceToStart(View targetView, OrientationHelper helper) { | |
if (isRTL && isGravityHorizontal) { | |
//TODO Check if this is still needed after updating support library. The helper currently assumes start == left and right == end. | |
return helper.getDecoratedEnd(targetView) - helper.getEndAfterPadding(); | |
} | |
return helper.getDecoratedStart(targetView) - helper.getStartAfterPadding(); | |
} | |
/** | |
* Calculates the passed view's distance from the end of this parent. | |
*/ | |
private int distanceToEnd(View targetView, OrientationHelper helper) { | |
if (isRTL && isGravityHorizontal) { | |
//TODO Check if this is still needed after updating support library. The helper currently assumes start == left and right == end. | |
return helper.getDecoratedStart(targetView) - helper.getStartAfterPadding(); | |
} | |
return helper.getDecoratedEnd(targetView) - helper.getEndAfterPadding(); | |
} | |
/** | |
* Returns the child view that is currently closest to the start of this parent. | |
* | |
* @param layoutManager The {@link LayoutManager} associated with the attached | |
* {@link RecyclerView}. | |
* @param helper The relevant {@link OrientationHelper} for the attached {@link RecyclerView}. | |
* | |
* @return the child view that is currently closest to the start of this parent. | |
*/ | |
@Nullable | |
private View findStartView(LayoutManager layoutManager, OrientationHelper helper) { | |
if (layoutManager instanceof LinearLayoutManager) { | |
int firstChild = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition(); | |
if (firstChild == RecyclerView.NO_POSITION) { | |
return null; | |
} | |
View child = layoutManager.findViewByPosition(firstChild); | |
//TODO Check if this is still needed after updating support library. The helper currently assumes start == left and right == end. | |
boolean shouldReturnChild; | |
if (isRTL && isGravityHorizontal) { | |
shouldReturnChild = helper.getDecoratedStart(child) <= helper.getDecoratedMeasurement(child) / 2; | |
} else { | |
shouldReturnChild = helper.getDecoratedEnd(child) >= helper.getDecoratedMeasurement(child) / 2; | |
} | |
if (isFlingingInPositiveDirection | |
&& ((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition() == layoutManager.getItemCount() - 1) { | |
return null; | |
} else if (shouldReturnChild) { | |
return child; | |
} else { | |
return layoutManager.findViewByPosition(firstChild + 1); | |
} | |
} | |
return super.findSnapView(layoutManager); | |
} | |
/** | |
* Returns the child view that is currently closest to the end of this parent. | |
* | |
* @param layoutManager The {@link LayoutManager} associated with the attached | |
* {@link RecyclerView}. | |
* @param helper The relevant {@link OrientationHelper} for the attached {@link RecyclerView}. | |
* | |
* @return the child view that is currently closest to the end of this parent. | |
*/ | |
@Nullable | |
private View findEndView(LayoutManager layoutManager, OrientationHelper helper) { | |
if (layoutManager instanceof LinearLayoutManager) { | |
int lastChild = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition(); | |
if (lastChild == RecyclerView.NO_POSITION) { | |
return null; | |
} | |
View child = layoutManager.findViewByPosition(lastChild); | |
//TODO Check if this is still needed after updating support library. The helper currently assumes start == left and right == end. | |
boolean shouldReturnChild; | |
if (isRTL && isGravityHorizontal) { | |
shouldReturnChild = helper.getDecoratedEnd(child) - helper.getDecoratedMeasurement(child) / 2 >= layoutManager.getPaddingLeft(); | |
} else { | |
shouldReturnChild = helper.getDecoratedStart(child) + helper.getDecoratedMeasurement(child) / 2 <= helper.getTotalSpace(); | |
} | |
if (!isFlingingInPositiveDirection | |
&& ((LinearLayoutManager) layoutManager).findFirstCompletelyVisibleItemPosition() == 0) { | |
return null; | |
} else if (shouldReturnChild) { | |
return child; | |
} else { | |
return layoutManager.findViewByPosition(lastChild - 1); | |
} | |
} | |
return super.findSnapView(layoutManager); | |
} | |
/** | |
* Creates and returns a vertical {@link OrientationHelper} for the passed {@link LayoutManager}. | |
*/ | |
@NonNull | |
private OrientationHelper getVerticalHelper(LayoutManager layoutManager) { | |
if (verticalHelper == null) { | |
verticalHelper = OrientationHelper.createVerticalHelper(layoutManager); | |
} | |
return verticalHelper; | |
} | |
/** | |
* Creates and returns a horizontal {@link OrientationHelper} for the passed {@link LayoutManager}. | |
*/ | |
@NonNull | |
private OrientationHelper getHorizontalHelper(LayoutManager layoutManager) { | |
if (horizontalHelper == null) { | |
horizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager); | |
} | |
return horizontalHelper; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment