使用recyclerview实现选择器

我是直接将github上https://github.com/BCsl/GalleryLayoutManager复制下来添加了禁止滚动

class GalleryLayoutManager(orientation: Int) : RecyclerView.LayoutManager(),
    ScrollVectorProvider {
    private var mFirstVisiblePosition = 0
    private var mLastVisiblePos = 0
    private var mInitialSelectedPosition = 0
    var curSelectedPosition = -1
    var mCurSelectedView: View? = null

    /**
     * Scroll state
     */
    private var mState: State? = null
    private val mSnapHelper = LinearSnapHelper()
    private val mInnerScrollListener: InnerScrollListener = InnerScrollListener()
    private var mCallbackInFling = false

    /**
     * Current orientation. Either [.HORIZONTAL] or [.VERTICAL]
     */
    var orientation = HORIZONTAL
    var isScrollEnabled = true
    private var mHorizontalHelper: OrientationHelper? = null
    private var mVerticalHelper: OrientationHelper? = null
    override fun generateDefaultLayoutParams(): RecyclerView.LayoutParams {
        return if (orientation == VERTICAL) {
            LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT
            )
        } else {
            LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.MATCH_PARENT
            )
        }
    }

    override fun generateLayoutParams(c: Context, attrs: AttributeSet): RecyclerView.LayoutParams {
        return LayoutParams(c, attrs)
    }

    override fun generateLayoutParams(lp: ViewGroup.LayoutParams): RecyclerView.LayoutParams {
        return if (lp is MarginLayoutParams) {
            LayoutParams(lp)
        } else {
            LayoutParams(lp)
        }
    }

    override fun checkLayoutParams(lp: RecyclerView.LayoutParams): Boolean {
        return lp is LayoutParams
    }

    override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State) {
        if (BuildConfig.DEBUG) {
            Log.d(
                TAG,
                "onLayoutChildren() called with: state = [$state]"
            )
        }
        if (itemCount == 0) {
            reset()
            detachAndScrapAttachedViews(recycler)
            return
        }
        if (state.isPreLayout) {
            return
        }
        if (state.itemCount != 0 && !state.didStructureChange()) {
            if (BuildConfig.DEBUG) {
                Log.d(TAG, "onLayoutChildren: ignore extra layout step")
            }
            return
        }
        if (childCount == 0 || state.didStructureChange()) {
            reset()
        }
        mInitialSelectedPosition = Math.min(Math.max(0, mInitialSelectedPosition), itemCount - 1)
        detachAndScrapAttachedViews(recycler)
        firstFillCover(recycler, state, 0)
    }

    private fun reset() {
        if (BuildConfig.DEBUG) {
            Log.d(TAG, "reset: ")
        }
        if (mState != null) {
            mState!!.mItemsFrames.clear()
        }
        //when data set update keep the last selected position
        if (curSelectedPosition != -1) {
            mInitialSelectedPosition = curSelectedPosition
        }
        mInitialSelectedPosition = Math.min(Math.max(0, mInitialSelectedPosition), itemCount - 1)
        mFirstVisiblePosition = mInitialSelectedPosition
        mLastVisiblePos = mInitialSelectedPosition
        curSelectedPosition = -1
        if (mCurSelectedView != null) {
            mCurSelectedView!!.isSelected = false
            mCurSelectedView = null
        }
    }

    private fun firstFillCover(
        recycler: RecyclerView.Recycler,
        state: RecyclerView.State,
        scrollDelta: Int
    ) {
        if (orientation == HORIZONTAL) {
            firstFillWithHorizontal(recycler, state)
        } else {
            firstFillWithVertical(recycler, state)
        }
        if (BuildConfig.DEBUG) {
            Log.d(
                TAG,
                "firstFillCover finish:first: $mFirstVisiblePosition,last:$mLastVisiblePos"
            )
        }
        if (mItemTransformer != null) {
            var child: View?
            for (i in 0 until childCount) {
                child = getChildAt(i)
                mItemTransformer!!.transformItem(
                    this,
                    child,
                    calculateToCenterFraction(child, scrollDelta.toFloat())
                )
            }
        }
        mInnerScrollListener.onScrolled(mRecyclerView!!, 0, 0)
    }

    /**
     * Layout the item view witch position specified by [GalleryLayoutManager.mInitialSelectedPosition] first and then layout the other
     *
     * @param recycler
     * @param state
     */
    private fun firstFillWithHorizontal(
        recycler: RecyclerView.Recycler,
        state: RecyclerView.State
    ) {
        detachAndScrapAttachedViews(recycler)
        val leftEdge = orientationHelper!!.startAfterPadding
        val rightEdge = orientationHelper!!.endAfterPadding
        val startPosition = mInitialSelectedPosition
        val scrapWidth: Int
        val scrapHeight: Int
        val scrapRect = Rect()
        val height = verticalSpace
        val topOffset: Int
        //layout the init position view
        val scrap = recycler.getViewForPosition(mInitialSelectedPosition)
        addView(scrap, 0)
        measureChildWithMargins(scrap, 0, 0)
        scrapWidth = getDecoratedMeasuredWidth(scrap)
        scrapHeight = getDecoratedMeasuredHeight(scrap)
        topOffset = (paddingTop + (height - scrapHeight) / 2.0f).toInt()
        val left = (paddingLeft + (horizontalSpace - scrapWidth) / 2f).toInt()
        scrapRect[left, topOffset, left + scrapWidth] = topOffset + scrapHeight
        layoutDecorated(scrap, scrapRect.left, scrapRect.top, scrapRect.right, scrapRect.bottom)
        if (this.state!!.mItemsFrames[startPosition] == null) {
            this.state!!.mItemsFrames.put(startPosition, scrapRect)
        } else {
            this.state!!.mItemsFrames[startPosition]!!.set(scrapRect)
        }
        mLastVisiblePos = startPosition
        mFirstVisiblePosition = mLastVisiblePos
        val leftStartOffset = getDecoratedLeft(scrap)
        val rightStartOffset = getDecoratedRight(scrap)
        //fill left of center
        fillLeft(recycler, mInitialSelectedPosition - 1, leftStartOffset, leftEdge)
        //fill right of center
        fillRight(recycler, mInitialSelectedPosition + 1, rightStartOffset, rightEdge)
    }

    override fun onItemsRemoved(recyclerView: RecyclerView, positionStart: Int, itemCount: Int) {
        super.onItemsRemoved(recyclerView, positionStart, itemCount)
    }

    /**
     * Layout the item view witch position special by [GalleryLayoutManager.mInitialSelectedPosition] first and then layout the other
     *
     * @param recycler
     * @param state
     */
    private fun firstFillWithVertical(recycler: RecyclerView.Recycler, state: RecyclerView.State) {
        detachAndScrapAttachedViews(recycler)
        val topEdge = orientationHelper!!.startAfterPadding
        val bottomEdge = orientationHelper!!.endAfterPadding
        val startPosition = mInitialSelectedPosition
        val scrapWidth: Int
        val scrapHeight: Int
        val scrapRect = Rect()
        val width = horizontalSpace
        val leftOffset: Int
        //layout the init position view
        val scrap = recycler.getViewForPosition(mInitialSelectedPosition)
        addView(scrap, 0)
        measureChildWithMargins(scrap, 0, 0)
        scrapWidth = getDecoratedMeasuredWidth(scrap)
        scrapHeight = getDecoratedMeasuredHeight(scrap)
        leftOffset = (paddingLeft + (width - scrapWidth) / 2.0f).toInt()
        val top = (paddingTop + (verticalSpace - scrapHeight) / 2f).toInt()
        scrapRect[leftOffset, top, leftOffset + scrapWidth] = top + scrapHeight
        layoutDecorated(scrap, scrapRect.left, scrapRect.top, scrapRect.right, scrapRect.bottom)
        if (this.state!!.mItemsFrames[startPosition] == null) {
            this.state!!.mItemsFrames.put(startPosition, scrapRect)
        } else {
            this.state!!.mItemsFrames[startPosition]!!.set(scrapRect)
        }
        mLastVisiblePos = startPosition
        mFirstVisiblePosition = mLastVisiblePos
        val topStartOffset = getDecoratedTop(scrap)
        val bottomStartOffset = getDecoratedBottom(scrap)
        //fill left of center
        fillTop(recycler, mInitialSelectedPosition - 1, topStartOffset, topEdge)
        //fill right of center
        fillBottom(recycler, mInitialSelectedPosition + 1, bottomStartOffset, bottomEdge)
    }

    /**
     * Fill left of the center view
     *
     * @param recycler
     * @param startPosition start position to fill left
     * @param startOffset   layout start offset
     * @param leftEdge
     */
    private fun fillLeft(
        recycler: RecyclerView.Recycler,
        startPosition: Int,
        startOffset: Int,
        leftEdge: Int
    ) {
        var startOffset = startOffset
        var scrap: View
        var topOffset: Int
        var scrapWidth: Int
        var scrapHeight: Int
        val scrapRect = Rect()
        val height = verticalSpace
        var i = startPosition
        while (i >= 0 && startOffset > leftEdge) {
            scrap = recycler.getViewForPosition(i)
            addView(scrap, 0)
            measureChildWithMargins(scrap, 0, 0)
            scrapWidth = getDecoratedMeasuredWidth(scrap)
            scrapHeight = getDecoratedMeasuredHeight(scrap)
            topOffset = (paddingTop + (height - scrapHeight) / 2.0f).toInt()
            scrapRect[startOffset - scrapWidth, topOffset, startOffset] = topOffset + scrapHeight
            layoutDecorated(scrap, scrapRect.left, scrapRect.top, scrapRect.right, scrapRect.bottom)
            startOffset = scrapRect.left
            mFirstVisiblePosition = i
            if (state!!.mItemsFrames[i] == null) {
                state!!.mItemsFrames.put(i, scrapRect)
            } else {
                state!!.mItemsFrames[i]!!.set(scrapRect)
            }
            i--
        }
    }

    /**
     * Fill right of the center view
     *
     * @param recycler
     * @param startPosition start position to fill right
     * @param startOffset   layout start offset
     * @param rightEdge
     */
    private fun fillRight(
        recycler: RecyclerView.Recycler,
        startPosition: Int,
        startOffset: Int,
        rightEdge: Int
    ) {
        var startOffset = startOffset
        var scrap: View
        var topOffset: Int
        var scrapWidth: Int
        var scrapHeight: Int
        val scrapRect = Rect()
        val height = verticalSpace
        var i = startPosition
        while (i < itemCount && startOffset < rightEdge) {
            scrap = recycler.getViewForPosition(i)
            addView(scrap)
            measureChildWithMargins(scrap, 0, 0)
            scrapWidth = getDecoratedMeasuredWidth(scrap)
            scrapHeight = getDecoratedMeasuredHeight(scrap)
            topOffset = (paddingTop + (height - scrapHeight) / 2.0f).toInt()
            scrapRect[startOffset, topOffset, startOffset + scrapWidth] = topOffset + scrapHeight
            layoutDecorated(scrap, scrapRect.left, scrapRect.top, scrapRect.right, scrapRect.bottom)
            startOffset = scrapRect.right
            mLastVisiblePos = i
            if (state!!.mItemsFrames[i] == null) {
                state!!.mItemsFrames.put(i, scrapRect)
            } else {
                state!!.mItemsFrames[i]!!.set(scrapRect)
            }
            i++
        }
    }

    /**
     * Fill top of the center view
     *
     * @param recycler
     * @param startPosition start position to fill top
     * @param startOffset   layout start offset
     * @param topEdge       top edge of the RecycleView
     */
    private fun fillTop(
        recycler: RecyclerView.Recycler,
        startPosition: Int,
        startOffset: Int,
        topEdge: Int
    ) {
        var startOffset = startOffset
        var scrap: View
        var leftOffset: Int
        var scrapWidth: Int
        var scrapHeight: Int
        val scrapRect = Rect()
        val width = horizontalSpace
        var i = startPosition
        while (i >= 0 && startOffset > topEdge) {
            scrap = recycler.getViewForPosition(i)
            addView(scrap, 0)
            measureChildWithMargins(scrap, 0, 0)
            scrapWidth = getDecoratedMeasuredWidth(scrap)
            scrapHeight = getDecoratedMeasuredHeight(scrap)
            leftOffset = (paddingLeft + (width - scrapWidth) / 2.0f).toInt()
            scrapRect[leftOffset, startOffset - scrapHeight, leftOffset + scrapWidth] = startOffset
            layoutDecorated(scrap, scrapRect.left, scrapRect.top, scrapRect.right, scrapRect.bottom)
            startOffset = scrapRect.top
            mFirstVisiblePosition = i
            if (state!!.mItemsFrames[i] == null) {
                state!!.mItemsFrames.put(i, scrapRect)
            } else {
                state!!.mItemsFrames[i]!!.set(scrapRect)
            }
            i--
        }
    }

    /**
     * Fill bottom of the center view
     *
     * @param recycler
     * @param startPosition start position to fill bottom
     * @param startOffset   layout start offset
     * @param bottomEdge    bottom edge of the RecycleView
     */
    private fun fillBottom(
        recycler: RecyclerView.Recycler,
        startPosition: Int,
        startOffset: Int,
        bottomEdge: Int
    ) {
        var startOffset = startOffset
        var scrap: View
        var leftOffset: Int
        var scrapWidth: Int
        var scrapHeight: Int
        val scrapRect = Rect()
        val width = horizontalSpace
        var i = startPosition
        while (i < itemCount && startOffset < bottomEdge) {
            scrap = recycler.getViewForPosition(i)
            addView(scrap)
            measureChildWithMargins(scrap, 0, 0)
            scrapWidth = getDecoratedMeasuredWidth(scrap)
            scrapHeight = getDecoratedMeasuredHeight(scrap)
            leftOffset = (paddingLeft + (width - scrapWidth) / 2.0f).toInt()
            scrapRect[leftOffset, startOffset, leftOffset + scrapWidth] = startOffset + scrapHeight
            layoutDecorated(scrap, scrapRect.left, scrapRect.top, scrapRect.right, scrapRect.bottom)
            startOffset = scrapRect.bottom
            mLastVisiblePos = i
            if (state!!.mItemsFrames[i] == null) {
                state!!.mItemsFrames.put(i, scrapRect)
            } else {
                state!!.mItemsFrames[i]!!.set(scrapRect)
            }
            i++
        }
    }

    private fun fillCover(
        recycler: RecyclerView.Recycler,
        state: RecyclerView.State,
        scrollDelta: Int
    ) {
        if (itemCount == 0) {
            return
        }
        if (orientation == HORIZONTAL) {
            fillWithHorizontal(recycler, state, scrollDelta)
        } else {
            fillWithVertical(recycler, state, scrollDelta)
        }
        if (mItemTransformer != null) {
            var child: View?
            for (i in 0 until childCount) {
                child = getChildAt(i)
                mItemTransformer!!.transformItem(
                    this,
                    child,
                    calculateToCenterFraction(child, scrollDelta.toFloat())
                )
            }
        }
    }

    private fun calculateToCenterFraction(child: View?, pendingOffset: Float): Float {
        val distance = calculateDistanceCenter(child, pendingOffset)
        val childLength = if (orientation == HORIZONTAL) child!!.width else child!!.height
        if (BuildConfig.DEBUG) {
            Log.d(
                TAG,
                "calculateToCenterFraction: distance:$distance,childLength:$childLength"
            )
        }
        return Math.max(-1f, Math.min(1f, distance * 1f / childLength))
    }

    /**
     * @param child
     * @param pendingOffset child view will scroll by
     * @return
     */
    private fun calculateDistanceCenter(child: View?, pendingOffset: Float): Int {
        val orientationHelper = orientationHelper
        val parentCenter =
            (orientationHelper!!.endAfterPadding - orientationHelper.startAfterPadding) / 2 + orientationHelper.startAfterPadding
        return if (orientation == HORIZONTAL) {
            (child!!.width / 2 - pendingOffset + child.left - parentCenter).toInt()
        } else {
            (child!!.height / 2 - pendingOffset + child.top - parentCenter).toInt()
        }
    }

    /**
     * @param recycler
     * @param state
     * @param dy
     */
    private fun fillWithVertical(
        recycler: RecyclerView.Recycler,
        state: RecyclerView.State,
        dy: Int
    ) {
        if (BuildConfig.DEBUG) {
            Log.d(TAG, "fillWithVertical: dy:$dy")
        }
        val topEdge = orientationHelper!!.startAfterPadding
        val bottomEdge = orientationHelper!!.endAfterPadding

        //1.remove and recycle the view that disappear in screen
        var child: View?
        if (childCount > 0) {
            if (dy >= 0) {
                //remove and recycle the top off screen view
                var fixIndex = 0
                for (i in 0 until childCount) {
                    child = getChildAt(i + fixIndex)
                    if (getDecoratedBottom(child!!) - dy < topEdge) {
                        if (BuildConfig.DEBUG) {
                            Log.v(
                                TAG, "fillWithVertical: removeAndRecycleView:" + getPosition(
                                    child
                                ) + ",bottom:" + getDecoratedBottom(child)
                            )
                        }
                        removeAndRecycleView(child, recycler)
                        mFirstVisiblePosition++
                        fixIndex--
                    } else {
                        if (BuildConfig.DEBUG) {
                            Log.d(
                                TAG, "fillWithVertical: break:" + getPosition(
                                    child
                                ) + ",bottom:" + getDecoratedBottom(child)
                            )
                        }
                        break
                    }
                }
            } else { //dy<0
                //remove and recycle the bottom off screen view
                for (i in childCount - 1 downTo 0) {
                    child = getChildAt(i)
                    if (getDecoratedTop(child!!) - dy > bottomEdge) {
                        if (BuildConfig.DEBUG) {
                            Log.v(
                                TAG, "fillWithVertical: removeAndRecycleView:" + getPosition(
                                    child
                                )
                            )
                        }
                        removeAndRecycleView(child, recycler)
                        mLastVisiblePos--
                    } else {
                        break
                    }
                }
            }
        }
        var startPosition = mFirstVisiblePosition
        var startOffset = -1
        var scrapWidth: Int
        var scrapHeight: Int
        var scrapRect: Rect?
        val width = horizontalSpace
        var leftOffset: Int
        var scrap: View
        //2.Add or reattach item view to fill screen
        if (dy >= 0) {
            if (childCount != 0) {
                val lastView = getChildAt(childCount - 1)
                startPosition = getPosition(lastView!!) + 1
                startOffset = getDecoratedBottom(lastView)
            }
            var i = startPosition
            while (i < itemCount && startOffset < bottomEdge + dy) {
                scrapRect = this.state!!.mItemsFrames[i]
                scrap = recycler.getViewForPosition(i)
                addView(scrap)
                if (scrapRect == null) {
                    scrapRect = Rect()
                    this.state!!.mItemsFrames.put(i, scrapRect)
                }
                measureChildWithMargins(scrap, 0, 0)
                scrapWidth = getDecoratedMeasuredWidth(scrap)
                scrapHeight = getDecoratedMeasuredHeight(scrap)
                leftOffset = (paddingLeft + (width - scrapWidth) / 2.0f).toInt()
                if (startOffset == -1 && startPosition == 0) {
                    //layout the first position item in center
                    val top = (paddingTop + (verticalSpace - scrapHeight) / 2f).toInt()
                    scrapRect[leftOffset, top, leftOffset + scrapWidth] = top + scrapHeight
                } else {
                    scrapRect[leftOffset, startOffset, leftOffset + scrapWidth] =
                        startOffset + scrapHeight
                }
                layoutDecorated(
                    scrap,
                    scrapRect.left,
                    scrapRect.top,
                    scrapRect.right,
                    scrapRect.bottom
                )
                startOffset = scrapRect.bottom
                mLastVisiblePos = i
                if (BuildConfig.DEBUG) {
                    Log.d(
                        TAG,
                        "fillWithVertical: add view:$i,startOffset:$startOffset,mLastVisiblePos:$mLastVisiblePos,bottomEdge$bottomEdge"
                    )
                }
                i++
            }
        } else {
            //dy<0
            if (childCount > 0) {
                val firstView = getChildAt(0)
                startPosition = getPosition(firstView!!) - 1 //前一个View的position
                startOffset = getDecoratedTop(firstView)
            }
            var i = startPosition
            while (i >= 0 && startOffset > topEdge + dy) {
                scrapRect = this.state!!.mItemsFrames[i]
                scrap = recycler.getViewForPosition(i)
                addView(scrap, 0)
                if (scrapRect == null) {
                    scrapRect = Rect()
                    this.state!!.mItemsFrames.put(i, scrapRect)
                }
                measureChildWithMargins(scrap, 0, 0)
                scrapWidth = getDecoratedMeasuredWidth(scrap)
                scrapHeight = getDecoratedMeasuredHeight(scrap)
                leftOffset = (paddingLeft + (width - scrapWidth) / 2.0f).toInt()
                scrapRect[leftOffset, startOffset - scrapHeight, leftOffset + scrapWidth] =
                    startOffset
                layoutDecorated(
                    scrap,
                    scrapRect.left,
                    scrapRect.top,
                    scrapRect.right,
                    scrapRect.bottom
                )
                startOffset = scrapRect.top
                mFirstVisiblePosition = i
                i--
            }
        }
    }

    /**
     * @param recycler
     * @param state
     */
    private fun fillWithHorizontal(
        recycler: RecyclerView.Recycler,
        state: RecyclerView.State,
        dx: Int
    ) {
        val leftEdge = orientationHelper!!.startAfterPadding
        val rightEdge = orientationHelper!!.endAfterPadding
        if (BuildConfig.DEBUG) {
            Log.v(
                TAG,
                "fillWithHorizontal() called with: dx = [$dx],leftEdge:$leftEdge,rightEdge:$rightEdge"
            )
        }
        //1.remove and recycle the view that disappear in screen
        var child: View?
        if (childCount > 0) {
            if (dx >= 0) {
                //remove and recycle the left off screen view
                var fixIndex = 0
                for (i in 0 until childCount) {
                    child = getChildAt(i + fixIndex)
                    if (getDecoratedRight(child!!) - dx < leftEdge) {
                        removeAndRecycleView(child, recycler)
                        mFirstVisiblePosition++
                        fixIndex--
                        if (BuildConfig.DEBUG) {
                            Log.v(
                                TAG, "fillWithHorizontal:removeAndRecycleView:" + getPosition(
                                    child
                                ) + " mFirstVisiblePosition change to:" + mFirstVisiblePosition
                            )
                        }
                    } else {
                        break
                    }
                }
            } else { //dx<0
                //remove and recycle the right off screen view
                for (i in childCount - 1 downTo 0) {
                    child = getChildAt(i)
                    if (getDecoratedLeft(child!!) - dx > rightEdge) {
                        removeAndRecycleView(child, recycler)
                        mLastVisiblePos--
                        if (BuildConfig.DEBUG) {
                            Log.v(
                                TAG, "fillWithHorizontal:removeAndRecycleView:" + getPosition(
                                    child
                                ) + "mLastVisiblePos change to:" + mLastVisiblePos
                            )
                        }
                    }
                }
            }
        }
        //2.Add or reattach item view to fill screen
        var startPosition = mFirstVisiblePosition
        var startOffset = -1
        var scrapWidth: Int
        var scrapHeight: Int
        var scrapRect: Rect?
        val height = verticalSpace
        var topOffset: Int
        var scrap: View
        if (dx >= 0) {
            if (childCount != 0) {
                val lastView = getChildAt(childCount - 1)
                startPosition = getPosition(lastView!!) + 1 //start layout from next position item
                startOffset = getDecoratedRight(lastView)
                if (BuildConfig.DEBUG) {
                    Log.d(
                        TAG,
                        "fillWithHorizontal:to right startPosition:$startPosition,startOffset:$startOffset,rightEdge:$rightEdge"
                    )
                }
            }
            var i = startPosition
            while (i < itemCount && startOffset < rightEdge + dx) {
                scrapRect = this.state!!.mItemsFrames[i]
                scrap = recycler.getViewForPosition(i)
                addView(scrap)
                if (scrapRect == null) {
                    scrapRect = Rect()
                    this.state!!.mItemsFrames.put(i, scrapRect)
                }
                measureChildWithMargins(scrap, 0, 0)
                scrapWidth = getDecoratedMeasuredWidth(scrap)
                scrapHeight = getDecoratedMeasuredHeight(scrap)
                topOffset = (paddingTop + (height - scrapHeight) / 2.0f).toInt()
                if (startOffset == -1 && startPosition == 0) {
                    // layout the first position item in center
                    val left = (paddingLeft + (horizontalSpace - scrapWidth) / 2f).toInt()
                    scrapRect[left, topOffset, left + scrapWidth] = topOffset + scrapHeight
                } else {
                    scrapRect[startOffset, topOffset, startOffset + scrapWidth] =
                        topOffset + scrapHeight
                }
                layoutDecorated(
                    scrap,
                    scrapRect.left,
                    scrapRect.top,
                    scrapRect.right,
                    scrapRect.bottom
                )
                startOffset = scrapRect.right
                mLastVisiblePos = i
                if (BuildConfig.DEBUG) {
                    Log.d(
                        TAG,
                        "fillWithHorizontal,layout:mLastVisiblePos: $mLastVisiblePos"
                    )
                }
                i++
            }
        } else {
            //dx<0
            if (childCount > 0) {
                val firstView = getChildAt(0)
                startPosition =
                    getPosition(firstView!!) - 1 //start layout from previous position item
                startOffset = getDecoratedLeft(firstView)
                if (BuildConfig.DEBUG) {
                    Log.d(
                        TAG,
                        "fillWithHorizontal:to left startPosition:$startPosition,startOffset:$startOffset,leftEdge:$leftEdge,child count:$childCount"
                    )
                }
            }
            var i = startPosition
            while (i >= 0 && startOffset > leftEdge + dx) {
                scrapRect = this.state!!.mItemsFrames[i]
                scrap = recycler.getViewForPosition(i)
                addView(scrap, 0)
                if (scrapRect == null) {
                    scrapRect = Rect()
                    this.state!!.mItemsFrames.put(i, scrapRect)
                }
                measureChildWithMargins(scrap, 0, 0)
                scrapWidth = getDecoratedMeasuredWidth(scrap)
                scrapHeight = getDecoratedMeasuredHeight(scrap)
                topOffset = (paddingTop + (height - scrapHeight) / 2.0f).toInt()
                scrapRect[startOffset - scrapWidth, topOffset, startOffset] =
                    topOffset + scrapHeight
                layoutDecorated(
                    scrap,
                    scrapRect.left,
                    scrapRect.top,
                    scrapRect.right,
                    scrapRect.bottom
                )
                startOffset = scrapRect.left
                mFirstVisiblePosition = i
                i--
            }
        }
    }

    private val horizontalSpace: Int
        private get() = width - paddingRight - paddingLeft
    private val verticalSpace: Int
        private get() = height - paddingBottom - paddingTop
    val state: State?
        get() {
            if (mState == null) {
                mState = State()
            }
            return mState
        }

    private fun calculateScrollDirectionForPosition(position: Int): Int {
        if (childCount == 0) {
            return LAYOUT_START
        }
        val firstChildPos = mFirstVisiblePosition
        return if (position < firstChildPos) LAYOUT_START else LAYOUT_END
    }

    override fun computeScrollVectorForPosition(targetPosition: Int): PointF? {
        val direction = calculateScrollDirectionForPosition(targetPosition)
        val outVector = PointF()
        if (direction == 0) {
            return null
        }
        if (orientation == HORIZONTAL) {
            outVector.x = direction.toFloat()
            outVector.y = 0f
        } else {
            outVector.x = 0f
            outVector.y = direction.toFloat()
        }
        return outVector
    }

    /**
     * @author chensuilun
     */
    inner class State {
        /**
         * Record all item view 's last position after last layout
         */
        var mItemsFrames: SparseArray<Rect?>

        /**
         * RecycleView 's current scroll distance since first layout
         */
        var mScrollDelta: Int

        init {
            mItemsFrames = SparseArray()
            mScrollDelta = 0
        }
    }

    override fun canScrollHorizontally(): Boolean {
        return orientation == HORIZONTAL && isScrollEnabled
    }

    override fun canScrollVertically(): Boolean {
        return orientation == VERTICAL && isScrollEnabled
    }

    override fun scrollHorizontallyBy(
        dx: Int,
        recycler: RecyclerView.Recycler,
        state: RecyclerView.State
    ): Int {
        // When dx is positive,finger fling from right to left(←),scrollX+
        if (childCount == 0 || dx == 0) {
            return 0
        }
        var delta = -dx
        val parentCenter =
            (orientationHelper!!.endAfterPadding - orientationHelper!!.startAfterPadding) / 2 + orientationHelper!!.startAfterPadding
        val child: View?
        if (dx > 0) {
            //If we've reached the last item, enforce limits
            if (getPosition(getChildAt(childCount - 1)!!) == itemCount - 1) {
                child = getChildAt(childCount - 1)
                delta = -Math.max(
                    0,
                    Math.min(dx, (child!!.right - child.left) / 2 + child.left - parentCenter)
                )
            }
        } else {
            //If we've reached the first item, enforce limits
            if (mFirstVisiblePosition == 0) {
                child = getChildAt(0)
                delta = -Math.min(
                    0,
                    Math.max(dx, (child!!.right - child.left) / 2 + child.left - parentCenter)
                )
            }
        }
        if (BuildConfig.DEBUG) {
            Log.d(
                TAG,
                "scrollHorizontallyBy: dx:$dx,fixed:$delta"
            )
        }
        this.state!!.mScrollDelta = -delta
        fillCover(recycler, state, -delta)
        offsetChildrenHorizontal(delta)
        return -delta
    }

    override fun scrollVerticallyBy(
        dy: Int,
        recycler: RecyclerView.Recycler,
        state: RecyclerView.State
    ): Int {
        if (childCount == 0 || dy == 0) {
            return 0
        }
        var delta = -dy
        val parentCenter =
            (orientationHelper!!.endAfterPadding - orientationHelper!!.startAfterPadding) / 2 + orientationHelper!!.startAfterPadding
        val child: View?
        if (dy > 0) {
            //If we've reached the last item, enforce limits
            if (getPosition(getChildAt(childCount - 1)!!) == itemCount - 1) {
                child = getChildAt(childCount - 1)
                delta = -Math.max(
                    0, Math.min(
                        dy, (getDecoratedBottom(child!!) - getDecoratedTop(
                            child
                        )) / 2 + getDecoratedTop(child) - parentCenter
                    )
                )
            }
        } else {
            //If we've reached the first item, enforce limits
            if (mFirstVisiblePosition == 0) {
                child = getChildAt(0)
                delta = -Math.min(
                    0, Math.max(
                        dy, (getDecoratedBottom(child!!) - getDecoratedTop(
                            child
                        )) / 2 + getDecoratedTop(child) - parentCenter
                    )
                )
            }
        }
        if (BuildConfig.DEBUG) {
            Log.d(
                TAG,
                "scrollVerticallyBy: dy:$dy,fixed:$delta"
            )
        }
        this.state!!.mScrollDelta = -delta
        fillCover(recycler, state, -delta)
        offsetChildrenVertical(delta)
        return -delta
    }

    val orientationHelper: OrientationHelper?
        get() = if (orientation == HORIZONTAL) {
            if (mHorizontalHelper == null) {
                mHorizontalHelper = OrientationHelper.createHorizontalHelper(this)
            }
            mHorizontalHelper
        } else {
            if (mVerticalHelper == null) {
                mVerticalHelper = OrientationHelper.createVerticalHelper(this)
            }
            mVerticalHelper
        }

    /**
     * @author chensuilun
     */
    class LayoutParams : RecyclerView.LayoutParams {
        constructor(c: Context?, attrs: AttributeSet?) : super(c, attrs) {}
        constructor(width: Int, height: Int) : super(width, height) {}
        constructor(source: MarginLayoutParams?) : super(source) {}
        constructor(source: ViewGroup.LayoutParams?) : super(source) {}
        constructor(source: RecyclerView.LayoutParams?) : super(source) {}
    }

    private var mItemTransformer: ItemTransformer? = null
    fun setItemTransformer(itemTransformer: ItemTransformer?) {
        mItemTransformer = itemTransformer
    }

    /**
     * A ItemTransformer is invoked whenever a attached item is scrolled.
     * This offers an opportunity for the application to apply a custom transformation
     * to the item views using animation properties.
     */
    interface ItemTransformer {
        /**
         * Apply a property transformation to the given item.
         *
         * @param layoutManager Current LayoutManager
         * @param item          Apply the transformation to this item
         * @param fraction      of page relative to the current front-and-center position of the pager.
         * 0 is front and center. 1 is one full
         * page position to the right, and -1 is one page position to the left.
         */
        fun transformItem(layoutManager: GalleryLayoutManager?, item: View?, fraction: Float)
    }

    /**
     * Listen for changes to the selected item
     *
     * @author chensuilun
     */
    interface OnItemSelectedListener {
        /**
         * @param recyclerView The RecyclerView which item view belong to.
         * @param item         The current selected view
         * @param position     The current selected view's position
         */
        fun onItemSelected(recyclerView: RecyclerView?, item: View?, position: Int)
    }

    private var mOnItemSelectedListener: OnItemSelectedListener? = null
    fun setOnItemSelectedListener(onItemSelectedListener: OnItemSelectedListener?) {
        mOnItemSelectedListener = onItemSelectedListener
    }

    /**
     * @param recyclerView
     * @param selectedPosition
     */
    @JvmOverloads
    fun attach(recyclerView: RecyclerView?, selectedPosition: Int = -1) {
        requireNotNull(recyclerView) { "The attach RecycleView must not null!!" }
        mRecyclerView = recyclerView
        mInitialSelectedPosition = Math.max(0, selectedPosition)
        recyclerView.layoutManager = this
        mSnapHelper.attachToRecyclerView(recyclerView)
        recyclerView.addOnScrollListener(mInnerScrollListener)
    }

    var mRecyclerView: RecyclerView? = null

    init {
        this.orientation = orientation
    }

    fun setCallbackInFling(callbackInFling: Boolean) {
        mCallbackInFling = callbackInFling
    }

    /**
     * Inner Listener to listen for changes to the selected item
     *
     * @author chensuilun
     */
    private inner class InnerScrollListener : RecyclerView.OnScrollListener() {
        var mState = 0
        var mCallbackOnIdle = false
        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            super.onScrolled(recyclerView, dx, dy)
            val snap = mSnapHelper.findSnapView(recyclerView.layoutManager)
            if (snap != null) {
                val selectedPosition = recyclerView.layoutManager!!.getPosition(snap)
                if (selectedPosition != curSelectedPosition) {
                    if (mCurSelectedView != null) {
                        mCurSelectedView!!.isSelected = false
                    }
                    mCurSelectedView = snap
                    mCurSelectedView!!.isSelected = true
                    curSelectedPosition = selectedPosition
                    if (!mCallbackInFling && mState != RecyclerView.SCROLL_STATE_IDLE) {
                        if (BuildConfig.DEBUG) {
                            Log.v(TAG, "ignore selection change callback when fling ")
                        }
                        mCallbackOnIdle = true
                        return
                    }
                    if (mOnItemSelectedListener != null) {
                        mOnItemSelectedListener!!.onItemSelected(
                            recyclerView, snap,
                            curSelectedPosition
                        )
                    }
                }
            }
            if (BuildConfig.DEBUG) {
                Log.v(TAG, "onScrolled: dx:$dx,dy:$dy")
            }
        }

        override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
            super.onScrollStateChanged(recyclerView, newState)
            mState = newState
            if (BuildConfig.DEBUG) {
                Log.v(
                    TAG,
                    "onScrollStateChanged: $newState"
                )
            }
            if (mState == RecyclerView.SCROLL_STATE_IDLE) {
                val snap = mSnapHelper.findSnapView(recyclerView.layoutManager)
                if (snap != null) {
                    val selectedPosition = recyclerView.layoutManager!!.getPosition(snap)
                    if (selectedPosition != curSelectedPosition) {
                        if (mCurSelectedView != null) {
                            mCurSelectedView!!.isSelected = false
                        }
                        mCurSelectedView = snap
                        mCurSelectedView!!.isSelected = true
                        curSelectedPosition = selectedPosition
                        if (mOnItemSelectedListener != null) {
                            mOnItemSelectedListener!!.onItemSelected(
                                recyclerView, snap,
                                curSelectedPosition
                            )
                        }
                    } else if (!mCallbackInFling && mOnItemSelectedListener != null && mCallbackOnIdle) {
                        mCallbackOnIdle = false
                        mOnItemSelectedListener!!.onItemSelected(
                            recyclerView, snap,
                            curSelectedPosition
                        )
                    }
                } else {
                    Log.e(TAG, "onScrollStateChanged: snap null")
                }
            }
        }
    }

    override fun smoothScrollToPosition(
        recyclerView: RecyclerView,
        state: RecyclerView.State,
        position: Int
    ) {
        val linearSmoothScroller: GallerySmoothScroller =
            GallerySmoothScroller(recyclerView.context)
        linearSmoothScroller.targetPosition = position
        startSmoothScroll(linearSmoothScroller)
    }

    /**
     * Implement to support [GalleryLayoutManager.smoothScrollToPosition]
     */
    private inner class GallerySmoothScroller(context: Context?) :
        LinearSmoothScroller(context) {
        /**
         * Calculates the horizontal scroll amount necessary to make the given view in center of the RecycleView
         *
         * @param view The view which we want to make in center of the RecycleView
         * @return The horizontal scroll amount necessary to make the view in center of the RecycleView
         */
        fun calculateDxToMakeCentral(view: View): Int {
            val layoutManager = layoutManager
            if (layoutManager == null || !layoutManager.canScrollHorizontally()) {
                return 0
            }
            val params = view.layoutParams as RecyclerView.LayoutParams
            val left = layoutManager.getDecoratedLeft(view) - params.leftMargin
            val right = layoutManager.getDecoratedRight(view) + params.rightMargin
            val start = layoutManager.paddingLeft
            val end = layoutManager.width - layoutManager.paddingRight
            val childCenter = left + ((right - left) / 2.0f).toInt()
            val containerCenter = ((end - start) / 2f).toInt()
            return containerCenter - childCenter
        }

        /**
         * Calculates the vertical scroll amount necessary to make the given view in center of the RecycleView
         *
         * @param view The view which we want to make in center of the RecycleView
         * @return The vertical scroll amount necessary to make the view in center of the RecycleView
         */
        fun calculateDyToMakeCentral(view: View): Int {
            val layoutManager = layoutManager
            if (layoutManager == null || !layoutManager.canScrollVertically()) {
                return 0
            }
            val params = view.layoutParams as RecyclerView.LayoutParams
            val top = layoutManager.getDecoratedTop(view) - params.topMargin
            val bottom = layoutManager.getDecoratedBottom(view) + params.bottomMargin
            val start = layoutManager.paddingTop
            val end = layoutManager.height - layoutManager.paddingBottom
            val childCenter = top + ((bottom - top) / 2.0f).toInt()
            val containerCenter = ((end - start) / 2f).toInt()
            return containerCenter - childCenter
        }

        override fun onTargetFound(targetView: View, state: RecyclerView.State, action: Action) {
            val dx = calculateDxToMakeCentral(targetView)
            val dy = calculateDyToMakeCentral(targetView)
            val distance = Math.sqrt((dx * dx + dy * dy).toDouble()).toInt()
            val time = calculateTimeForDeceleration(distance)
            if (time > 0) {
                action.update(-dx, -dy, time, mDecelerateInterpolator)
            }
        }
    }

    companion object {
        private const val TAG = "GalleryLayoutManager"
        const val LAYOUT_START = -1
        const val LAYOUT_END = 1
        const val HORIZONTAL = OrientationHelper.HORIZONTAL
        const val VERTICAL = OrientationHelper.VERTICAL
    }
}

绑定数据

private val mAdapter by lazy { StepSelectAdapter() }
private val bloodSugarManager by lazy { GalleryLayoutManager(GalleryLayoutManager.HORIZONTAL) }
private val mManager by lazy { GalleryLayoutManager(GalleryLayoutManager.VERTICAL) }
 bloodSugarManager.apply {
 			//设置默认选中
            curSelectedPosition = bloodSugarAdapter.data.indexOf(if (mBloodSugarRecord == null) defaultBloodSugar.toString() else mBloodSugarRecord?.mBloodSugarPre)
            //设置缩放比例
            setItemTransformer(ScaleTransformer())
            setOnItemSelectedListener(object :GalleryLayoutManager.OnItemSelectedListener{
                override fun onItemSelected(recyclerView: RecyclerView?, item: View?, position: Int) {
                    val bloodSugar = bloodSugarAdapter.data[position].toDouble()
                    setTextData(bloodSugar)
                    //设置不可滚动
                    isScrollEnabled = mBloodSugarRecord == null
                }
            })
        }
        bloodSugarManager.attach(mBindView.rvBloodSugar)
        mBindView.rvBloodSugar.adapter = bloodSugarAdapter

字体缩放样式

class ScaleTransformer : GalleryLayoutManager.ItemTransformer {
    override fun transformItem(layoutManager: GalleryLayoutManager?, item: View?, fraction: Float) {
        item!!.pivotX = item.width / 2f
        item.pivotY = item.height / 2f
        val scale = 1 - 0.3f * abs(fraction)
        item.scaleX = scale
        item.scaleY = scale
    }
}

