4

我正在尝试RecyclerView在包含大量数据的应用程序中使用它,并希望对其进行快速滚动,就像ListView. 这个答案的方法对我有用ListView,但对RecyclerView. 即使我trueRecyclerView布局中设置了快速滚动,它仍然不起作用:

    <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:scrollbars="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fastScrollEnabled="true"
            android:fastScrollAlwaysVisible="true" />

是否RecyclerView支持 Android L 中的快速滚动?在文档中找不到任何关于此的内容。

4

5 回答 5

7

你会在 RecyclerView 中找到唯一的东西是回收逻辑的基本实现。它与 ListView 完全相反,因为它为您提供最大的可定制性(与 ListView 不同,您可以实现任何您想要的独特布局),但它几乎没有内置任何东西(不像 ListView 具有许多功能,如快速滚动拇指) .

如果你想添加类似快速滚动功能的东西,你现在需要自己开发它。

RecyclerView 的新fastScrollEnabled布尔标志。如果启用,则必须设置 fastScrollHorizo​​ntalThumbDrawable、fastScrollHorizo​​ntalTrackDrawable、fastScrollVerticalThumbDrawable 和 fastScrollVerticalTrackDrawable。现在可在支持库 26.0.0上使用

于 2014-10-19T13:35:13.973 回答
3

我使用https://github.com/woozzu/IndexableListView/tree/master/src/com/woozzu/android/widget为自己制作了一个

并将 ListView 更改为 RecylerView

public class IndexableRecylerView extends RecyclerView implements RecyclerView.OnItemTouchListener{

    public IndexScroller mScroller = null;

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

    public IndexableRecylerView(Context context, AttributeSet attrs) {
        super(context, attrs);
 init();
    }

    public IndexableRecylerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
 init();
    }

public void init() {
    addOnItemTouchListener(this);
}


    public void setFastScrollEnabled(boolean enable) {
        if (enable) {
            if (mScroller == null)
                mScroller = new IndexScroller(getContext(), this);
        } else {
            if (mScroller != null) {
                mScroller.hide();
                mScroller = null;
            }
        }
    }

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);

        // Overlay index bar
        if (mScroller != null)
            mScroller.draw(canvas);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {

        if (mScroller != null)
          mScroller.show();

        // Intercept ListView's touch event
        if (mScroller != null && mScroller.onTouchEvent(ev))
            return true;


        return super.onTouchEvent(ev);
    }

    public void setIndexAdapter(List<String> sectionName, List<Integer> sectionPosition) {
        if (mScroller != null)
            mScroller.notifyChanges(sectionName, sectionPosition);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (mScroller != null)
            mScroller.onSizeChanged(w, h, oldw, oldh);
    }

    @Override
    public void stopScroll()
    {
        try
        {
            super.stopScroll();
        }
        catch( NullPointerException exception )
        {
            Log.i("RecyclerView", "NPE caught in stopScroll");
        }
    }

@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {

    if (mScroller != null && mScroller.contains(e.getX(), e.getY())) {
        mScroller.show();
        return true;
    }else{
        return false;
    }

}

@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
}

}

此类在侧面绘制索引器,并允许您在它们上滚动并滚动 recylerview ..

public class IndexScroller {

    private float mIndexbarWidth;
    private float mIndexbarMargin;
    private float mPreviewPadding;
    private float mDensity;
    private float mScaledDensity;
    private float mAlphaRate;
    private int mState = STATE_HIDDEN;
    private int mListViewWidth;
    private int mListViewHeight;
    private int mCurrentSection = -1;
    private boolean mIsIndexing = false;
    private RecyclerView recyclerView = null;
    public List<String> mSections = new ArrayList<>();
    public List<Integer> mSectionPosition = new ArrayList<>();
    private RectF mIndexbarRect;

    private static final int STATE_HIDDEN = 0;
    private static final int STATE_SHOWING = 1;
    private static final int STATE_SHOWN = 2;
    private static final int STATE_HIDING = 3;

    public IndexScroller(Context context, RecyclerView lv) {
        mDensity = context.getResources().getDisplayMetrics().density;
        mScaledDensity = context.getResources().getDisplayMetrics().scaledDensity;
        recyclerView = lv;

        mIndexbarWidth = 20 * mDensity;
        mIndexbarMargin = 10 * mDensity;
        mPreviewPadding = 5 * mDensity;
    }

