您的位置:首页 > 汽车 > 新车 > 使用recyclerview实现选择器

使用recyclerview实现选择器

2024/9/23 13:17:17 来源:https://blog.csdn.net/CruelLei/article/details/140499532  浏览:    关键词:使用recyclerview实现选择器

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

class GalleryLayoutManager(orientation: Int) : RecyclerView.LayoutManager(),ScrollVectorProvider {private var mFirstVisiblePosition = 0private var mLastVisiblePos = 0private var mInitialSelectedPosition = 0var curSelectedPosition = -1var mCurSelectedView: View? = null/*** Scroll state*/private var mState: State? = nullprivate val mSnapHelper = LinearSnapHelper()private val mInnerScrollListener: InnerScrollListener = InnerScrollListener()private var mCallbackInFling = false/*** Current orientation. Either [.HORIZONTAL] or [.VERTICAL]*/var orientation = HORIZONTALvar isScrollEnabled = trueprivate var mHorizontalHelper: OrientationHelper? = nullprivate var mVerticalHelper: OrientationHelper? = nulloverride 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 positionif (curSelectedPosition != -1) {mInitialSelectedPosition = curSelectedPosition}mInitialSelectedPosition = Math.min(Math.max(0, mInitialSelectedPosition), itemCount - 1)mFirstVisiblePosition = mInitialSelectedPositionmLastVisiblePos = mInitialSelectedPositioncurSelectedPosition = -1if (mCurSelectedView != null) {mCurSelectedView!!.isSelected = falsemCurSelectedView = 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!!.startAfterPaddingval rightEdge = orientationHelper!!.endAfterPaddingval startPosition = mInitialSelectedPositionval scrapWidth: Intval scrapHeight: Intval scrapRect = Rect()val height = verticalSpaceval topOffset: Int//layout the init position viewval 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 + scrapHeightlayoutDecorated(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 = startPositionmFirstVisiblePosition = mLastVisiblePosval leftStartOffset = getDecoratedLeft(scrap)val rightStartOffset = getDecoratedRight(scrap)//fill left of centerfillLeft(recycler, mInitialSelectedPosition - 1, leftStartOffset, leftEdge)//fill right of centerfillRight(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!!.startAfterPaddingval bottomEdge = orientationHelper!!.endAfterPaddingval startPosition = mInitialSelectedPositionval scrapWidth: Intval scrapHeight: Intval scrapRect = Rect()val width = horizontalSpaceval leftOffset: Int//layout the init position viewval 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 + scrapHeightlayoutDecorated(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 = startPositionmFirstVisiblePosition = mLastVisiblePosval topStartOffset = getDecoratedTop(scrap)val bottomStartOffset = getDecoratedBottom(scrap)//fill left of centerfillTop(recycler, mInitialSelectedPosition - 1, topStartOffset, topEdge)//fill right of centerfillBottom(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 = startOffsetvar scrap: Viewvar topOffset: Intvar scrapWidth: Intvar scrapHeight: Intval scrapRect = Rect()val height = verticalSpacevar i = startPositionwhile (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 + scrapHeightlayoutDecorated(scrap, scrapRect.left, scrapRect.top, scrapRect.right, scrapRect.bottom)startOffset = scrapRect.leftmFirstVisiblePosition = iif (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 = startOffsetvar scrap: Viewvar topOffset: Intvar scrapWidth: Intvar scrapHeight: Intval scrapRect = Rect()val height = verticalSpacevar i = startPositionwhile (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 + scrapHeightlayoutDecorated(scrap, scrapRect.left, scrapRect.top, scrapRect.right, scrapRect.bottom)startOffset = scrapRect.rightmLastVisiblePos = iif (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 = startOffsetvar scrap: Viewvar leftOffset: Intvar scrapWidth: Intvar scrapHeight: Intval scrapRect = Rect()val width = horizontalSpacevar i = startPositionwhile (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] = startOffsetlayoutDecorated(scrap, scrapRect.left, scrapRect.top, scrapRect.right, scrapRect.bottom)startOffset = scrapRect.topmFirstVisiblePosition = iif (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 = startOffsetvar scrap: Viewvar leftOffset: Intvar scrapWidth: Intvar scrapHeight: Intval scrapRect = Rect()val width = horizontalSpacevar i = startPositionwhile (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 + scrapHeightlayoutDecorated(scrap, scrapRect.left, scrapRect.top, scrapRect.right, scrapRect.bottom)startOffset = scrapRect.bottommLastVisiblePos = iif (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!!.heightif (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 = orientationHelperval parentCenter =(orientationHelper!!.endAfterPadding - orientationHelper.startAfterPadding) / 2 + orientationHelper.startAfterPaddingreturn 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!!.startAfterPaddingval bottomEdge = orientationHelper!!.endAfterPadding//1.remove and recycle the view that disappear in screenvar child: View?if (childCount > 0) {if (dy >= 0) {//remove and recycle the top off screen viewvar fixIndex = 0for (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 viewfor (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 = mFirstVisiblePositionvar startOffset = -1var scrapWidth: Intvar scrapHeight: Intvar scrapRect: Rect?val width = horizontalSpacevar leftOffset: Intvar scrap: View//2.Add or reattach item view to fill screenif (dy >= 0) {if (childCount != 0) {val lastView = getChildAt(childCount - 1)startPosition = getPosition(lastView!!) + 1startOffset = getDecoratedBottom(lastView)}var i = startPositionwhile (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 centerval 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.bottommLastVisiblePos = iif (BuildConfig.DEBUG) {Log.d(TAG,"fillWithVertical: add view:$i,startOffset:$startOffset,mLastVisiblePos:$mLastVisiblePos,bottomEdge$bottomEdge")}i++}} else {//dy<0if (childCount > 0) {val firstView = getChildAt(0)startPosition = getPosition(firstView!!) - 1 //前一个View的positionstartOffset = getDecoratedTop(firstView)}var i = startPositionwhile (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] =startOffsetlayoutDecorated(scrap,scrapRect.left,scrapRect.top,scrapRect.right,scrapRect.bottom)startOffset = scrapRect.topmFirstVisiblePosition = ii--}}}/*** @param recycler* @param state*/private fun fillWithHorizontal(recycler: RecyclerView.Recycler,state: RecyclerView.State,dx: Int) {val leftEdge = orientationHelper!!.startAfterPaddingval rightEdge = orientationHelper!!.endAfterPaddingif (BuildConfig.DEBUG) {Log.v(TAG,"fillWithHorizontal() called with: dx = [$dx],leftEdge:$leftEdge,rightEdge:$rightEdge")}//1.remove and recycle the view that disappear in screenvar child: View?if (childCount > 0) {if (dx >= 0) {//remove and recycle the left off screen viewvar fixIndex = 0for (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 viewfor (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 screenvar startPosition = mFirstVisiblePositionvar startOffset = -1var scrapWidth: Intvar scrapHeight: Intvar scrapRect: Rect?val height = verticalSpacevar topOffset: Intvar scrap: Viewif (dx >= 0) {if (childCount != 0) {val lastView = getChildAt(childCount - 1)startPosition = getPosition(lastView!!) + 1 //start layout from next position itemstartOffset = getDecoratedRight(lastView)if (BuildConfig.DEBUG) {Log.d(TAG,"fillWithHorizontal:to right startPosition:$startPosition,startOffset:$startOffset,rightEdge:$rightEdge")}}var i = startPositionwhile (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 centerval 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.rightmLastVisiblePos = iif (BuildConfig.DEBUG) {Log.d(TAG,"fillWithHorizontal,layout:mLastVisiblePos: $mLastVisiblePos")}i++}} else {//dx<0if (childCount > 0) {val firstView = getChildAt(0)startPosition =getPosition(firstView!!) - 1 //start layout from previous position itemstartOffset = getDecoratedLeft(firstView)if (BuildConfig.DEBUG) {Log.d(TAG,"fillWithHorizontal:to left startPosition:$startPosition,startOffset:$startOffset,leftEdge:$leftEdge,child count:$childCount")}}var i = startPositionwhile (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 + scrapHeightlayoutDecorated(scrap,scrapRect.left,scrapRect.top,scrapRect.right,scrapRect.bottom)startOffset = scrapRect.leftmFirstVisiblePosition = ii--}}}private val horizontalSpace: Intprivate get() = width - paddingRight - paddingLeftprivate val verticalSpace: Intprivate get() = height - paddingBottom - paddingTopval state: State?get() {if (mState == null) {mState = State()}return mState}private fun calculateScrollDirectionForPosition(position: Int): Int {if (childCount == 0) {return LAYOUT_START}val firstChildPos = mFirstVisiblePositionreturn 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 = 0foutVector.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: Intinit {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 = -dxval parentCenter =(orientationHelper!!.endAfterPadding - orientationHelper!!.startAfterPadding) / 2 + orientationHelper!!.startAfterPaddingval child: View?if (dx > 0) {//If we've reached the last item, enforce limitsif (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 limitsif (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 = -deltafillCover(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 = -dyval parentCenter =(orientationHelper!!.endAfterPadding - orientationHelper!!.startAfterPadding) / 2 + orientationHelper!!.startAfterPaddingval child: View?if (dy > 0) {//If we've reached the last item, enforce limitsif (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 limitsif (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 = -deltafillCover(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? = nullfun 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? = nullfun setOnItemSelectedListener(onItemSelectedListener: OnItemSelectedListener?) {mOnItemSelectedListener = onItemSelectedListener}/*** @param recyclerView* @param selectedPosition*/@JvmOverloadsfun attach(recyclerView: RecyclerView?, selectedPosition: Int = -1) {requireNotNull(recyclerView) { "The attach RecycleView must not null!!" }mRecyclerView = recyclerViewmInitialSelectedPosition = Math.max(0, selectedPosition)recyclerView.layoutManager = thismSnapHelper.attachToRecyclerView(recyclerView)recyclerView.addOnScrollListener(mInnerScrollListener)}var mRecyclerView: RecyclerView? = nullinit {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 = 0var mCallbackOnIdle = falseoverride 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 = snapmCurSelectedView!!.isSelected = truecurSelectedPosition = selectedPositionif (!mCallbackInFling && mState != RecyclerView.SCROLL_STATE_IDLE) {if (BuildConfig.DEBUG) {Log.v(TAG, "ignore selection change callback when fling ")}mCallbackOnIdle = truereturn}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 = newStateif (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 = snapmCurSelectedView!!.isSelected = truecurSelectedPosition = selectedPositionif (mOnItemSelectedListener != null) {mOnItemSelectedListener!!.onItemSelected(recyclerView, snap,curSelectedPosition)}} else if (!mCallbackInFling && mOnItemSelectedListener != null && mCallbackOnIdle) {mCallbackOnIdle = falsemOnItemSelectedListener!!.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 = positionstartSmoothScroll(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 = layoutManagerif (layoutManager == null || !layoutManager.canScrollHorizontally()) {return 0}val params = view.layoutParams as RecyclerView.LayoutParamsval left = layoutManager.getDecoratedLeft(view) - params.leftMarginval right = layoutManager.getDecoratedRight(view) + params.rightMarginval start = layoutManager.paddingLeftval end = layoutManager.width - layoutManager.paddingRightval 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 = layoutManagerif (layoutManager == null || !layoutManager.canScrollVertically()) {return 0}val params = view.layoutParams as RecyclerView.LayoutParamsval top = layoutManager.getDecoratedTop(view) - params.topMarginval bottom = layoutManager.getDecoratedBottom(view) + params.bottomMarginval start = layoutManager.paddingTopval end = layoutManager.height - layoutManager.paddingBottomval 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 = -1const val LAYOUT_END = 1const val HORIZONTAL = OrientationHelper.HORIZONTALconst 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 / 2fitem.pivotY = item.height / 2fval scale = 1 - 0.3f * abs(fraction)item.scaleX = scaleitem.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"><strokeandroid:width="1dp"android:color="#DADADA" /></shape></item><!-- 第二条直线 --><item android:top="43dp"><shape android:shape="line"><strokeandroid: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

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com