简单实现Google play 横向RecyclerListView效果

现在更好的方式是使用SnapHelper 在RecyclerView 24.2.0 支持库之后添加使用方法

需要实现的功能

这里只实现回弹的效果 和 在一个宽度内显示2个半item的效果。

分析

下面是需要实现的效果:

1.看起来就是一个横向的ListView,现在有我们可以容易的使用RecyclerView并配合LinearLayoutManager 实现一个横向的ListView

2.需要支持回弹效果,RecyclerView 本身拥有的scrollToPosition(int targetPosition)smoothScrollToPosition(int targetPosition),目前看来很简单。

实现

好吧,看起来没什么可分析的。为了方便使用 自定义一个HorizontalRecyclerView继承自 RecyclerView

HorizontalRecyclerView

public class HorizontalRecyclerView extends RecyclerView {
    private LinearLayoutManager mLayoutManager;

    public HorizontalRecyclerView(Context context) {
        super(context);
        init(context);
    }

    public HorizontalRecyclerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public HorizontalRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }

    private void init(Context context){
        mLayoutManager = new LinearLayoutManager(context);//自定义的LinearLayoutManager extends LinearLayoutManager
        mLayoutManager.setOrientation(android.support.v7.widget.LinearLayoutManager.HORIZONTAL);
        setLayoutManager(mLayoutManager);
        addOnScrollListener(new OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                switch (newState){
                    case SCROLL_STATE_IDLE://
                        int firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition();
                        int firstCompletelyVisibleItem = mLayoutManager.findFirstCompletelyVisibleItemPosition();
                        int lastCompletelyVisibleItem = mLayoutManager.findLastCompletelyVisibleItemPosition();
                        if(lastCompletelyVisibleItem == getAdapter().getItemCount()-1) return;
                        if(firstCompletelyVisibleItem == firstVisibleItem) return;
                        View firstItem = mLayoutManager.findViewByPosition(firstVisibleItem);
                        if(Math.abs(firstItem.getLeft())*2>firstItem.getWidth()) {
                            smoothScrollToPosition(firstCompletelyVisibleItem);
                        }else {
                            smoothScrollToPosition(firstVisibleItem);
                        }
                        break;
                }
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
            }
        });
    }
}

就是做一个初始化工作,设置一个横向的LinearLayoutManager,并且添加滑动监听。在监听里判断需要滑到哪个位置,执行滑动。

运行之后发现,并没有进行滑动。下面是我解决的方案:

1.重写LayoutManagersmoothScrollToPosition方法使用自定义的MyLinearSmoothScroller 代替LinearLayoutManager默认的scroller。

 @Override
    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
        MyLinearSmoothScroller linearSmoothScroller =
                new MyLinearSmoothScroller(recyclerView.getContext()) {
                    @Override
                    public PointF computeScrollVectorForPosition(int targetPosition) {
                        return LinearLayoutManager.this
                                .computeScrollVectorForPosition(targetPosition);
                    }
                };
        linearSmoothScroller.setTargetPosition(position);
        startSmoothScroll(linearSmoothScroller);
    }

2.MyLinearSmoothScroller 继承自LinearSmoothScroller重写下面两个方法,第一个是为了使移动能够发生,第二个是控制滑动速度。

 @Override
    public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int snapPreference) {
        switch (snapPreference) {
            case SNAP_TO_START:
                return boxStart - viewStart;
            case SNAP_TO_END:
                return boxEnd - viewEnd;
            case SNAP_TO_ANY:
                final int dtStart = boxStart - viewStart;
//                if (dtStart > 0) {
                    return dtStart;
//                }
//                final int dtEnd = boxEnd - viewEnd;
//                if (dtEnd < 0) {
//                    return dtEnd;
//                }
//                break;
            default:
                throw new IllegalArgumentException("snap preference should be one of the"
                        + " constants defined in SmoothScroller, starting with SNAP_");
        }
//        return 0;
    }
@Override
    protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
         return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;//返回的是移动一个像素 需要的毫秒数
    }

3.控制一次布局展示可以展现 2.5个Item,重写LinearLayoutManager的测量子view的方法

@Override
    public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
        final RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
