- 分析
- 其实质是view事件分发的处理
public class HorizonSlideRecycleView extends RecyclerView {
private boolean mSnapEnabled = false;
private boolean mUserScrolling = false;
private boolean mScrolling = false;
private int mScrollState;
private long lastScrollTime = 0;
private final static int MINIMUM_SCROLL_EVENT_OFFSET_MS = 20;
public HorizonSlideRecycleView(Context context) {
this(context,null);
}
public HorizonSlideRecycleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public void setSnapEnabled(boolean enabled) {
mSnapEnabled = enabled;
if (enabled) {
addOnScrollListener(new OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == SCROLL_STATE_TOUCH_SCROLL && !mScrolling) { //正在滚动
mUserScrolling = true;
} else if (newState == SCROLL_STATE_FLING) {
mScrolling = true;
}else if (newState == SCROLL_STATE_IDLE) {//停止滚动时
if (mUserScrolling) {
scrollToView(getCenterView());
}
mUserScrolling = false;
mScrolling = false;
}
mScrollState = newState;
}
});
} else {
addOnScrollListener(null);
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (!mSnapEnabled) {
return super.dispatchTouchEvent(event);
}
long currentTime = System.currentTimeMillis();
/** if touch events are being spammed, this is due to user scrolling right after a tap,
* so set userScrolling to true **/
if (mScrolling && mScrollState == SCROLL_STATE_TOUCH_SCROLL) {
if ((currentTime - lastScrollTime) < MINIMUM_SCROLL_EVENT_OFFSET_MS) {
mUserScrolling = true;
}
}
lastScrollTime = currentTime;
View targetView = getChildClosestToPosition((int) event.getX());
if (!mUserScrolling) {
if (event.getAction() == MotionEvent.ACTION_UP) {
if (targetView != getCenterView()) {
scrollToView(targetView);
return true;
}
}
}
return super.dispatchTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
if (!mSnapEnabled) {
return super.onInterceptTouchEvent(e);
}
View targetView = getChildClosestToPosition((int) e.getX());
if (targetView != getCenterView()) {
return true;
}
return super.onInterceptTouchEvent(e);
}
private View getChildClosestToPosition(int x) {
if (getChildCount() <= 0) {
return null;
} else {
int itemWidth = getChildAt(0).getMeasuredWidth();
int closestX = 9999;
View closestChild = null;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
int childCenterX = ((int) child.getX() + (itemWidth / 2));
int xDistance = childCenterX - x;
/** if child center is closer than previous closest, set it as closest **/
if (Math.abs(xDistance) < Math.abs(closestX)) {
closestX = xDistance;
closestChild = child;
}
}
return closestChild;
}
}
private View getCenterView() {
return getChildClosestToPosition(getMeasuredWidth() / 2);
}
private void scrollToView(View child) {
if (child == null)
return;
stopScroll();
int scrollDistance = getScrollDistance(child);
if (scrollDistance != 0)
smoothScrollBy(scrollDistance, 0);
}
private int getScrollDistance(View child) {
int x = (int) child.getX();
int scrollX = child.getScrollX();
return x-scrollX;
}
@Override
public boolean fling(int velocityX, int velocityY) {
// velocityY *= 0.7;
velocityX *= 0.3; //for Horizontal recycler view. comment velocityY line not require for Horizontal Mode.
return super.fling(velocityX, velocityY);
}
}