Skip to content

Instantly share code, notes, and snippets.

@Sevastyan
Forked from mandybess/ItemDecoration.java
Created January 30, 2018 10:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Sevastyan/5a30cc11cb5f06a7bc2b8f0863eb2744 to your computer and use it in GitHub Desktop.
Save Sevastyan/5a30cc11cb5f06a7bc2b8f0863eb2744 to your computer and use it in GitHub Desktop.
RecyclerView extension that "sticks" items in the center on scroll
import android.graphics.Rect;
import android.support.v7.widget.RecyclerView;
import android.view.View;
public class ItemDecoration extends RecyclerView.ItemDecoration {
/**
*
* {@link #startPadding} and {@link #endPadding} are final and required on initialization
* because {@link android.support.v7.widget.RecyclerView.ItemDecoration} are drawn
* before the adapter's child views so you cannot rely on the child view measurements
* to determine padding as the two are connascent
*
* see {@see <a href="https://en.wikipedia.org/wiki/Connascence_(computer_programming)"}
*/
/**
* @param startPadding
* @param endPadding
*/
private final int startPadding;
private final int endPadding;
public ItemDecoration(int startPadding, int endPadding) {
this.startPadding = startPadding;
this.endPadding = endPadding;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
RecyclerView.State state) {
int totalWidth = parent.getWidth();
//first element
if (parent.getChildAdapterPosition(view) == 0) {
int firstPadding = (totalWidth - startPadding) / 2;
firstPadding = Math.max(0, firstPadding);
outRect.set(firstPadding, 0, 0, 0);
}
//last element
if (parent.getChildAdapterPosition(view) == parent.getAdapter().getItemCount() - 1 &&
parent.getAdapter().getItemCount() > 1) {
int lastPadding = (totalWidth - endPadding) / 2;
lastPadding = Math.max(0, lastPadding);
outRect.set(0, 0, lastPadding, 0);
}
}
}
Raw
import android.content.Context;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;
/**
* A {@link android.support.v7.widget.RecyclerView} implementation which snaps the current visible
* item to the center of the screen based on scroll directions defined by {@link
* #getScrollDirection()}
* <p>
* Designed to work with {@link LinearLayoutManager} with {@link #HORIZONTAL} orientation *ONLY*
*/
public class StickyRecyclerView extends RecyclerView {
/**
* The horizontal distance (in pixels) scrolled is > 0
*
* @see #getScrollDirection()
*/
public static final int SCROLL_DIRECTION_LEFT = 0;
/**
* The horizontal distance scrolled (in pixels) is < 0
*
* @see #getScrollDirection()
*/
public static final int SCROLL_DIRECTION_RIGHT = 1;
private int mScrollDirection;
private OnCenterItemChangedListener mCenterItemChangedListener;
public StickyRecyclerView(Context context) {
super(context);
}
public StickyRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public StickyRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
if (state == SCROLL_STATE_IDLE) {
if (mCenterItemChangedListener != null) {
mCenterItemChangedListener.onCenterItemChanged(findCenterViewIndex());
}
}
}
@Override
public void onScrolled(int dx, int dy) {
super.onScrolled(dx, dy);
setScrollDirection(dx);
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
float percentage = getPercentageFromCenter(child);
float scale = 1f - (0.4f * percentage);
child.setScaleX(scale);
child.setScaleY(scale);
}
}
@Override
public boolean fling(int velocityX, int velocityY) {
smoothScrollToCenter();
return true;
}
private float getPercentageFromCenter(View child) {
float centerX = (getMeasuredWidth() / 2);
float childCenterX = child.getX() + (child.getWidth() / 2);
float offSet = Math.max(centerX, childCenterX) - Math.min(centerX, childCenterX);
int maxOffset = (getMeasuredWidth() / 2) + child.getWidth();
return (offSet / maxOffset);
}
private int findCenterViewIndex() {
int count = getChildCount();
int index = -1;
int closest = Integer.MAX_VALUE;
int centerX = (getMeasuredWidth() / 2);
for (int i = 0; i < count; ++i) {
View child = getLayoutManager().getChildAt(i);
int childCenterX = (int) (child.getX() + (child.getWidth() / 2));
int distance = Math.abs(centerX - childCenterX);
if (distance < closest) {
closest = distance;
index = i;
}
}
if (index == -1) {
throw new IllegalStateException("Can\'t find central view.");
} else {
return index;
}
}
private void smoothScrollToCenter() {
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager();
int lastVisibleView = linearLayoutManager.findLastVisibleItemPosition();
int firstVisibleView = linearLayoutManager.findFirstVisibleItemPosition();
View firstView = linearLayoutManager.findViewByPosition(firstVisibleView);
View lastView = linearLayoutManager.findViewByPosition(lastVisibleView);
int screenWidth = this.getWidth();
//since views have variable sizes, we need to calculate side margins separately.
int leftMargin = (screenWidth - lastView.getWidth()) / 2;
int rightMargin = (screenWidth - firstView.getWidth()) / 2 + firstView.getWidth();
int leftEdge = lastView.getLeft();
int rightEdge = firstView.getRight();
int scrollDistanceLeft = leftEdge - leftMargin;
int scrollDistanceRight = rightMargin - rightEdge;
if (mScrollDirection == SCROLL_DIRECTION_LEFT) {
smoothScrollBy(scrollDistanceLeft, 0);
} else if (mScrollDirection == SCROLL_DIRECTION_RIGHT) {
smoothScrollBy(-scrollDistanceRight, 0);
}
}
/**
* Returns the last recorded scrolling direction of the StickyRecyclerView as
* set in {@link #onScrolled}
*
* @return {@link #SCROLL_DIRECTION_LEFT} or {@link #SCROLL_DIRECTION_RIGHT}
*/
public int getScrollDirection() {
return mScrollDirection;
}
private void setScrollDirection(int dx) {
mScrollDirection = dx >= 0 ? SCROLL_DIRECTION_LEFT : SCROLL_DIRECTION_RIGHT;
}
/**
* Set a listener that will be notified when the central item is changed.
*
* @param listener Listener to set or null to clear
*/
public void setOnCenterItemChangedListener(OnCenterItemChangedListener listener) {
mCenterItemChangedListener = listener;
}
/**
* A listener interface that can be added to the {@link StickyRecyclerView} to get
* notified when the central item is changed.
*/
public interface OnCenterItemChangedListener {
/**
* @param centerPosition position of the center item
*/
void onCenterItemChanged(int centerPosition);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment