这个是一个针对RecyclerView实现的滚动回弹效果,目前仅针对LinearLayoutManager做了拓展。核心代码是:
首先,通过修改LinearLayoutManager的滚动方法scrollVerticallyBy,实现over scroll。
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
/* scroll before content */
int scrolled = scrollVerticallyBefore(dy, recycler, state);
/* scroll content */
scrolled += super.scrollVerticallyBy(dy - scrolled, recycler, state);
/* scroll after content */
scrolled += scrollVerticallyAfter(dy - scrolled, recycler, state);
/* if still throw {java.lang.IllegalArgumentException: Pixel distance must be non-negative}
just disable prefect 0.0 */
//this.setItemPrefetchEnabled(mOverOffsetY == 0);
return scrolled;
}
然后修改onScrollStateChanged方法,在滚动结束的时候实现回弹效果:
@Override
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
if (state == RecyclerView.SCROLL_STATE_IDLE) {
if (mOverScrollY != 0) {
this.mRecyclerView.smoothScrollBy(0, -mOverScrollY);
}
}
}
完整代码如下:
/**
* Description: extend LinearLayoutManager for over scroll or fling
* Author: xuqingqi
* E-mail: [email protected]
* Date: 2017/6/15
*/
public class LinearLayoutManagerExtend extends LinearLayoutManager {
private static final String TAG = LinearLayoutManagerExtend.class.getSimpleName();
private static final int MAX_OVER_SCROLL_DOWN = -128;
private static final int MAX_OVER_SCROLL_UP = 128;
private RecyclerView mRecyclerView;
private int mMaxOverScrollUp;
private int mMaxOverScrollDown;
private int mOverScrollY;
private int mOverOffsetY;
private int mOverFactorY = 2;
private List mOverScrollListeners;
private boolean mOnFling = false;
public LinearLayoutManagerExtend(RecyclerView recyclerView, int orientation, boolean reverseLayout) {
super(recyclerView.getContext(), orientation, reverseLayout);
this.mRecyclerView = recyclerView;
this.mRecyclerView.setOverScrollMode(RecyclerView.OVER_SCROLL_NEVER);
float density = recyclerView.getContext().getResources().getDisplayMetrics().density;
this.mMaxOverScrollDown = (int) (MAX_OVER_SCROLL_DOWN * density * mOverFactorY);
this.mMaxOverScrollUp = (int) (MAX_OVER_SCROLL_UP * density * mOverFactorY);
}
@Override
public void collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state, LayoutPrefetchRegistry layoutPrefetchRegistry) {
if (mOverScrollY != 0) {
// TODO ugly bug fix. should not happen
return;
}
try {
super.collectAdjacentPrefetchPositions(dx, dy, state, layoutPrefetchRegistry);
} catch (IllegalArgumentException iae) {
iae.printStackTrace();
// TODO fix me, still throw #IllegalArgumentException: Pixel distance must be non-negative
}
}
@Override
public void collectInitialPrefetchPositions(int adapterItemCount, LayoutPrefetchRegistry layoutPrefetchRegistry) {
if (mOverScrollY != 0) {
// TODO ugly bug fix. should not happen
return;
}
super.collectInitialPrefetchPositions(adapterItemCount, layoutPrefetchRegistry);
}
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
/* scroll before content */
int scrolled = scrollVerticallyBefore(dy, recycler, state);
/* scroll content */
scrolled += super.scrollVerticallyBy(dy - scrolled, recycler, state);
/* scroll after content */
scrolled += scrollVerticallyAfter(dy - scrolled, recycler, state);
/* if still throw {java.lang.IllegalArgumentException: Pixel distance must be non-negative}
just disable prefect 0.0 */
//this.setItemPrefetchEnabled(mOverOffsetY == 0);
return scrolled;
}
@Override
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
return super.scrollHorizontallyBy(dx, recycler, state);
}
@SuppressWarnings("unused")
private int scrollVerticallyBefore(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
if (getOrientation() == HORIZONTAL || dy == 0) {
return 0;
}
int consumed = 0;
if (dy > 0 && mOverScrollY < 0) { /* scroll up */
consumed = dy + mOverScrollY > 0?
-mOverScrollY : dy;
} else if (dy < 0 && mOverScrollY > 0) { /* scroll down */
consumed = dy + mOverScrollY < 0?
-mOverScrollY : dy;
}
if (consumed != 0) {
mOverScrollY += consumed;
int offset = mOverScrollY / mOverFactorY - mOverOffsetY;
offsetChildrenVertical(-offset);
mOverOffsetY += offset; /* mOverOffsetY equals (int) (mOverScrollY * mOverFactorY) */
dispatchOverScrolled(consumed, offset);
Looger.I(this, "over scrolled before by " + consumed);
}
return consumed;
}
@SuppressWarnings("unused")
private int scrollVerticallyAfter(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
if (getOrientation() == HORIZONTAL || dy == 0) {
return 0;
}
int consumed = 0;
if (dy > 0) { /* scroll up */
consumed = dy + mOverScrollY > mMaxOverScrollUp?
mMaxOverScrollUp - mOverScrollY : dy;
} else if (dy < 0) { /* scroll down */
consumed = dy + mOverScrollY < mMaxOverScrollDown?
mMaxOverScrollDown - mOverScrollY : dy;
}
if (consumed != 0) {
mOverScrollY += consumed;
int offset = mOverScrollY / mOverFactorY - mOverOffsetY;
offsetChildrenVertical(-offset);
mOverOffsetY += offset; /* mOverOffsetY equals {mOverScrollY / mOverFactorY} */
dispatchOverScrolled(consumed, offset);
Looger.I(this, "over scrolled after by " + consumed);
}
return consumed;
}
@Override
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
if (state == RecyclerView.SCROLL_STATE_IDLE) {
if (mOverScrollY != 0) {
this.mRecyclerView.smoothScrollBy(0, -mOverScrollY);
}
}
}
@SuppressWarnings("unused")
private Context getContext() {
return this.mRecyclerView.getContext();
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state){
super.onLayoutChildren(recycler, state);
}
@Override
public void onLayoutCompleted(RecyclerView.State state) {
super.onLayoutCompleted(state);
updateLayoutState();
}
private void updateLayoutState() {
mOverScrollY = 0;
mOverOffsetY = 0;
dispatchStateUpdated();
}
@Override
public void onAttachedToWindow(RecyclerView view) {
updateLayoutState();
super.onAttachedToWindow(view);
}
@Override
public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
super.onDetachedFromWindow(view, recycler);
updateLayoutState();
}
public void addOverScroll(OnOverScrollListener listener) {
if (listener == null) {
return;
}
if (mOverScrollListeners == null) {
mOverScrollListeners = new ArrayList<>();
}
if (mOverScrollListeners.contains(listener)) {
return;
}
mOverScrollListeners.add(listener);
}
public void removeOverScroll(OnOverScrollListener listener) {
if (listener == null) {
return;
}
if (mOverScrollListeners == null) {
return;
}
mOverScrollListeners.remove(listener);
}
private void dispatchOverScrolled(int dScroll, int dOffset) {
if (mOverScrollListeners == null) {
return;
}
for (int i = 0; i < mOverScrollListeners.size(); i++) {
OnOverScrollListener listener = mOverScrollListeners.get(i);
listener.onOverScrolled(mRecyclerView, mOverScrollY, mOverOffsetY, dScroll, dOffset);
}
}
private void dispatchStateUpdated() {
if (mOverScrollListeners == null) {
return;
}
for (int i = 0; i < mOverScrollListeners.size(); i++) {
OnOverScrollListener listener = mOverScrollListeners.get(i);
listener.onStateUpdated(mRecyclerView, mOverScrollY, mOverOffsetY);
}
}
public void onFling(int velocityX, int velocityY) {
mOnFling = true;
}
}