//
//        final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
//        widthUsed += insets.left + insets.right;
//        heightUsed += insets.top + insets.bottom;
//
//        final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
//                getPaddingLeft() + getPaddingRight() +
//                        lp.leftMargin + lp.rightMargin + widthUsed, lp.width,
//                canScrollHorizontally());
        final int widthSpec = getChildMeasureSpec((int) (0.4*getWidth()),getWidthMode(),
                0,lp.width,canScrollHorizontally());
        final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
                getPaddingTop() + getPaddingBottom() +
                        lp.topMargin + lp.bottomMargin + heightUsed, lp.height,
                canScrollVertically());
//        if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
            child.measure(widthSpec, heightSpec);
//        }
    }

其他

那么为什么之前调用滑动,没有进行滑动呢。还是看这个方法

 /**
     * Helper method for {@link #calculateDxToMakeVisible(android.view.View, int)} and
     * {@link #calculateDyToMakeVisible(android.view.View, int)}
     */
    public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int
            snapPreference) {
        switch (snapPreference) {
            case SNAP_TO_START:
                return boxStart - viewStart;
            case SNAP_TO_END:
                return boxEnd - viewEnd;
            case SNAP_TO_ANY:
                final int dtStart = boxStart - viewStart;
                if (dtStart > 0) {
                    return dtStart;
                }
                final int dtEnd = boxEnd - viewEnd;
                if (dtEnd < 0) {
                    return dtEnd;
                }
                break;
            default:
                throw new IllegalArgumentException("snap preference should be one of the"
                        + " constants defined in SmoothScroller, starting with SNAP_");
        }
        return 0;
    }

我们触发滑动时会穿过去的snapPreference == SNAP_TO_ANY 然后不满足下面两个if条件 最后返回 0。然后snapPreference 是个什么?如果能保证snapPreference==SNAP_TO_START 就不用重写这个方法了。看下面两个方法注释

 /**
     * When the target scroll position is not a child of the RecyclerView, this method calculates
     * a direction vector towards that child and triggers a smooth scroll.
     *
     * @see #computeScrollVectorForPosition(int)
     */
    protected void updateActionForInterimTarget(Action action) {
        // find an interim target position
        PointF scrollVector = computeScrollVectorForPosition(getTargetPosition());
        if (scrollVector == null || (scrollVector.x == 0 && scrollVector.y == 0)) {
            Log.e(TAG, "To support smooth scrolling, you should override \n"
                    + "LayoutManager#computeScrollVectorForPosition.\n"
                    + "Falling back to instant scroll");
            final int target = getTargetPosition();
            action.jumpTo(target);
            stop();
            return;
        }
        normalize(scrollVector);
        mTargetVector = scrollVector;

        mInterimTargetDx = (int) (TARGET_SEEK_SCROLL_DISTANCE_PX * scrollVector.x);
        mInterimTargetDy = (int) (TARGET_SEEK_SCROLL_DISTANCE_PX * scrollVector.y);
        final int time = calculateTimeForScrolling(TARGET_SEEK_SCROLL_DISTANCE_PX);
        // To avoid UI hiccups, trigger a smooth scroll to a distance little further than the
        // interim target. Since we track the distance travelled in onSeekTargetStep callback, it
        // won't actually scroll more than what we need.
        action.update((int) (mInterimTargetDx * TARGET_SEEK_EXTRA_SCROLL_RATIO)
                , (int) (mInterimTargetDy * TARGET_SEEK_EXTRA_SCROLL_RATIO)
                , (int) (time * TARGET_SEEK_EXTRA_SCROLL_RATIO), mLinearInterpolator);
    }
 /**
         * 

RecyclerView will call this method each time it scrolls until it can find the target * position in the layout.

*

SmoothScroller should check dx, dy and if scroll should be changed, update the * provided {@link Action} to define the next scroll.

* * @param dx Last scroll amount horizontally * @param dy Last scroll amount verticaully * @param state Transient state of RecyclerView * @param action If you want to trigger a new smooth scroll and cancel the previous one, * update this object. */ abstract protected void onSeekTargetStep(int dx, int dy, State state, Action action);

你可能感兴趣的:(android开发)