当前位置: 首页 > 知识库问答 >
问题:

如何捕捉RecyclerView项目,以便将每X个项目视为一个单元来捕捉?

卜鹏
2023-03-14

可以使用以下命令将 RecyclerView 捕捉到其中心:

LinearSnapHelper().attachToRecyclerView(recyclerView)

示例:

主活动. kt

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val inflater = LayoutInflater.from(this)

        recyclerView.adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
            override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
                val textView = holder.itemView as TextView
                textView.setBackgroundColor(if (position % 2 == 0) 0xffff0000.toInt() else 0xff00ff00.toInt())
                textView.text = position.toString()
            }

            override fun getItemCount(): Int {
                return 100
            }

            override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {
                val view = inflater.inflate(android.R.layout.simple_list_item_1, parent, false) as TextView
                val cellSize = recyclerView.width / 3
                view.layoutParams.height = cellSize
                view.layoutParams.width = cellSize
                view.gravity = Gravity.CENTER
                return object : RecyclerView.ViewHolder(view) {}
            }
        }
        LinearSnapHelper().attachToRecyclerView(recyclerView)
    }
}

activity_main.xml

<android.support.v7.widget.RecyclerView
    android:id="@+id/recyclerView" xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal"
    app:layoutManager="android.support.v7.widget.LinearLayoutManager"/>

也可以将其捕捉到另一侧,就像在一些库中所做的那样,比如这里。

也有一些库允许RecyclerView像ViewPager一样工作,比如这里。

假设我有一个包含许多项目的RecyclView(在我的例子中是水平的),我希望它将每个X项目(X是常数)视为一个单元,并与每个单元对齐。

例如,如果我滚动一点,它可以捕捉到0项或X项,但不能捕捉到它们之间的东西。

在某种程度上,它的行为类似于普通的ViewPager,只是每个页面都有X个项目。

例如,如果我们继续我上面写的示例代码,假设X==3,快照将来自这种空闲状态:

到这个空闲状态(如果我们滚动得足够多,否则会停留在以前的状态) :

应该像在ViewPager上一样处理更多的投掷或滚动,就像我上面提到的库一样。

滚动更多(在同一方向)到下一个对齐点将到达项目“6”,“9”,依此类推...

我试图搜索替代库,我也试图阅读有关此的文档,但我没有找到任何可能有用的东西。

使用ViewPager也可能是可能的,但我认为这不是最好的方法,因为ViewPager不能很好地回收其项目,而且我认为它在如何捕捉方面不如RecyclizerView灵活。

