简单实现Google play 横向RecyclerListView效果

来源:互联网 发布:云墙mac破解版 编辑:程序博客网 时间:2024/06/07 11:45

现在更好的方式是使用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);
3 0