leochuan / ViewPagerLayoutManager

ViewPager like LayoutManager which supports some awesome animations and infinite scroll.
Apache License 2.0
1.75k stars 330 forks source link

很严重的BUG!用了CenterSnapHelper().attachToRecyclerView(mRecyvlerView)后点击事件会失效 #50

Closed CordyHo closed 6 years ago

CordyHo commented 6 years ago

用ScaleLayoutManager作为RecyvlerView的LayoutManager,再用CenterSnapHelper().attachToRecyclerView(mRecyvlerView)后,RecyvlerView的item点击事件就会失效,无法,及时修复!

leochuan commented 6 years ago

CenterSnapHelper 并没有对点击事件做拦截什么的,你运行demo是好的话?很有可能是其他原因引起的

CordyHo commented 6 years ago

就是用了CenterSnapHelper 之后,滑动的时候,中间的item有时会不完全居中,就是看起来居中,但实际有些许偏移的,这时候就会出现item无法点击的情况,包括用smoothScrollToPosition()也会出现这种情况

leochuan commented 6 years ago

你有修改滚动速度什么的吗?用的版本是多少?

leochuan commented 6 years ago

之前是有过 你可以看下#37 但是已经修复了 我自己这边运行下来也都是没有问题的 你看下版本 然后尽量把问题范围缩小一点 不然 我也不太好定位

CordyHo commented 6 years ago

没有设置滚动速度,用的就是你目前得demo的library,没有用远程依赖,直接下载下来的; 下面就是代码: rv_wine = findViewById(R.id.rv_wine); ScaleLayoutManager scaleLayoutManager = new ScaleLayoutManager(this, 0); rv_wine.setLayoutManager(scaleLayoutManager); new CenterSnapHelper().attachToRecyclerView(rv_wine);

leochuan commented 6 years ago

那应该是不会有问题 你方便的话 抽个demo给我 我运行下 看看

CordyHo commented 6 years ago

是个BUG,默认不设置速度的话就会出现item不能点击的情况,而我设置了scaleLayoutManager.setMoveSpeed(5f); 就正常了,怎么修复?

leochuan commented 6 years ago

你把你整个ViewPagerLayoutManager的代码贴上来我看看

CordyHo commented 6 years ago

就是你现在github那个

`package com.leochuan;

import android.content.Context; import android.os.Parcel; import android.os.Parcelable; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.SparseArray; import android.view.View; import android.view.ViewGroup; import android.view.animation.Interpolator;

import java.util.ArrayList;

import static android.support.v7.widget.RecyclerView.NO_POSITION;

/**

@SuppressWarnings({"WeakerAccess", "unused", "SameParameterValue"}) public abstract class ViewPagerLayoutManager extends LinearLayoutManager {

public static final int DETERMINE_BY_MAX_AND_MIN = -1;

public static final int HORIZONTAL = OrientationHelper.HORIZONTAL;

public static final int VERTICAL = OrientationHelper.VERTICAL;

private static final int DIRECTION_NO_WHERE = -1;

private static final int DIRECTION_FORWARD = 0;

private static final int DIRECTION_BACKWARD = 1;

protected static final int INVALID_SIZE = Integer.MAX_VALUE;

private SparseArray<View> positionCache = new SparseArray<>();

protected int mDecoratedMeasurement;

protected int mDecoratedMeasurementInOther;

/**
 * Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL}
 */
int mOrientation;

protected int mSpaceMain;

protected int mSpaceInOther;

/**
 * The offset of property which will change while scrolling
 */
protected float mOffset;

/**
 * Many calculations are made depending on orientation. To keep it clean, this interface
 * helps {@link LinearLayoutManager} make those decisions.
 * Based on {@link #mOrientation}, an implementation is lazily created in
 * {@link #ensureLayoutState} method.
 */
protected OrientationHelper mOrientationHelper;

/**
 * Defines if layout should be calculated from end to start.
 */
private boolean mReverseLayout = false;

/**
 * This keeps the final value for how LayoutManager should start laying out views.
 * It is calculated by checking {@link #getReverseLayout()} and View's layout direction.
 * {@link #onLayoutChildren(RecyclerView.Recycler, RecyclerView.State)} is run.
 */
private boolean mShouldReverseLayout = false;

/**
 * Works the same way as {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}.
 * see {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}
 */
private boolean mSmoothScrollbarEnabled = true;

/**
 * When LayoutManager needs to scroll to a position, it sets this variable and requests a
 * layout which will check this variable and re-layout accordingly.
 */
private int mPendingScrollPosition = NO_POSITION;

private SavedState mPendingSavedState = null;

protected float mInterval; //the mInterval of each item's mOffset

/* package */ OnPageChangeListener onPageChangeListener;

private boolean mRecycleChildrenOnDetach;

private boolean mInfinite = false;

private boolean mEnableBringCenterToFront;

private int mLeftItems;

private int mRightItems;

/**
 * max visible item count
 */
private int mMaxVisibleItemCount = DETERMINE_BY_MAX_AND_MIN;

private Interpolator mSmoothScrollInterpolator;

private int mDistanceToBottom = INVALID_SIZE;

/**
 * use for handle focus
 */
private View currentFocusView;

/**
 * @return the mInterval of each item's mOffset
 */
protected abstract float setInterval();

protected abstract void setItemViewProperty(View itemView, float targetOffset);

/**
 * cause elevation is not support below api 21,
 * so you can set your elevation here for supporting it below api 21
 * or you can just setElevation in {@link #setItemViewProperty(View, float)}
 */
protected float setViewElevation(View itemView, float targetOffset) {
    return 0;
}

/**
 * Creates a horizontal ViewPagerLayoutManager
 */
public ViewPagerLayoutManager(Context context) {
    this(context, HORIZONTAL, false);
}

/**
 * @param orientation   Layout orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL}
 * @param reverseLayout When set to true, layouts from end to start
 */
public ViewPagerLayoutManager(Context context, int orientation, boolean reverseLayout) {
    super(context);
    setOrientation(orientation);
    setReverseLayout(reverseLayout);
    setAutoMeasureEnabled(true);
    setItemPrefetchEnabled(false);
}

@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
    return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
            ViewGroup.LayoutParams.WRAP_CONTENT);
}

/**
 * Returns whether LayoutManager will recycle its children when it is detached from
 * RecyclerView.
 *
 * @return true if LayoutManager will recycle its children when it is detached from
 * RecyclerView.
 */
public boolean getRecycleChildrenOnDetach() {
    return mRecycleChildrenOnDetach;
}

/**
 * Set whether LayoutManager will recycle its children when it is detached from
 * RecyclerView.
 * <p>
 * If you are using a {@link RecyclerView.RecycledViewPool}, it might be a good idea to set
 * this flag to <code>true</code> so that views will be available to other RecyclerViews
 * immediately.
 * <p>
 * Note that, setting this flag will result in a performance drop if RecyclerView
 * is restored.
 *
 * @param recycleChildrenOnDetach Whether children should be recycled in detach or not.
 */
public void setRecycleChildrenOnDetach(boolean recycleChildrenOnDetach) {
    mRecycleChildrenOnDetach = recycleChildrenOnDetach;
}

@Override
public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
    super.onDetachedFromWindow(view, recycler);
    if (mRecycleChildrenOnDetach) {
        removeAndRecycleAllViews(recycler);
        recycler.clear();
    }
}

@Override
public Parcelable onSaveInstanceState() {
    if (mPendingSavedState != null) {
        return new SavedState(mPendingSavedState);
    }
    SavedState savedState = new SavedState();
    savedState.position = mPendingScrollPosition;
    savedState.offset = mOffset;
    savedState.isReverseLayout = mShouldReverseLayout;
    return savedState;
}

@Override
public void onRestoreInstanceState(Parcelable state) {
    if (state instanceof SavedState) {
        mPendingSavedState = new SavedState((SavedState) state);
        requestLayout();
    }
}

/**
 * @return true if {@link #getOrientation()} is {@link #HORIZONTAL}
 */
@Override
public boolean canScrollHorizontally() {
    return mOrientation == HORIZONTAL;
}

/**
 * @return true if {@link #getOrientation()} is {@link #VERTICAL}
 */
@Override
public boolean canScrollVertically() {
    return mOrientation == VERTICAL;
}

/**
 * Returns the current orientation of the layout.
 *
 * @return Current orientation,  either {@link #HORIZONTAL} or {@link #VERTICAL}
 * @see #setOrientation(int)
 */
public int getOrientation() {
    return mOrientation;
}

/**
 * Sets the orientation of the layout. {@link ViewPagerLayoutManager}
 * will do its best to keep scroll position.
 *
 * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL}
 */
public void setOrientation(int orientation) {
    if (orientation != HORIZONTAL && orientation != VERTICAL) {
        throw new IllegalArgumentException("invalid orientation:" + orientation);
    }
    assertNotInLayoutOrScroll(null);
    if (orientation == mOrientation) {
        return;
    }
    mOrientation = orientation;
    mOrientationHelper = null;
    mDistanceToBottom = INVALID_SIZE;
    removeAllViews();
}

/**
 * Returns the max visible item count, {@link #DETERMINE_BY_MAX_AND_MIN} means it haven't been set now
 * And it will use {@link #maxRemoveOffset()} and {@link #minRemoveOffset()} to handle the range
 *
 * @return Max visible item count
 */
public int getMaxVisibleItemCount() {
    return mMaxVisibleItemCount;
}

/**
 * Set the max visible item count, {@link #DETERMINE_BY_MAX_AND_MIN} means it haven't been set now
 * And it will use {@link #maxRemoveOffset()} and {@link #minRemoveOffset()} to handle the range
 *
 * @param mMaxVisibleItemCount Max visible item count
 */
public void setMaxVisibleItemCount(int mMaxVisibleItemCount) {
    assertNotInLayoutOrScroll(null);
    if (this.mMaxVisibleItemCount == mMaxVisibleItemCount) return;
    this.mMaxVisibleItemCount = mMaxVisibleItemCount;
    removeAllViews();
}

/**
 * Calculates the view layout order. (e.g. from end to start or start to end)
 * RTL layout support is applied automatically. So if layout is RTL and
 * {@link #getReverseLayout()} is {@code true}, elements will be laid out starting from left.
 */
private void resolveShouldLayoutReverse() {
    // A == B is the same result, but we rather keep it readable
    if (mOrientation == VERTICAL || !isLayoutRTL()) {
        mShouldReverseLayout = mReverseLayout;
    } else {
        mShouldReverseLayout = !mReverseLayout;
    }
}

/**
 * Returns if views are laid out from the opposite direction of the layout.
 *
 * @return If layout is reversed or not.
 * @see #setReverseLayout(boolean)
 */
public boolean getReverseLayout() {
    return mReverseLayout;
}

/**
 * Used to reverse item traversal and layout order.
 * This behaves similar to the layout change for RTL views. When set to true, first item is
 * laid out at the end of the UI, second item is laid out before it etc.
 * <p>
 * For horizontal layouts, it depends on the layout direction.
 * When set to true, If {@link android.support.v7.widget.RecyclerView} is LTR, than it will
 * layout from RTL, if {@link android.support.v7.widget.RecyclerView}} is RTL, it will layout
 * from LTR.
 */
public void setReverseLayout(boolean reverseLayout) {
    assertNotInLayoutOrScroll(null);
    if (reverseLayout == mReverseLayout) {
        return;
    }
    mReverseLayout = reverseLayout;
    removeAllViews();
}

public void setSmoothScrollInterpolator(Interpolator smoothScrollInterpolator) {
    this.mSmoothScrollInterpolator = smoothScrollInterpolator;
}

@Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
    final int offsetPosition = getOffsetToPosition(position);
    if (mOrientation == VERTICAL) {
        recyclerView.smoothScrollBy(0, offsetPosition, mSmoothScrollInterpolator);
    } else {
        recyclerView.smoothScrollBy(offsetPosition, 0, mSmoothScrollInterpolator);
    }
}

@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    if (state.getItemCount() == 0) {
        removeAndRecycleAllViews(recycler);
        mOffset = 0;
        return;
    }

    ensureLayoutState();
    resolveShouldLayoutReverse();

    //make sure properties are correct while measure more than once
    View scrap = recycler.getViewForPosition(0);
    measureChildWithMargins(scrap, 0, 0);
    mDecoratedMeasurement = mOrientationHelper.getDecoratedMeasurement(scrap);
    mDecoratedMeasurementInOther = mOrientationHelper.getDecoratedMeasurementInOther(scrap);
    mSpaceMain = (mOrientationHelper.getTotalSpace() - mDecoratedMeasurement) / 2;
    if (mDistanceToBottom == INVALID_SIZE) {
        mSpaceInOther = (mOrientationHelper.getTotalSpaceInOther() - mDecoratedMeasurementInOther) / 2;
    } else {
        mSpaceInOther = mOrientationHelper.getTotalSpaceInOther() - mDecoratedMeasurementInOther - mDistanceToBottom;
    }

    mInterval = setInterval();
    setUp();
    mLeftItems = (int) Math.abs(minRemoveOffset() / mInterval) + 1;
    mRightItems = (int) Math.abs(maxRemoveOffset() / mInterval) + 1;

    if (mPendingSavedState != null) {
        mShouldReverseLayout = mPendingSavedState.isReverseLayout;
        mPendingScrollPosition = mPendingSavedState.position;
        mOffset = mPendingSavedState.offset;
    }

    if (mPendingScrollPosition != NO_POSITION) {
        mOffset = mShouldReverseLayout ?
                mPendingScrollPosition * -mInterval : mPendingScrollPosition * mInterval;
    }

    detachAndScrapAttachedViews(recycler);
    layoutItems(recycler);
}

@Override
public void onLayoutCompleted(RecyclerView.State state) {
    super.onLayoutCompleted(state);
    mPendingSavedState = null;
    mPendingScrollPosition = NO_POSITION;
}

@Override
public boolean onAddFocusables(RecyclerView recyclerView, ArrayList<View> views, int direction, int focusableMode) {
    final int currentPosition = getCurrentPosition();
    final View currentView = findViewByPosition(currentPosition);
    if (currentView == null) return true;
    if (recyclerView.hasFocus()) {
        final int movement = getMovement(direction);
        if (movement != DIRECTION_NO_WHERE) {
            final int targetPosition = movement == DIRECTION_BACKWARD ?
                    currentPosition - 1 : currentPosition + 1;
            recyclerView.smoothScrollToPosition(targetPosition);
        }
    } else {
        currentView.addFocusables(views, direction, focusableMode);
    }
    return true;
}

@Override
public View onFocusSearchFailed(View focused, int focusDirection, RecyclerView.Recycler recycler, RecyclerView.State state) {
    return null;
}

private int getMovement(int direction) {
    if (mOrientation == VERTICAL) {
        if (direction == View.FOCUS_UP) {
            return mShouldReverseLayout ? DIRECTION_FORWARD : DIRECTION_BACKWARD;
        } else if (direction == View.FOCUS_DOWN) {
            return mShouldReverseLayout ? DIRECTION_BACKWARD : DIRECTION_FORWARD;
        } else {
            return DIRECTION_NO_WHERE;
        }
    } else {
        if (direction == View.FOCUS_LEFT) {
            return mShouldReverseLayout ? DIRECTION_FORWARD : DIRECTION_BACKWARD;
        } else if (direction == View.FOCUS_RIGHT) {
            return mShouldReverseLayout ? DIRECTION_BACKWARD : DIRECTION_FORWARD;
        } else {
            return DIRECTION_NO_WHERE;
        }
    }
}

void ensureLayoutState() {
    if (mOrientationHelper == null) {
        mOrientationHelper = OrientationHelper.createOrientationHelper(this, mOrientation);
    }
}

/**
 * You can set up your own properties here or change the exist properties like mSpaceMain and mSpaceInOther
 */
protected void setUp() {

}

private float getProperty(int position) {
    return mShouldReverseLayout ? position * -mInterval : position * mInterval;
}

@Override
public void onAdapterChanged(RecyclerView.Adapter oldAdapter, RecyclerView.Adapter newAdapter) {
    removeAllViews();
    mOffset = 0;
}

@Override
public void scrollToPosition(int position) {
    if (!mInfinite && (position < 0 || position >= getItemCount())) return;
    mPendingScrollPosition = position;
    mOffset = mShouldReverseLayout ? position * -mInterval : position * mInterval;
    requestLayout();
}

@Override
public int computeHorizontalScrollOffset(RecyclerView.State state) {
    return computeScrollOffset();
}

@Override
public int computeVerticalScrollOffset(RecyclerView.State state) {
    return computeScrollOffset();
}

@Override
public int computeHorizontalScrollExtent(RecyclerView.State state) {
    return computeScrollExtent();
}

@Override
public int computeVerticalScrollExtent(RecyclerView.State state) {
    return computeScrollExtent();
}

@Override
public int computeHorizontalScrollRange(RecyclerView.State state) {
    return computeScrollRange();
}

@Override
public int computeVerticalScrollRange(RecyclerView.State state) {
    return computeScrollRange();
}

private int computeScrollOffset() {
    if (getChildCount() == 0) {
        return 0;
    }

    if (!mSmoothScrollbarEnabled) {
        return !mShouldReverseLayout ?
                getCurrentPosition() : getItemCount() - getCurrentPosition() - 1;
    }

    final float realOffset = getOffsetOfRightAdapterPosition();
    return !mShouldReverseLayout ? (int) realOffset : (int) ((getItemCount() - 1) * mInterval + realOffset);
}

private int computeScrollExtent() {
    if (getChildCount() == 0) {
        return 0;
    }

    if (!mSmoothScrollbarEnabled) {
        return 1;
    }

    return (int) mInterval;
}

private int computeScrollRange() {
    if (getChildCount() == 0) {
        return 0;
    }

    if (!mSmoothScrollbarEnabled) {
        return getItemCount();
    }

    return (int) (getItemCount() * mInterval);
}

@Override
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
    if (mOrientation == VERTICAL) {
        return 0;
    }
    return scrollBy(dx, recycler, state);
}

@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
    if (mOrientation == HORIZONTAL) {
        return 0;
    }
    return scrollBy(dy, recycler, state);
}

private int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
    if (getChildCount() == 0 || dy == 0) {
        return 0;
    }
    ensureLayoutState();
    int willScroll = dy;

    float realDx = dy / getDistanceRatio();
    if (Math.abs(realDx) < 0.00000001f) {
        return 0;
    }
    float targetOffset = mOffset + realDx;

    //handle the boundary
    if (!mInfinite && targetOffset < getMinOffset()) {
        willScroll -= (targetOffset - getMinOffset()) * getDistanceRatio();
    } else if (!mInfinite && targetOffset > getMaxOffset()) {
        willScroll = (int) ((getMaxOffset() - mOffset) * getDistanceRatio());
    }

    realDx = willScroll / getDistanceRatio();

    mOffset += realDx;

    //handle recycle
    layoutItems(recycler);

    return willScroll;
}

private void layoutItems(RecyclerView.Recycler recycler) {
    detachAndScrapAttachedViews(recycler);
    positionCache.clear();

    final int itemCount = getItemCount();
    if (itemCount == 0) return;

    // make sure that current position start from 0 to 1
    final int currentPos = mShouldReverseLayout ?
            -getCurrentPositionOffset() : getCurrentPositionOffset();
    int start = currentPos - mLeftItems;
    int end = currentPos + mRightItems;

    // handle max visible count
    if (useMaxVisibleCount()) {
        boolean isEven = mMaxVisibleItemCount % 2 == 0;
        if (isEven) {
            int offset = mMaxVisibleItemCount / 2;
            start = currentPos - offset + 1;
            end = currentPos + offset + 1;
        } else {
            int offset = (mMaxVisibleItemCount - 1) / 2;
            start = currentPos - offset;
            end = currentPos + offset + 1;
        }
    }

    if (!mInfinite) {
        if (start < 0) {
            start = 0;
            if (useMaxVisibleCount()) end = mMaxVisibleItemCount;
        }
        if (end > itemCount) end = itemCount;
    }

    float lastOrderWeight = Float.MIN_VALUE;
    for (int i = start; i < end; i++) {
        if (useMaxVisibleCount() || !removeCondition(getProperty(i) - mOffset)) {
            // start and end base on current position,
            // so we need to calculate the adapter position
            int adapterPosition = i;
            if (i >= itemCount) {
                adapterPosition %= itemCount;
            } else if (i < 0) {
                int delta = (-adapterPosition) % itemCount;
                if (delta == 0) delta = itemCount;
                adapterPosition = itemCount - delta;
            }
            final View scrap = recycler.getViewForPosition(adapterPosition);
            measureChildWithMargins(scrap, 0, 0);
            resetViewProperty(scrap);
            // we need i to calculate the real offset of current view
            final float targetOffset = getProperty(i) - mOffset;
            layoutScrap(scrap, targetOffset);
            final float orderWeight = mEnableBringCenterToFront ?
                    setViewElevation(scrap, targetOffset) : adapterPosition;
            if (orderWeight > lastOrderWeight) {
                addView(scrap);
            } else {
                addView(scrap, 0);
            }
            if (i == currentPos) currentFocusView = scrap;
            lastOrderWeight = orderWeight;
            positionCache.put(i, scrap);
        }
    }

    currentFocusView.requestFocus();
}

private boolean useMaxVisibleCount() {
    return mMaxVisibleItemCount != DETERMINE_BY_MAX_AND_MIN;
}

private boolean removeCondition(float targetOffset) {
    return targetOffset > maxRemoveOffset() || targetOffset < minRemoveOffset();
}

private void resetViewProperty(View v) {
    v.setRotation(0);
    v.setRotationY(0);
    v.setRotationX(0);
    v.setScaleX(1f);
    v.setScaleY(1f);
    v.setAlpha(1f);
}

private float getMaxOffset() {
    return !mShouldReverseLayout ? (getItemCount() - 1) * mInterval : 0;
}

private float getMinOffset() {
    return !mShouldReverseLayout ? 0 : -(getItemCount() - 1) * mInterval;
}

private void layoutScrap(View scrap, float targetOffset) {
    final int left = calItemLeft(scrap, targetOffset);
    final int top = calItemTop(scrap, targetOffset);
    if (mOrientation == VERTICAL) {
        layoutDecorated(scrap, mSpaceInOther + left, mSpaceMain + top,
                mSpaceInOther + left + mDecoratedMeasurementInOther, mSpaceMain + top + mDecoratedMeasurement);
    } else {
        layoutDecorated(scrap, mSpaceMain + left, mSpaceInOther + top,
                mSpaceMain + left + mDecoratedMeasurement, mSpaceInOther + top + mDecoratedMeasurementInOther);
    }
    setItemViewProperty(scrap, targetOffset);
}

protected int calItemLeft(View itemView, float targetOffset) {
    return mOrientation == VERTICAL ? 0 : (int) targetOffset;
}

protected int calItemTop(View itemView, float targetOffset) {
    return mOrientation == VERTICAL ? (int) targetOffset : 0;
}

/**
 * when the target offset reach this,
 * the view will be removed and recycled in {@link #layoutItems(RecyclerView.Recycler)}
 */
protected float maxRemoveOffset() {
    return mOrientationHelper.getTotalSpace() - mSpaceMain;
}

/**
 * when the target offset reach this,
 * the view will be removed and recycled in {@link #layoutItems(RecyclerView.Recycler)}
 */
protected float minRemoveOffset() {
    return -mDecoratedMeasurement - mOrientationHelper.getStartAfterPadding() - mSpaceMain;
}

protected float propertyChangeWhenScroll(View itemView) {
    if (mOrientation == VERTICAL)
        return itemView.getTop() - mSpaceMain;
    return itemView.getLeft() - mSpaceMain;
}

protected float getDistanceRatio() {
    return 1f;
}

public int getCurrentPosition() {
    if (getItemCount() == 0) return 0;

    int position = getCurrentPositionOffset();
    if (!mInfinite) return Math.abs(position);

    position = !mShouldReverseLayout ?
            //take care of position = getItemCount()
            (position >= 0 ?
                    position % getItemCount() :
                    getItemCount() + position % getItemCount()) :
            (position > 0 ?
                    getItemCount() - position % getItemCount() :
                    -position % getItemCount());
    return position == getItemCount() ? 0 : position;
}

@Override
public View findViewByPosition(int position) {
    final int itemCount = getItemCount();
    if (itemCount == 0) return null;
    for (int i = 0; i < positionCache.size(); i++) {
        final int key = positionCache.keyAt(i);
        if (key >= 0) {
            if (position == key % itemCount) return positionCache.valueAt(i);
        } else {
            int delta = key % itemCount;
            if (delta == 0) delta = -itemCount;
            if (itemCount + delta == position) return positionCache.valueAt(i);
        }
    }
    return null;
}

private int getCurrentPositionOffset() {
    return Math.round(mOffset / mInterval);
}

/**
 * Sometimes we need to get the right offset of matching adapter position
 * cause when {@link #mInfinite} is set true, there will be no limitation of {@link #mOffset}
 */
private float getOffsetOfRightAdapterPosition() {
    if (mShouldReverseLayout)
        return mInfinite ?
                (mOffset <= 0 ?
                        (mOffset % (mInterval * getItemCount())) :
                        (getItemCount() * -mInterval + mOffset % (mInterval * getItemCount()))) :
                mOffset;
    else
        return mInfinite ?
                (mOffset >= 0 ?
                        (mOffset % (mInterval * getItemCount())) :
                        (getItemCount() * mInterval + mOffset % (mInterval * getItemCount()))) :
                mOffset;
}

/**
 * used by {@link CenterSnapHelper} to center the current view
 *
 * @return the dy between center and current position
 */
public int getOffsetToCenter() {
    if (mInfinite)
        return (int) ((getCurrentPositionOffset() * mInterval - mOffset) * getDistanceRatio());
    return (int) ((getCurrentPosition() *
            (!mShouldReverseLayout ? mInterval : -mInterval) - mOffset) * getDistanceRatio());
}

public int getOffsetToPosition(int position) {
    if (mInfinite)
        return (int) (((getCurrentPositionOffset() +
                (!mShouldReverseLayout ? position - getCurrentPosition() : getCurrentPosition() - position)) *
                mInterval - mOffset) * getDistanceRatio());
    return (int) ((position *
            (!mShouldReverseLayout ? mInterval : -mInterval) - mOffset) * getDistanceRatio());
}

public void setOnPageChangeListener(OnPageChangeListener onPageChangeListener) {
    this.onPageChangeListener = onPageChangeListener;
}

public void setInfinite(boolean enable) {
    assertNotInLayoutOrScroll(null);
    if (enable == mInfinite) {
        return;
    }
    mInfinite = enable;
    requestLayout();
}

public boolean getInfinite() {
    return mInfinite;
}

public int getDistanceToBottom() {
    return mDistanceToBottom == INVALID_SIZE ?
            (mOrientationHelper.getTotalSpaceInOther() - mDecoratedMeasurementInOther) / 2 : mDistanceToBottom;
}

public void setDistanceToBottom(int mDistanceToBottom) {
    assertNotInLayoutOrScroll(null);
    if (this.mDistanceToBottom == mDistanceToBottom) return;
    this.mDistanceToBottom = mDistanceToBottom;
    removeAllViews();
}

/**
 * When smooth scrollbar is enabled, the position and size of the scrollbar thumb is computed
 * based on the number of visible pixels in the visible items. This however assumes that all
 * list items have similar or equal widths or heights (depending on list orientation).
 * If you use a list in which items have different dimensions, the scrollbar will change
 * appearance as the user scrolls through the list. To avoid this issue,  you need to disable
 * this property.
 * <p>
 * When smooth scrollbar is disabled, the position and size of the scrollbar thumb is based
 * solely on the number of items in the adapter and the position of the visible items inside
 * the adapter. This provides a stable scrollbar as the user navigates through a list of items
 * with varying widths / heights.
 *
 * @param enabled Whether or not to enable smooth scrollbar.
 * @see #setSmoothScrollbarEnabled(boolean)
 */
public void setSmoothScrollbarEnabled(boolean enabled) {
    mSmoothScrollbarEnabled = enabled;
}

public void setEnableBringCenterToFront(boolean bringCenterToTop) {
    assertNotInLayoutOrScroll(null);
    if (mEnableBringCenterToFront == bringCenterToTop) {
        return;
    }
    this.mEnableBringCenterToFront = bringCenterToTop;
    requestLayout();
}

public boolean getEnableBringCenterToFront() {
    return mEnableBringCenterToFront;
}

/**
 * Returns the current state of the smooth scrollbar feature. It is enabled by default.
 *
 * @return True if smooth scrollbar is enabled, false otherwise.
 * @see #setSmoothScrollbarEnabled(boolean)
 */
public boolean getSmoothScrollbarEnabled() {
    return mSmoothScrollbarEnabled;
}

private static class SavedState implements Parcelable {
    int position;
    float offset;
    boolean isReverseLayout;

    SavedState() {

    }

    SavedState(Parcel in) {
        position = in.readInt();
        offset = in.readFloat();
        isReverseLayout = in.readInt() == 1;
    }

    public SavedState(SavedState other) {
        position = other.position;
        offset = other.offset;
        isReverseLayout = other.isReverseLayout;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(position);
        dest.writeFloat(offset);
        dest.writeInt(isReverseLayout ? 1 : 0);
    }

    public static final Parcelable.Creator<SavedState> CREATOR
            = new Parcelable.Creator<SavedState>() {
        @Override
        public SavedState createFromParcel(Parcel in) {
            return new SavedState(in);
        }

        @Override
        public SavedState[] newArray(int size) {
            return new SavedState[size];
        }
    };
}

public interface OnPageChangeListener {
    void onPageSelected(int position);

    void onPageScrollStateChanged(int state);
}

} `

leochuan commented 6 years ago

你直接运行我的demo也会有同样的问题吗?

CordyHo commented 6 years ago

你的Demo没问题,可我也没有其他特别的属性设置,但是你的Demo有设置初始滑动速度之类得吧

leochuan commented 6 years ago

你的recyclerview还有别的设置吗?一起贴上来

CordyHo commented 6 years ago

rv_wine = findViewById(R.id.rv_wine);

ScaleLayoutManager scaleLayoutManager = new ScaleLayoutManager(this, (int) (getResources().getDisplayMetrics().density * 10 + 0.5f));

scaleLayoutManager.setMinScale(0.7f); scaleLayoutManager.setMoveSpeed(4f); //设置速度后可以正常点击item,但滑动很快,体验不佳 rv_wine.setLayoutManager(scaleLayoutManager); new CenterSnapHelper().attachToRecyclerView(rv_wine);

这是可以正常点击的代码,就是加了scaleLayoutManager.setMoveSpeed();Adapter里没代码 就是重写onBindViewHolder几个方法

leochuan commented 6 years ago

没有其他设置了吗 上面的这些代码 对我并没有帮助 我一开始怀疑是你手机的问题 但既然你运行我的demo是正常的 也就是说VPLM 里面的代码是正常运行的 所以我怀疑你对RecyclerView的一些和我这个库无关的设置 比如滚动的监听之类的 影响到了他的滚动 所以你看下如果没别的设置 方便的话 还是抽一个demo发我 我运行下看看吧

CordyHo commented 6 years ago

知道原因了,但是不太清楚为什么会这样,就是因为我要做无限循环的效果,加多了一行:rv_wine.scrollToPosition(list.size() * 10000),让RV一开始就滑到很后,这样就会导致item点击经常无效,但是初始位置不太大就没问题,为什么?你有什么头绪吗?

leochuan commented 6 years ago

无限循环 不是有现成的api吗 setInfinite()

CordyHo commented 6 years ago

setInfinite()不行,不符合我这边的业务逻辑,滚回到第一项的时候有滚动动画

leochuan commented 6 years ago

float 精度问题 数字一大,误差比较大, 导致计算回滚的时候不能滚动到目标位置。我还要兼容CircleLayoutManager所以暂时想不到什么好的解决方法,你目前的情况,你可以把ViewPagerLayoutManager这个类里面的mOffsetmInterval换成int类型,然后相关计算的地方也也都用int,我试过了,这样是没问题的。

CordyHo commented 6 years ago

顺便问下就是怎样得到当前居中的item的位置?有什么回调吗?

leochuan commented 6 years ago

getCurrentPosition()

CordyHo commented 6 years ago

好,谢谢,没什么问题了