>

  • 是否可以将 RecyclerView 设置为贴靠每个 X 个项目,将每个 X 个项目视为要对齐到的单个页面?

    当然,这些物品将均匀地占据整个RecyclerView的足够空间。

    假设这是可能的,当回收人员视图即将捕捉到某个项目时,包括在它被捕捉之前拥有这个项目时,我将如何获得回调?我问这个问题是因为它与我在这里问的同一个问题有关。

    基于“Cheticamp”答案的工作静态编程语言解决方案(此处),无需验证您是否具有回收站视图大小,并且可以选择具有网格而不是列表,在示例中:

    主活动. kt

    class MainActivity : AppCompatActivity() {
        val USE_GRID = false
        //        val USE_GRID = true
        val ITEMS_PER_PAGE = 4
        var selectedItemPos = 0
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            val inflater = LayoutInflater.from(this)
    
            recyclerView.adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
                override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
                    val textView = holder.itemView as TextView
                    textView.setBackgroundColor(if (position % 2 == 0) 0xffff0000.toInt() else 0xff00ff00.toInt())
                    textView.text = if (selectedItemPos == position) "selected: $position" else position.toString()
                }
    
                override fun getItemCount(): Int {
                    return 100
                }
    
                override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {
                    val view = inflater.inflate(android.R.layout.simple_list_item_1, parent, false) as TextView
                    view.layoutParams.width = if (USE_GRID)
                        recyclerView.width / (ITEMS_PER_PAGE / 2)
                    else
                        recyclerView.width / 4
                    view.layoutParams.height = recyclerView.height / (ITEMS_PER_PAGE / 2)
                    view.gravity = Gravity.CENTER
                    return object : RecyclerView.ViewHolder(view) {
                    }
                }
            }
            recyclerView.layoutManager = if (USE_GRID)
                GridLayoutManager(this, ITEMS_PER_PAGE / 2, GridLayoutManager.HORIZONTAL, false)
            else
                LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
            val snapToBlock = SnapToBlock(recyclerView, ITEMS_PER_PAGE)
            snapToBlock.attachToRecyclerView(recyclerView)
            snapToBlock.setSnapBlockCallback(object : SnapToBlock.SnapBlockCallback {
                override fun onBlockSnap(snapPosition: Int) {
                    if (selectedItemPos == snapPosition)
                        return
                    selectedItemPos = snapPosition
                    recyclerView.adapter.notifyDataSetChanged()
                }
    
                override fun onBlockSnapped(snapPosition: Int) {
                    if (selectedItemPos == snapPosition)
                        return
                    selectedItemPos = snapPosition
                    recyclerView.adapter.notifyDataSetChanged()
                }
    
            })
        }
    
    }
    

    SnapToBlock.kt

    /**@param maxFlingBlocks Maxim blocks to move during most vigorous fling*/
    class SnapToBlock constructor(private val maxFlingBlocks: Int) : SnapHelper() {
        private var recyclerView: RecyclerView? = null
        // Total number of items in a block of view in the RecyclerView
        private var blocksize: Int = 0
        // Maximum number of positions to move on a fling.
        private var maxPositionsToMove: Int = 0
        // Width of a RecyclerView item if orientation is horizonal; height of the item if vertical
        private var itemDimension: Int = 0
        // Callback interface when blocks are snapped.
        private var snapBlockCallback: SnapBlockCallback? = null
        // When snapping, used to determine direction of snap.
        private var priorFirstPosition = RecyclerView.NO_POSITION
        // Our private scroller
        private var scroller: Scroller? = null
        // Horizontal/vertical layout helper
        private var orientationHelper: OrientationHelper? = null
        // LTR/RTL helper
        private var layoutDirectionHelper: LayoutDirectionHelper? = null
    
        @Throws(IllegalStateException::class)
        override fun attachToRecyclerView(recyclerView: RecyclerView?) {
            if (recyclerView != null) {
                this.recyclerView = recyclerView
                val layoutManager = recyclerView.layoutManager as LinearLayoutManager
                orientationHelper = when {
                    layoutManager.canScrollHorizontally() -> OrientationHelper.createHorizontalHelper(layoutManager)
                    layoutManager.canScrollVertically() -> OrientationHelper.createVerticalHelper(layoutManager)
                    else -> throw IllegalStateException("RecyclerView must be scrollable")
                }
                scroller = Scroller(this.recyclerView!!.context, sInterpolator)
                initItemDimensionIfNeeded(layoutManager)
            }
            super.attachToRecyclerView(recyclerView)
        }
    
        // Called when the target view is available and we need to know how much more
        // to scroll to get it lined up with the side of the RecyclerView.
        override fun calculateDistanceToFinalSnap(layoutManager: RecyclerView.LayoutManager, targetView: View): IntArray {
            val out = IntArray(2)
            initLayoutDirectionHelperIfNeeded(layoutManager)
            if (layoutManager.canScrollHorizontally())
                out[0] = layoutDirectionHelper!!.getScrollToAlignView(targetView)
            if (layoutManager.canScrollVertically())
                out[1] = layoutDirectionHelper!!.getScrollToAlignView(targetView)
            if (snapBlockCallback != null)
                if (out[0] == 0 && out[1] == 0)
                    snapBlockCallback!!.onBlockSnapped(layoutManager.getPosition(targetView))
                else
                    snapBlockCallback!!.onBlockSnap(layoutManager.getPosition(targetView))
            return out
        }
    
        private fun initLayoutDirectionHelperIfNeeded(layoutManager: RecyclerView.LayoutManager) {
            if (layoutDirectionHelper == null)
                if (layoutManager.canScrollHorizontally())
                    layoutDirectionHelper = LayoutDirectionHelper()
                else if (layoutManager.canScrollVertically())
                // RTL doesn't matter for vertical scrolling for this class.
                    layoutDirectionHelper = LayoutDirectionHelper(false)
        }
    
        // We are flinging and need to know where we are heading.
        override fun findTargetSnapPosition(layoutManager: RecyclerView.LayoutManager, velocityX: Int, velocityY: Int): Int {
            initLayoutDirectionHelperIfNeeded(layoutManager)
            val lm = layoutManager as LinearLayoutManager
            initItemDimensionIfNeeded(layoutManager)
            scroller!!.fling(0, 0, velocityX, velocityY, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE)
            return when {
                velocityX != 0 -> layoutDirectionHelper!!.getPositionsToMove(lm, scroller!!.finalX, itemDimension)
                else -> if (velocityY != 0)
                    layoutDirectionHelper!!.getPositionsToMove(lm, scroller!!.finalY, itemDimension)
                else RecyclerView.NO_POSITION
            }
        }
    
        // We have scrolled to the neighborhood where we will snap. Determine the snap position.
        override fun findSnapView(layoutManager: RecyclerView.LayoutManager): View? {
            // Snap to a view that is either 1) toward the bottom of the data and therefore on screen,
            // or, 2) toward the top of the data and may be off-screen.
            val snapPos = calcTargetPosition(layoutManager as LinearLayoutManager)
            val snapView = if (snapPos == RecyclerView.NO_POSITION)
                null
            else
                layoutManager.findViewByPosition(snapPos)
            if (snapView == null)
                Log.d(TAG, "<<<<findSnapView is returning null!")
            Log.d(TAG, "<<<<findSnapView snapos=" + snapPos)
            return snapView
        }
    
        // Does the heavy lifting for findSnapView.
        private fun calcTargetPosition(layoutManager: LinearLayoutManager): Int {
            val snapPos: Int
            initLayoutDirectionHelperIfNeeded(layoutManager)
            val firstVisiblePos = layoutManager.findFirstVisibleItemPosition()
            if (firstVisiblePos == RecyclerView.NO_POSITION)
                return RecyclerView.NO_POSITION
            initItemDimensionIfNeeded(layoutManager)
            if (firstVisiblePos >= priorFirstPosition) {
                // Scrolling toward bottom of data
                val firstCompletePosition = layoutManager.findFirstCompletelyVisibleItemPosition()
                snapPos = if (firstCompletePosition != RecyclerView.NO_POSITION && firstCompletePosition % blocksize == 0)
                    firstCompletePosition
                else
                    roundDownToBlockSize(firstVisiblePos + blocksize)
            } else {
                // Scrolling toward top of data
                snapPos = roundDownToBlockSize(firstVisiblePos)
                // Check to see if target view exists. If it doesn't, force a smooth scroll.
                // SnapHelper only snaps to existing views and will not scroll to a non-existant one.
                // If limiting fling to single block, then the following is not needed since the
                // views are likely to be in the RecyclerView pool.
                if (layoutManager.findViewByPosition(snapPos) == null) {
                    val toScroll = layoutDirectionHelper!!.calculateDistanceToScroll(layoutManager, snapPos)
                    recyclerView!!.smoothScrollBy(toScroll[0], toScroll[1], sInterpolator)
                }
            }
            priorFirstPosition = firstVisiblePos
            return snapPos
        }
    
        private fun initItemDimensionIfNeeded(layoutManager: RecyclerView.LayoutManager) {
            if (itemDimension != 0)
                return
            val child = layoutManager.getChildAt(0) ?: return
            if (layoutManager.canScrollHorizontally()) {
                itemDimension = child.width
                blocksize = getSpanCount(layoutManager) * (recyclerView!!.width / itemDimension)
            } else if (layoutManager.canScrollVertically()) {
                itemDimension = child.height
                blocksize = getSpanCount(layoutManager) * (recyclerView!!.height / itemDimension)
            }
            maxPositionsToMove = blocksize * maxFlingBlocks
        }
    
        private fun getSpanCount(layoutManager: RecyclerView.LayoutManager): Int = (layoutManager as? GridLayoutManager)?.spanCount ?: 1
    
        private fun roundDownToBlockSize(trialPosition: Int): Int = trialPosition - trialPosition % blocksize
    
        private fun roundUpToBlockSize(trialPosition: Int): Int = roundDownToBlockSize(trialPosition + blocksize - 1)
    
        override fun createScroller(layoutManager: RecyclerView.LayoutManager): LinearSmoothScroller? {
            return if (layoutManager !is RecyclerView.SmoothScroller.ScrollVectorProvider)
                null
            else object : LinearSmoothScroller(recyclerView!!.context) {
                override fun onTargetFound(targetView: View, state: RecyclerView.State?, action: RecyclerView.SmoothScroller.Action) {
                    val snapDistances = calculateDistanceToFinalSnap(recyclerView!!.layoutManager, targetView)
                    val dx = snapDistances[0]
                    val dy = snapDistances[1]
                    val time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy)))
                    if (time > 0)
                        action.update(dx, dy, time, sInterpolator)
                }
    
                override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics): Float = MILLISECONDS_PER_INCH / displayMetrics.densityDpi
            }
        }
    
        fun setSnapBlockCallback(callback: SnapBlockCallback?) {
            snapBlockCallback = callback
        }
    
        /*
            Helper class that handles calculations for LTR and RTL layouts.
         */
        private inner class LayoutDirectionHelper {
            // Is the layout an RTL one?
            private val mIsRTL: Boolean
    
            constructor() {
                mIsRTL = ViewCompat.getLayoutDirection(recyclerView) == ViewCompat.LAYOUT_DIRECTION_RTL
            }
    
            constructor(isRTL: Boolean) {
                mIsRTL = isRTL
            }
    
            /*
                Calculate the amount of scroll needed to align the target view with the layout edge.
             */
            fun getScrollToAlignView(targetView: View): Int = if (mIsRTL)
                orientationHelper!!.getDecoratedEnd(targetView) - recyclerView!!.width
            else
                orientationHelper!!.getDecoratedStart(targetView)
    
            /**
             * Calculate the distance to final snap position when the view corresponding to the snap
             * position is not currently available.
             *
             * @param layoutManager LinearLayoutManager or descendent class
             * @param targetPos     - Adapter position to snap to
             * @return int[2] {x-distance in pixels, y-distance in pixels}
             */
            fun calculateDistanceToScroll(layoutManager: LinearLayoutManager, targetPos: Int): IntArray {
                val out = IntArray(2)
                val firstVisiblePos = layoutManager.findFirstVisibleItemPosition()
                if (layoutManager.canScrollHorizontally()) {
                    if (targetPos <= firstVisiblePos)  // scrolling toward top of data
                        if (mIsRTL) {
                            val lastView = layoutManager.findViewByPosition(layoutManager.findLastVisibleItemPosition())
                            out[0] = orientationHelper!!.getDecoratedEnd(lastView) + (firstVisiblePos - targetPos) * itemDimension
                        } else {
                            val firstView = layoutManager.findViewByPosition(firstVisiblePos)
                            out[0] = orientationHelper!!.getDecoratedStart(firstView) - (firstVisiblePos - targetPos) * itemDimension
                        }
                }
                if (layoutManager.canScrollVertically() && targetPos <= firstVisiblePos) { // scrolling toward top of data
                    val firstView = layoutManager.findViewByPosition(firstVisiblePos)
                    out[1] = firstView.top - (firstVisiblePos - targetPos) * itemDimension
                }
                return out
            }
    
            /*
                Calculate the number of positions to move in the RecyclerView given a scroll amount
                and the size of the items to be scrolled. Return integral multiple of mBlockSize not
                equal to zero.
             */
            fun getPositionsToMove(llm: LinearLayoutManager, scroll: Int, itemSize: Int): Int {
                var positionsToMove: Int
                positionsToMove = roundUpToBlockSize(Math.abs(scroll) / itemSize)
                if (positionsToMove < blocksize)
                // Must move at least one block
                    positionsToMove = blocksize
                else if (positionsToMove > maxPositionsToMove)
                // Clamp number of positions to move so we don't get wild flinging.
                    positionsToMove = maxPositionsToMove
                if (scroll < 0)
                    positionsToMove *= -1
                if (mIsRTL)
                    positionsToMove *= -1
                return if (layoutDirectionHelper!!.isDirectionToBottom(scroll < 0)) {
                    // Scrolling toward the bottom of data.
                    roundDownToBlockSize(llm.findFirstVisibleItemPosition()) + positionsToMove
                } else roundDownToBlockSize(llm.findLastVisibleItemPosition()) + positionsToMove
                // Scrolling toward the top of the data.
            }
    
            fun isDirectionToBottom(velocityNegative: Boolean): Boolean = if (mIsRTL) velocityNegative else !velocityNegative
        }
    
        interface SnapBlockCallback {
            fun onBlockSnap(snapPosition: Int)
            fun onBlockSnapped(snapPosition: Int)
        }
    
        companion object {
            // Borrowed from ViewPager.java
            private val sInterpolator = Interpolator { input ->
                var t = input
                // _o(t) = t * t * ((tension + 1) * t + tension)
                // o(t) = _o(t - 1) + 1
                t -= 1.0f
                t * t * t + 1.0f
            }
    
            private val MILLISECONDS_PER_INCH = 100f
            private val TAG = "SnapToBlock"
        }
    }
    

    尽管我已经将答案标记为已接受,因为它运行良好,但我注意到它存在严重问题:

    >

  • 平滑滚动似乎不能正常工作(不能滚动到正确的位置)。只有滚动才能正常工作(但有“涂抹”效果):

    (recyclerView.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(targetPos,0)
    

    当切换到希伯来语(“עברית”)等RTL(从右到左)语言环境时,它根本不允许我滚动。

    我注意到,onCreateViewHolder被称为很多。事实上,每次我滚动时它都会被调用,甚至有时候它本应该循环使用viewholder。这意味着视图创建过多,也可能意味着内存泄漏。

    我自己也试过修复,但到目前为止失败了。

    如果这里有人知道如何修复它,我会给予额外的新赏金

    更新:当我们获得RTL / LTR的修复程序时,我在这篇文章中更新了Kotlin解决方案。

    更新:关于第3点,这似乎是因为回收器视图有一个视图池,它很快就被填满了。为了处理这个问题,我们可以简单地扩大池的大小,方法是为我们在其中的每个视图类型使用recyclerView.getRecycledViewPool(). setMaxRecycledViews(viewType,Integer.MAX_VALUE)。这确实是很需要的奇怪的事情。我已经将其发布到Google(这里和这里),但被拒绝默认情况下池应该是无限的。最后,我决定至少要求有一个更方便的函数来为所有视图类型执行此操作(这里)。

  • 共有3个答案

    呼延承平
    2023-03-14

    这个图书馆对https://github.com/TakuSemba/MultiSnapRecyclerView很有用

        //Adding multisnap to the recyclerview
        val multiSnapHelper = MultiSnapHelper(MultiSnapHelper.DEFAULT_GRAVITY, 1, 200F)
        multiSnapHelper.attachToRecyclerView(recyclerView)
    

    上述代码是为您的活动通过代码

        <com.takusemba.multisnaprecyclerview.MultiSnapRecyclerView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:msrv_gravity="start" or center, end
        app:msrv_interval="2" items to scroll over
        app:msrv_ms_per_inch="100" /> // speed of scrolling through.
    

    这是同样的方式,但在xml中这是您的选择

    所有这些信息都来自文档

    宣星光
    2023-03-14

    我会做类似的事情

    >

  • RecyclerView内部的块滚动(例如,如何禁用RecyclerView滚动?

    创建手势甩动Detecor并将其附加到回收器查看

    应该有效:)

  • 曾嘉言
    2023-03-14

    SnapHelper 为您正在尝试的内容提供了必要的框架,但需要对其进行扩展以处理视图块。下面的类 SnapToBlock 扩展了 SnapHelper 以捕捉到视图块。在此示例中,我对一个块使用了四个视图,但它可以或多或少。

    更新:代码已更改以适应GridLayoutManager以及LinearLayoutManager。现在禁止翻转,因此抓拍工作更多列出ViewPager。现在支持水平和垂直滚动以及LTR和RTL布局。

    更新:将平滑滚动插值器更改为更像<code>ViewPager</code>。

    更新:添加用于前/后捕捉的回调。

    更新:添加对RTL布局的支持。

    这是示例应用程序的快速视频:

    设置布局管理器,如下所示:

    // For LinearLayoutManager horizontal orientation
    recyclerView.setLayoutManager(new LinearLayoutManager(this, RecyclerView.HORIZONTAL, false));
    
    // For GridLayoutManager vertical orientation
    recyclerView.setLayoutManager(new GridLayoutManager(this, SPAN_COUNT, RecyclerView.VERTICAL, false));
    

    添加以下内容以将SnapToBlock附加到RecyclView

    SnapToBlock snapToBlock = new SnapToBlock(mMaxFlingPages);
    snapToBlock.attachToRecyclerView(recyclerView);
    

    mMaxFlingPages 是允许一次抛出的最大块数(行Cols * spans)。

    对于即将拍摄快照并已完成时的回电,添加以下内容:

    snapToBlock.setSnapBlockCallback(new SnapToBlock.SnapBlockCallback() {
        @Override
        public void onBlockSnap(int snapPosition) {
            ...
        }
    
        @Override
        public void onBlockSnapped(int snapPosition) {
            ...
        }
    });
    

    SnapToBlock.java

    /*  The number of items in the RecyclerView should be a multiple of block size; otherwise, the
        extra item views will not be positioned on a block boundary when the end of the data is reached.
        Pad out with empty item views if needed.
    
        Updated to accommodate RTL layouts.
     */
    
    public class SnapToBlock extends SnapHelper {
        private RecyclerView mRecyclerView;
    
        // Total number of items in a block of view in the RecyclerView
        private int mBlocksize;
    
        // Maximum number of positions to move on a fling.
        private int mMaxPositionsToMove;
    
        // Width of a RecyclerView item if orientation is horizonal; height of the item if vertical
        private int mItemDimension;
    
        // Maxim blocks to move during most vigorous fling.
        private final int mMaxFlingBlocks;
    
        // Callback interface when blocks are snapped.
        private SnapBlockCallback mSnapBlockCallback;
    
        // When snapping, used to determine direction of snap.
        private int mPriorFirstPosition = RecyclerView.NO_POSITION;
    
        // Our private scroller
        private Scroller mScroller;
    
        // Horizontal/vertical layout helper
        private OrientationHelper mOrientationHelper;
    
        // LTR/RTL helper
        private LayoutDirectionHelper mLayoutDirectionHelper;
    
        // Borrowed from ViewPager.java
        private static final Interpolator sInterpolator = new Interpolator() {
            public float getInterpolation(float t) {
                // _o(t) = t * t * ((tension + 1) * t + tension)
                // o(t) = _o(t - 1) + 1
                t -= 1.0f;
                return t * t * t + 1.0f;
            }
        };
    
        SnapToBlock(int maxFlingBlocks) {
            super();
            mMaxFlingBlocks = maxFlingBlocks;
        }
    
        @Override
        public void attachToRecyclerView(@Nullable final RecyclerView recyclerView)
            throws IllegalStateException {
    
            if (recyclerView != null) {
                mRecyclerView = recyclerView;
                final LinearLayoutManager layoutManager =
                    (LinearLayoutManager) recyclerView.getLayoutManager();
                if (layoutManager.canScrollHorizontally()) {
                    mOrientationHelper = OrientationHelper.createHorizontalHelper(layoutManager);
                    mLayoutDirectionHelper =
                        new LayoutDirectionHelper(ViewCompat.getLayoutDirection(mRecyclerView));
                } else if (layoutManager.canScrollVertically()) {
                    mOrientationHelper = OrientationHelper.createVerticalHelper(layoutManager);
                    // RTL doesn't matter for vertical scrolling for this class.
                    mLayoutDirectionHelper = new LayoutDirectionHelper(RecyclerView.LAYOUT_DIRECTION_LTR);
                } else {
                    throw new IllegalStateException("RecyclerView must be scrollable");
                }
                mScroller = new Scroller(mRecyclerView.getContext(), sInterpolator);
                initItemDimensionIfNeeded(layoutManager);
            }
            super.attachToRecyclerView(recyclerView);
        }
    
        // Called when the target view is available and we need to know how much more
        // to scroll to get it lined up with the side of the RecyclerView.
        @NonNull
        @Override
        public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager,
                                                  @NonNull View targetView) {
            int[] out = new int[2];
    
            if (layoutManager.canScrollHorizontally()) {
                out[0] = mLayoutDirectionHelper.getScrollToAlignView(targetView);
            }
            if (layoutManager.canScrollVertically()) {
                out[1] = mLayoutDirectionHelper.getScrollToAlignView(targetView);
            }
            if (mSnapBlockCallback != null) {
                if (out[0] == 0 && out[1] == 0) {
                    mSnapBlockCallback.onBlockSnapped(layoutManager.getPosition(targetView));
                } else {
                    mSnapBlockCallback.onBlockSnap(layoutManager.getPosition(targetView));
                }
            }
            return out;
        }
    
        // We are flinging and need to know where we are heading.
        @Override
        public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager,
                                          int velocityX, int velocityY) {
            LinearLayoutManager lm = (LinearLayoutManager) layoutManager;
    
            initItemDimensionIfNeeded(layoutManager);
            mScroller.fling(0, 0, velocityX, velocityY, Integer.MIN_VALUE, Integer.MAX_VALUE,
                            Integer.MIN_VALUE, Integer.MAX_VALUE);
    
            if (velocityX != 0) {
                return mLayoutDirectionHelper
                    .getPositionsToMove(lm, mScroller.getFinalX(), mItemDimension);
            }
    
            if (velocityY != 0) {
                return mLayoutDirectionHelper
                    .getPositionsToMove(lm, mScroller.getFinalY(), mItemDimension);
            }
    
            return RecyclerView.NO_POSITION;
        }
    
        // We have scrolled to the neighborhood where we will snap. Determine the snap position.
        @Override
        public View findSnapView(RecyclerView.LayoutManager layoutManager) {
            // Snap to a view that is either 1) toward the bottom of the data and therefore on screen,
            // or, 2) toward the top of the data and may be off-screen.
            int snapPos = calcTargetPosition((LinearLayoutManager) layoutManager);
            View snapView = (snapPos == RecyclerView.NO_POSITION)
                ? null : layoutManager.findViewByPosition(snapPos);
    
            if (snapView == null) {
                Log.d(TAG, "<<<<findSnapView is returning null!");
            }
            Log.d(TAG, "<<<<findSnapView snapos=" + snapPos);
            return snapView;
        }
    
        // Does the heavy lifting for findSnapView.
        private int calcTargetPosition(LinearLayoutManager layoutManager) {
            int snapPos;
            int firstVisiblePos = layoutManager.findFirstVisibleItemPosition();
    
            if (firstVisiblePos == RecyclerView.NO_POSITION) {
                return RecyclerView.NO_POSITION;
            }
            initItemDimensionIfNeeded(layoutManager);
            if (firstVisiblePos >= mPriorFirstPosition) {
                // Scrolling toward bottom of data
                int firstCompletePosition = layoutManager.findFirstCompletelyVisibleItemPosition();
                if (firstCompletePosition != RecyclerView.NO_POSITION
                    && firstCompletePosition % mBlocksize == 0) {
                    snapPos = firstCompletePosition;
                } else {
                    snapPos = roundDownToBlockSize(firstVisiblePos + mBlocksize);
                }
            } else {
                // Scrolling toward top of data
                snapPos = roundDownToBlockSize(firstVisiblePos);
                // Check to see if target view exists. If it doesn't, force a smooth scroll.
                // SnapHelper only snaps to existing views and will not scroll to a non-existant one.
                // If limiting fling to single block, then the following is not needed since the
                // views are likely to be in the RecyclerView pool.
                if (layoutManager.findViewByPosition(snapPos) == null) {
                    int[] toScroll = mLayoutDirectionHelper.calculateDistanceToScroll(layoutManager, snapPos);
                    mRecyclerView.smoothScrollBy(toScroll[0], toScroll[1], sInterpolator);
                }
            }
            mPriorFirstPosition = firstVisiblePos;
    
            return snapPos;
        }
    
        private void initItemDimensionIfNeeded(final RecyclerView.LayoutManager layoutManager) {
            if (mItemDimension != 0) {
                return;
            }
    
            View child;
            if ((child = layoutManager.getChildAt(0)) == null) {
                return;
            }
    
            if (layoutManager.canScrollHorizontally()) {
                mItemDimension = child.getWidth();
                mBlocksize = getSpanCount(layoutManager) * (mRecyclerView.getWidth() / mItemDimension);
            } else if (layoutManager.canScrollVertically()) {
                mItemDimension = child.getHeight();
                mBlocksize = getSpanCount(layoutManager) * (mRecyclerView.getHeight() / mItemDimension);
            }
            mMaxPositionsToMove = mBlocksize * mMaxFlingBlocks;
        }
    
        private int getSpanCount(RecyclerView.LayoutManager layoutManager) {
            return (layoutManager instanceof GridLayoutManager)
                ? ((GridLayoutManager) layoutManager).getSpanCount()
                : 1;
        }
    
        private int roundDownToBlockSize(int trialPosition) {
            return trialPosition - trialPosition % mBlocksize;
        }
    
        private int roundUpToBlockSize(int trialPosition) {
            return roundDownToBlockSize(trialPosition + mBlocksize - 1);
        }
    
        @Nullable
        protected LinearSmoothScroller createScroller(RecyclerView.LayoutManager layoutManager) {
            if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {
                return null;
            }
            return new LinearSmoothScroller(mRecyclerView.getContext()) {
                @Override
                protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
                    int[] snapDistances = calculateDistanceToFinalSnap(mRecyclerView.getLayoutManager(),
                                                                       targetView);
                    final int dx = snapDistances[0];
                    final int dy = snapDistances[1];
                    final int time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy)));
                    if (time > 0) {
                        action.update(dx, dy, time, sInterpolator);
                    }
                }
    
                @Override
                protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
                    return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
                }
            };
        }
    
        public void setSnapBlockCallback(@Nullable SnapBlockCallback callback) {
            mSnapBlockCallback = callback;
        }
    
        /*
            Helper class that handles calculations for LTR and RTL layouts.
         */
        private class LayoutDirectionHelper {
    
            // Is the layout an RTL one?
            private final boolean mIsRTL;
    
            @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
            LayoutDirectionHelper(int direction) {
                mIsRTL = direction == View.LAYOUT_DIRECTION_RTL;
            }
    
            /*
                Calculate the amount of scroll needed to align the target view with the layout edge.
             */
            int getScrollToAlignView(View targetView) {
                return (mIsRTL)
                    ? mOrientationHelper.getDecoratedEnd(targetView) - mRecyclerView.getWidth()
                    : mOrientationHelper.getDecoratedStart(targetView);
            }
    
            /**
             * Calculate the distance to final snap position when the view corresponding to the snap
             * position is not currently available.
             *
             * @param layoutManager LinearLayoutManager or descendent class
             * @param targetPos     - Adapter position to snap to
             * @return int[2] {x-distance in pixels, y-distance in pixels}
             */
            int[] calculateDistanceToScroll(LinearLayoutManager layoutManager, int targetPos) {
                int[] out = new int[2];
    
                int firstVisiblePos;
    
                firstVisiblePos = layoutManager.findFirstVisibleItemPosition();
                if (layoutManager.canScrollHorizontally()) {
                    if (targetPos <= firstVisiblePos) { // scrolling toward top of data
                        if (mIsRTL) {
                            View lastView = layoutManager.findViewByPosition(layoutManager.findLastVisibleItemPosition());
                            out[0] = mOrientationHelper.getDecoratedEnd(lastView)
                                + (firstVisiblePos - targetPos) * mItemDimension;
                        } else {
                            View firstView = layoutManager.findViewByPosition(firstVisiblePos);
                            out[0] = mOrientationHelper.getDecoratedStart(firstView)
                                - (firstVisiblePos - targetPos) * mItemDimension;
                        }
                    }
                }
                if (layoutManager.canScrollVertically()) {
                    if (targetPos <= firstVisiblePos) { // scrolling toward top of data
                        View firstView = layoutManager.findViewByPosition(firstVisiblePos);
                        out[1] = firstView.getTop() - (firstVisiblePos - targetPos) * mItemDimension;
                    }
                }
    
                return out;
            }
    
            /*
                Calculate the number of positions to move in the RecyclerView given a scroll amount
                and the size of the items to be scrolled. Return integral multiple of mBlockSize not
                equal to zero.
             */
            int getPositionsToMove(LinearLayoutManager llm, int scroll, int itemSize) {
                int positionsToMove;
    
                positionsToMove = roundUpToBlockSize(Math.abs(scroll) / itemSize);
    
                if (positionsToMove < mBlocksize) {
                    // Must move at least one block
                    positionsToMove = mBlocksize;
                } else if (positionsToMove > mMaxPositionsToMove) {
                    // Clamp number of positions to move so we don't get wild flinging.
                    positionsToMove = mMaxPositionsToMove;
                }
    
                if (scroll < 0) {
                    positionsToMove *= -1;
                }
                if (mIsRTL) {
                    positionsToMove *= -1;
                }
    
                if (mLayoutDirectionHelper.isDirectionToBottom(scroll < 0)) {
                    // Scrolling toward the bottom of data.
                    return roundDownToBlockSize(llm.findFirstVisibleItemPosition()) + positionsToMove;
                }
                // Scrolling toward the top of the data.
                return roundDownToBlockSize(llm.findLastVisibleItemPosition()) + positionsToMove;
            }
    
            boolean isDirectionToBottom(boolean velocityNegative) {
                //noinspection SimplifiableConditionalExpression
                return mIsRTL ? velocityNegative : !velocityNegative;
            }
        }
    
        public interface SnapBlockCallback {
            void onBlockSnap(int snapPosition);
    
            void onBlockSnapped(int snapPosition);
    
        }
    
        private static final float MILLISECONDS_PER_INCH = 100f;
        @SuppressWarnings("unused")
        private static final String TAG = "SnapToBlock";
    }
    

    上面定义的< code>SnapBlockCallback接口可用于报告视图在要捕捉的块的开始处的适配器位置。如果视图在屏幕之外,则当进行调用时,与该位置相关联的视图可能不会被实例化。

     类似资料:
    • 如果您有这样的字符串: < code >【hello world】这是【最好的。家]是个好地方。 如何仅提取括号[]中的每个单词(由空格分隔)。现在我有这个工作 https://regex101.com/r/Tgokeq/2 哪个返回: 你好世界 最好的。家 但我想要: 你好 世界 该 最好的 。主页 PS:我知道我可以在一个foreach中做字符串拆分,但我不想要在正则表达式本身中使用它,就像这

    • 我需要一些关于我的GridLayoutManager的帮助。 如果我有一个项目,它应该在中间正常显示。 对于两个项目,它们应该彼此相邻。从三到四个项目的示例,它应该像正方形一样显示。3个项目的示例4个项目的示例 如果我是对的,spanCount 2应该就是这种情况。但是,如果有5个项目(所以连续3个),那就有问题了 从5个项目(及以上)开始,每个项目应该是连续3个,例如有5个项目,如果底部行的项目

    • 最后是我的适配器类

    • 如何创建一个选项菜单,如以下屏幕截图: 点击RecyclerView项目的“更多”图标后,应打开选项菜单! 我的尝试是这样的: 但这会导致问题,因为如果我点击RecyclerView item More按钮,则会单击完整项目。。。 这是我的RecyclerViewOnTouchListener: 我没有发现任何类似的问题,所以我希望你能帮助我!

    • 问题内容: 我有两个在android studio中制作的单独的android应用程序项目,我正在尝试将项目1合并到项目2中,以使其全部成为一个应用程序,我该怎么做呢?我知道如何在Eclipse中做到这一点,但在android studio中却不行。请注意,这两个项目都是android应用程序,都不是android库。 任何帮助都将是惊人的! 当我将项目1导入到项目模块中时,在生成成绩时出现此错误