适配器

inner class StepSelectAdapter : MyBaseAdapter<Int>(R.layout.item_step_select) {
        override fun convert(holder: BaseViewHolder, item: Int) {
            holder.setText(R.id.tv_number, "$item")
        }
    }

布局

<androidx.appcompat.widget.AppCompatTextView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/tv_number"
    android:layout_width="52dp"
    android:layout_height="52dp"
    android:gravity="center"
    android:textStyle="bold"
    android:includeFontPadding="false"
    android:background="@drawable/select_pressure"
    android:textColor="@color/pressure_text_color"
    android:textSize="23sp"
    tools:text="11" />

可实现效果如下
在这里插入图片描述在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

其中最后一个的背景shape代码如下

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_selected="true">
        <layer-list>
            <!-- 第一条直线 -->
            <item android:top="-43dp">
                <shape android:shape="line">
                    <stroke
                        android:width="1dp"
                        android:color="#DADADA" />
                </shape>
            </item>
            <!-- 第二条直线 -->
            <item android:top="43dp">
                <shape android:shape="line">
                    <stroke
                        android:width="1dp"
                        android:color="#DADADA" />
                </shape>
            </item>
        </layer-list>
    </item>
    <item android:state_selected="false">
        <shape>
            <solid android:color="@color/color_clear"/>
        </shape>
    </item>
</selector>

这里注意的是第一条线要想在顶部就要为负数 而且要减去线的高度 第二条也是
参考链接github

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值