    public void draw(Canvas canvas) {
        if (mState == STATE_HIDDEN)
            return;

        // mAlphaRate determines the rate of opacity
        Paint indexbarPaint = new Paint();
        indexbarPaint.setColor(Color.BLACK);
        indexbarPaint.setAlpha((int) (64 * mAlphaRate));
        indexbarPaint.setAntiAlias(true);
        canvas.drawRoundRect(mIndexbarRect, 5 * mDensity, 5 * mDensity, indexbarPaint);

        if (mSections != null && mSections.size() > 0) {
            // Preview is shown when mCurrentSection is set
            if (mCurrentSection >= 0) {
                Paint previewPaint = new Paint();
                previewPaint.setColor(Color.BLACK);
                previewPaint.setAlpha(96);
                previewPaint.setAntiAlias(true);
                previewPaint.setShadowLayer(3, 0, 0, Color.argb(64, 0, 0, 0));

                Paint previewTextPaint = new Paint();
                previewTextPaint.setColor(Color.WHITE);
                previewTextPaint.setAntiAlias(true);
                previewTextPaint.setTextSize(50 * mScaledDensity);

                float previewTextWidth = previewTextPaint.measureText(mSections.get(mCurrentSection));
                float previewSize = 2 * mPreviewPadding + previewTextPaint.descent() - previewTextPaint.ascent();
                RectF previewRect = new RectF((mListViewWidth - previewSize) / 2
                        , (mListViewHeight - previewSize) / 2
                        , (mListViewWidth - previewSize) / 2 + previewSize
                        , (mListViewHeight - previewSize) / 2 + previewSize);

                canvas.drawRoundRect(previewRect, 5 * mDensity, 5 * mDensity, previewPaint);
                canvas.drawText(mSections.get(mCurrentSection), previewRect.left + (previewSize - previewTextWidth) / 2 - 1
                        , previewRect.top + mPreviewPadding - previewTextPaint.ascent() + 1, previewTextPaint);
            }

            Paint indexPaint = new Paint();
            indexPaint.setColor(Color.WHITE);
            indexPaint.setAlpha((int) (255 * mAlphaRate));
            indexPaint.setAntiAlias(true);
            indexPaint.setTextSize(12 * mScaledDensity);

            float sectionHeight = (mIndexbarRect.height() - 2 * mIndexbarMargin) / mSections.size();
            float paddingTop = (sectionHeight - (indexPaint.descent() - indexPaint.ascent())) / 2;
            for (int i = 0; i < mSections.size(); i++) {
                float paddingLeft = (mIndexbarWidth - indexPaint.measureText(mSections.get(i))) / 2;
                canvas.drawText(mSections.get(i), mIndexbarRect.left + paddingLeft
                        , mIndexbarRect.top + mIndexbarMargin + sectionHeight * i + paddingTop - indexPaint.ascent(), indexPaint);
            }
        }
    }


    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:

                // If down event occurs inside index bar region, start indexing
                if (mState != STATE_HIDDEN && contains(ev.getX(), ev.getY())) {
                    setState(STATE_SHOWN);

                    // It demonstrates that the motion event started from index bar
                    mIsIndexing = true;
                    // Determine which section the point is in, and move the list to that section
                    mCurrentSection = getSectionByPoint(ev.getY());
                    recyclerView.scrollToPosition(mSectionPosition.get(mCurrentSection));
                    return true;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (mIsIndexing) {
                    // If this event moves inside index bar
                    if (contains(ev.getX(), ev.getY())) {
                        // Determine which section the point is in, and move the list to that section
                        mCurrentSection = getSectionByPoint(ev.getY());
                        recyclerView.scrollToPosition(mSectionPosition.get(mCurrentSection));
                    }
                    return true;
                }
                break;
            case MotionEvent.ACTION_UP:
                if (mIsIndexing) {
                    mIsIndexing = false;
                    mCurrentSection = -1;
                }
                if (mState == STATE_SHOWN) {
                    setState(STATE_HIDING);
                }
                break;
        }
        return false;
    }

    public void onSizeChanged(int w, int h, int oldw, int oldh) {
        mListViewWidth = w;
        mListViewHeight = h;
        mIndexbarRect = new RectF(w - mIndexbarMargin - mIndexbarWidth
                , mIndexbarMargin
                , w - mIndexbarMargin
                , h - mIndexbarMargin);
    }

    public void show() {
        if (mState == STATE_HIDDEN)
            setState(STATE_SHOWING);
        else if (mState == STATE_HIDING)
            setState(STATE_HIDING);
    }

    public void hide() {
        if (mState == STATE_SHOWN)
            setState(STATE_HIDING);
    }

    private void setState(int state) {
        if (state < STATE_HIDDEN || state > STATE_HIDING)
            return;

        mState = state;
        switch (mState) {
            case STATE_HIDDEN:
                // Cancel any fade effect
                mHandler.removeMessages(0);
                break;
            case STATE_SHOWING:
                // Start to fade in
                mAlphaRate = 0;
                fade(0);
                break;
            case STATE_SHOWN:
                // Cancel any fade effect
                mHandler.removeMessages(0);
                break;
            case STATE_HIDING:
                // Start to fade out after three seconds
                mAlphaRate = 1;
                fade(3000);
                break;
        }
    }

    public boolean contains(float x, float y) {
        // Determine if the point is in index bar region, which includes the right margin of the bar
        return (x >= mIndexbarRect.left && y >= mIndexbarRect.top && y <= mIndexbarRect.top + mIndexbarRect.height());
    }

    private int getSectionByPoint(float y) {
        if (mSections == null || mSections.size() == 0)
            return 0;
        if (y < mIndexbarRect.top + mIndexbarMargin)
            return 0;
        if (y >= mIndexbarRect.top + mIndexbarRect.height() - mIndexbarMargin)
            return mSections.size() - 1;
        return (int) ((y - mIndexbarRect.top - mIndexbarMargin) / ((mIndexbarRect.height() - 2 * mIndexbarMargin) / mSections.size()));
    }

    private void fade(long delay) {
        mHandler.removeMessages(0);
        mHandler.sendEmptyMessageAtTime(0, SystemClock.uptimeMillis() + delay);
    }

    private Handler mHandler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

            switch (mState) {
                case STATE_SHOWING:
                    // Fade in effect
                    mAlphaRate += (1 - mAlphaRate) * 0.2;
                    if (mAlphaRate > 0.9) {
                        mAlphaRate = 1;
                        setState(STATE_SHOWN);
                    }

                    recyclerView.invalidate();
                    fade(10);
                    break;
                case STATE_SHOWN:
                    // If no action, hide automatically
                    setState(STATE_HIDING);
                    break;
                case STATE_HIDING:
                    // Fade out effect
                    mAlphaRate -= mAlphaRate * 0.2;
                    if (mAlphaRate < 0.1) {
                        mAlphaRate = 0;
                        setState(STATE_HIDDEN);
                    }

                    recyclerView.invalidate();
                    fade(10);
                    break;
            }
        }

    };


    public void notifyChanges(List<String> sectionName, List<Integer> sectionPosition) {

// Pre-calculate and pass your section header and position

        mSections = sectionNames;
        mSectionPosition = sectionPosition;

    }}

这只是我修改以测试它的快速修复。似乎对我有用,有 3100 个项目的列表。当您将 Items 设置为适配器时,您需要计算节标题和位置。在我的情况下,我遍历我的预排序列表并为每个字符获取第一项的位置并放入一个列表并传递给公共 void notifyChanges(List sectionName, List sectionPosition)。希望能帮助到你。

于 2015-01-22T07:50:01.547 回答
2

我发现的另一个好的解决方案在Mark Allison 的一篇关于如何为RecyclerView. 值得一试。

于 2015-03-23T11:32:08.283 回答
1

这里有一个("RecyclerViewFastScroller") ,它可能会有所帮助。

有人从我写的类似帖子中发布了它,here

于 2015-04-01T22:10:01.107 回答
1

我制作了一个来解决这个问题。现在有很多自定义选项。

于 2016-08-08T13:31:25.573 回答