业务逻辑
-
- 计算当前屏幕可见范围内中心
ItemView
的位置
-
- 计算中心
ItemView
和RecyclerView
中心之间的距离,只用于边界处理,例如中心ItemView
位置和目标ItemView
回滚位置相同,中断回滚逻辑处理
-
- (需要回滚
ItemView
的位置-
中心ItemView
的位置) *
(ItemView
的宽度 +
ItemDecoration
的宽度 *
2)
-
- 解耦合,将
RecyclerView
的特殊滑动处理交给ItemDecoration
处理
XML
文件
ItemView
的XML
文件R.layout.shape_item_view
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@drawable/shape_item_view">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textSize="16sp" />
</FrameLayout>
- 滑动到对齐
ItemView
的XML
文件 R.drawable.shape_item_view_selected
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#FFFFFF" />
<corners android:radius="8dp" />
<stroke android:color="#FFFF00" android:width="5dp" />
</shape>
- 未滑动到对齐
ItemView
的XML
文件 R.drawable.shape_item_view
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#FFFFFF" />
<corners android:radius="8dp" />
<stroke android:color="#000000" android:width="5dp" />
</shape>
Activity
的XML文件R.layout.activity_main
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<Button
android:id="@+id/reset_btn"
android:text="重置"
android:textSize="20sp"
android:layout_width="100dp"
android:layout_height="50dp">
</Button>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginLeft="@dimen/edit_crop_frame_padding"
android:layout_marginRight="@dimen/edit_crop_frame_padding"
android:orientation="horizontal" />
</LinearLayout>
RecyclerView
代码
class MyAdapter(private val numbers: List<Int>) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
private var selectedPosition = RecyclerView.NO_POSITION
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_view, parent, false)
return MyViewHolder(view)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.textView.text = numbers[position].toString()
if (selectedPosition == position) {
holder.itemView.setBackgroundResource(R.drawable.shape_item_view_selected)
} else {
holder.itemView.setBackgroundResource(R.drawable.shape_item_view)
}
}
override fun getItemCount() = numbers.size
fun setSelectedPosition(position: Int) {
val oldPosition = selectedPosition
selectedPosition = position
notifyItemChanged(oldPosition)
notifyItemChanged(selectedPosition)
}
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val textView: TextView = itemView.findViewById(R.id.textView)
}
}
open class HorizontalCenterItemDecoration(private val spaceSize: Int, private val itemSize: Int) : RecyclerView.ItemDecoration() {
private val mSnapHelper = LinearSnapHelper()
private var spacePadding = 0
open fun scrollToPosition(parent: RecyclerView, targetPosition : Int, needSmooth : Boolean) {
val centerItemViewPosition = Math.max(0, findCenterItemViewPosition(parent))
val distance = getDistanceToCenter(parent)
if(distance == 0 && centerItemViewPosition == targetPosition) return
var offsetX = (targetPosition - centerItemViewPosition) * (itemSize + spaceSize * 2)
if(needSmooth) {
parent.smoothScrollBy(offsetX, 0)
}else{
parent.scrollBy(offsetX, 0)
}
}
fun findCenterItemView(parent: RecyclerView): View? {
var centerItemView = parent.findChildViewUnder(parent.measuredWidth / 2f, parent.measuredHeight / 2f)
if (centerItemView == null) {
centerItemView = parent.findChildViewUnder(parent.measuredWidth / 2f + spaceSize / 2, parent.measuredHeight / 2f )
}
if (centerItemView == null) {
centerItemView = parent.findChildViewUnder(parent.measuredWidth / 2f - spaceSize / 2, parent.measuredHeight / 2f )
}
if (centerItemView == null) {
centerItemView = parent.findChildViewUnder(parent.measuredWidth / 2f + spaceSize , parent.measuredHeight / 2f )
}
if (centerItemView == null) {
centerItemView = parent.findChildViewUnder(parent.measuredWidth / 2f - spaceSize , parent.measuredHeight / 2f )
}
return centerItemView
}
fun findCenterItemViewPosition(parent: RecyclerView) : Int {
val centerItemView = findCenterItemView(parent)
return centerItemView?.let { parent.getChildAdapterPosition(it) } ?: -1
}
fun getDistanceToCenter(parent: RecyclerView): Int {
val centerItemView = findCenterItemView(parent)
val centerItemViewPosition = findCenterItemViewPosition(parent)
var distance = mSnapHelper.calculateDistanceToFinalSnap(parent.layoutManager!!, centerItemView!!)?.get(0)
if( centerItemViewPosition == 0 || centerItemViewPosition == parent.adapter!!.itemCount - 1) {
distance = 0
}
return distance ?: 0
}
private val paint = Paint().apply {
color = Color.RED
style = Paint.Style.FILL
}
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
super.getItemOffsets(outRect, view, parent, state)
outRect.left = spaceSize
outRect.right = spaceSize
spacePadding = (parent.measuredWidth / 2 - itemSize / 2)
val size = parent.adapter?.itemCount ?: 0
val position = parent.getChildAdapterPosition(view)
if (position == 0) {
outRect.left = spacePadding
} else if (position == size - 1) {
outRect.right = spacePadding
}
}
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDraw(c, parent, state)
val childCount = parent.childCount
for (i in 0 until childCount) {
val child = parent.getChildAt(i)
val position = parent.getChildAdapterPosition(child)
val params = child.layoutParams as RecyclerView.LayoutParams
var left : Int
var right : Int
var top : Int
var bottom : Int
if (position == 0) {
left = child.left - params.leftMargin - spacePadding
right = child.right + params.rightMargin + spaceSize
top = child.top - params.topMargin
bottom = child.bottom + params.bottomMargin
} else if (position == parent.adapter?.itemCount!! - 1) {
left = child.left - params.leftMargin - spaceSize
right = child.right + params.rightMargin + spacePadding
top = child.top - params.topMargin
bottom = child.bottom + params.bottomMargin
} else {
left = child.left - params.leftMargin - spaceSize
right = child.right + params.rightMargin + spaceSize
top = child.top - params.topMargin
bottom = child.bottom + params.bottomMargin
}
c.drawRect(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat(), paint)
}
}
}
RecyclerView
对齐工具类SnapHelper
实现类代码
- ①
attachToRecyclerView()
方法:将RecyclerView
对齐操作交给SnapHelper
实现类
override fun attachToRecyclerView(recyclerView: RecyclerView?) {
Log.i(TAG, "attachToRecyclerView")
mRecyclerView = recyclerView
super.attachToRecyclerView(recyclerView)
}
- ②
createScroller()
方法:创建惯性滑动的Scroller
onTargetFound()
回调方法:找到对齐位置之后回调,计算目标位置到对齐位置需要滚动的距离和时间calculateSpeedPerPixel()
回调方法:滚动一英寸所需时间除以屏幕密度,得到滚动一像素所需的时间
override fun createScroller(layoutManager: RecyclerView.LayoutManager?): RecyclerView.SmoothScroller? {
if (layoutManager !is RecyclerView.SmoothScroller.ScrollVectorProvider) {
return null
}
Log.i(TAG, "createScroller")
return object : LinearSmoothScroller(mRecyclerView?.context) {
override fun onTargetFound(targetView: View, state: RecyclerView.State, action: Action) {
if (mRecyclerView == null) return
Log.i(TAG, "onTargetFound")
val snapDistances : IntArray? = calculateDistanceToFinalSnap(mRecyclerView?.layoutManager!!, targetView)
val dx = snapDistances?.get(0) ?: 0
val dy = snapDistances?.get(1) ?: 0
val time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy))) * 10
if (time > 0) {
action.update(dx, dy, time, mInterpolator)
}
}
override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics?): Float {
Log.i(TAG, "calculateSpeedPerPixel")
return MILLISECONDS_PER_INCH / displayMetrics?.densityDpi!!
}
}
}
- ③
findTargetSnapPosition()
方法:找到需要对齐的ItemView
的位置
RecyclerView.SmoothScroller.ScrollVectorProvider.computeScrollVectorForPosition()
:计算从0
的位置滚动到ItemCount-1
的位置需要滚动的方向,vectorForEnd.x
表示水平方向(>0
向右,<0
向左),vectorForEnd.y
表示竖直方向(>0
向下,<0
向上),正负值由结束位置和开始位置的差值得出
override fun findTargetSnapPosition(layoutManager: RecyclerView.LayoutManager?, velocityX: Int, velocityY: Int): Int {
if (layoutManager !is RecyclerView.SmoothScroller.ScrollVectorProvider) return RecyclerView.NO_POSITION
val itemCount = layoutManager.itemCount
if (itemCount == 0) return RecyclerView.NO_POSITION
val currentView = findSnapView(layoutManager) ?: return RecyclerView.NO_POSITION
val currentPosition = layoutManager.getPosition(currentView)
if (currentPosition == RecyclerView.NO_POSITION) return RecyclerView.NO_POSITION
val vectorProvider = layoutManager as RecyclerView.SmoothScroller.ScrollVectorProvider
val vectorForEnd = vectorProvider.computeScrollVectorForPosition(itemCount - 1) ?: return RecyclerView.NO_POSITION
Log.i(TAG, "findTargetSnapPosition")
var maxHorizontalItemViewCount: Int
var maxVerticalItemViewCount: Int
if (layoutManager.canScrollHorizontally()) {
maxHorizontalItemViewCount = estimateNextPositionDiffForFling(layoutManager, getHorizontalHelper(layoutManager), velocityX, 0)
var sign = Math.signum(velocityX.toFloat())
if (sign == 0f) sign = 1f
maxHorizontalItemViewCount = (sign * Math.min(Math.max(Math.abs(maxHorizontalItemViewCount), 0), 2)).toInt()
if (vectorForEnd.x < 0) {
maxHorizontalItemViewCount = - maxHorizontalItemViewCount
}
}else{
maxHorizontalItemViewCount = 0
}
if (layoutManager.canScrollVertically()) {
maxVerticalItemViewCount = estimateNextPositionDiffForFling(layoutManager, getVerticalHelper(layoutManager), 0, velocityY)
var sign = Math.signum(velocityY.toFloat())
if (sign == 0f) sign = 1f
maxVerticalItemViewCount = (sign * Math.min(Math.max(Math.abs(maxVerticalItemViewCount), 0), 2)).toInt()
if (vectorForEnd.y < 0) {
maxVerticalItemViewCount = - maxVerticalItemViewCount
}
}else{
maxVerticalItemViewCount = 0
}
val finalItemCount = if(layoutManager.canScrollHorizontally()){
maxHorizontalItemViewCount
}else{
maxVerticalItemViewCount
}
if (finalItemCount == 0) return RecyclerView.NO_POSITION
var targetPosition = currentPosition + finalItemCount
if (targetPosition < 0) targetPosition = 0
if (targetPosition >= layoutManager.itemCount) targetPosition = layoutManager.itemCount - 1
return targetPosition
}
- ④
findSnapView()
方法:调用findCenterView()
找到最接近中心点的ItemView
findCenterView()
方法:拿到每个ItemView
的left
加上自身宽度的一半和RecyclerView
的中心点进行比较,找到最接近中心点的ItemView
override fun findSnapView(layoutManager: RecyclerView.LayoutManager?): View? {
Log.i(TAG, "findSnapView")
if (layoutManager!!.canScrollVertically()) {
return findCenterView(layoutManager, getVerticalHelper(layoutManager))
} else if (layoutManager.canScrollHorizontally()) {
return findCenterView(layoutManager, getHorizontalHelper(layoutManager))
}
return null
}
private fun findCenterView(layoutManager: RecyclerView.LayoutManager, helper: OrientationHelper): View? {
Log.i(TAG, "findCenterView")
val childCount = layoutManager.childCount
if (childCount == 0) return null
var closestItemView: View? = null
val center = helper.startAfterPadding + helper.totalSpace / 2
var absClosest = Int.MAX_VALUE
for (i in 0 until childCount) {
val child = layoutManager.getChildAt(i)
val childCenter = child?.left!! + helper.getDecoratedMeasurement(child) / 2
val childDistance = Math.abs(childCenter - center)
if (childDistance < absClosest) {
absClosest = childDistance
closestItemView = child
}
}
return closestItemView
}
- ⑤
estimateNextPositionDiffForFling()
方法:计算当前位置到目标对齐位置还差了几个ItemView
的个数
calculateScrollDistance()
:计算RecyclerView
的滚动距离computeDistancePerChild()
:计算每个ItemView
的滚动距离
private fun estimateNextPositionDiffForFling(layoutManager: RecyclerView.LayoutManager, helper: OrientationHelper, velocityX: Int, velocityY: Int): Int {
Log.i(TAG, "estimateNextPositionDiffForFling")
val distances = calculateScrollDistance(velocityX, velocityY)
val distancePerChild = computeDistancePerChild(layoutManager, helper)
if (distancePerChild <= 0) return 0
val distance = if (Math.abs(distances[0]) > Math.abs(distances[1])) distances[0] else distances[1]
return Math.round(distance / distancePerChild)
}
override fun calculateScrollDistance(velocityX: Int, velocityY: Int): IntArray {
Log.i(TAG, "calculateScrollDistance")
return super.calculateScrollDistance(velocityX, velocityY)
}
private fun computeDistancePerChild(layoutManager: RecyclerView.LayoutManager, helper: OrientationHelper): Float {
Log.i(TAG, "computeDistancePerChild")
var minPositionView : View ?= null
var maxPositionView : View ?= null
var minPosition = Integer.MAX_VALUE
var maxPosition = Integer.MIN_VALUE
val itemViewCount = layoutManager.childCount
if (itemViewCount == 0) return INVALID_DISTANCE
for (i in 0 until itemViewCount) {
val child = layoutManager.getChildAt(i) ?: continue
val position = layoutManager.getPosition(child)
if (position == RecyclerView.NO_POSITION) continue
if (position < minPosition) {
minPosition = position
minPositionView = child
}
if (position > maxPosition) {
maxPosition = position
maxPositionView = child
}
}
if (minPositionView == null || maxPositionView == null) return INVALID_DISTANCE
val start = Math.min(helper.getDecoratedStart(minPositionView), helper.getDecoratedStart(maxPositionView))
val end = Math.max(helper.getDecoratedEnd(minPositionView), helper.getDecoratedEnd(maxPositionView))
val distance = end - start
if (distance <= 0) return INVALID_DISTANCE
return 1f * distance / (maxPosition - minPosition + 1)
}
- ⑥
onTargetFound()
方法:找对对齐ItemView
位置后回调
calculateDistanceToFinalSnap()
:计算最终需要滚动到对齐ItemView
位置的距离calculateTimeForDeceleration()
:计算最终需要滚动到对齐ItemView
位置所花时间
override fun onTargetFound(targetView: View, state: RecyclerView.State, action: Action) {
if (mRecyclerView == null) return
Log.i(TAG, "onTargetFound")
val snapDistances : IntArray? = calculateDistanceToFinalSnap(mRecyclerView?.layoutManager!!, targetView)
val dx = snapDistances?.get(0) ?: 0
val dy = snapDistances?.get(1) ?: 0
val time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy))) * 10
if (time > 0) {
action.update(dx, dy, time, mInterpolator)
}
}
override fun calculateDistanceToFinalSnap(layoutManager: RecyclerView.LayoutManager, targetView: View): IntArray? {
Log.i(TAG, "calculateDistanceToFinalSnap")
val out = IntArray(2)
if (layoutManager.canScrollHorizontally()) {
out[0] = distanceToCenter(targetView, getHorizontalHelper(layoutManager)!!)
} else {
out[0] = 0
}
if (layoutManager.canScrollVertically()) {
out[1] = distanceToCenter(targetView, getVerticalHelper(layoutManager)!!)
} else {
out[1] = 0
}
return out
}
- ⑦
distanceToCenter()
方法:计算目标对齐ItemView
距离RecyclerView
中心点的距离
private fun distanceToCenter(targetView: View, helper: OrientationHelper): Int {
Log.i(TAG, "distanceToCenter")
val childCenter = helper.getDecoratedStart(targetView) + helper.getDecoratedMeasurement(targetView) / 2
val containerCenter = helper.startAfterPadding + helper.totalSpace / 2
return childCenter - containerCenter
}
open class MySmoothSnapHelper : SnapHelper() {
private val INVALID_DISTANCE = 1f
private val MILLISECONDS_PER_INCH = 25f
private var mVerticalHelper : OrientationHelper ?= null
private var mHorizontalHelper : OrientationHelper ?= null
private var mRecyclerView : RecyclerView ?= null
private val mInterpolator = LinearOutSlowInInterpolator()
override fun attachToRecyclerView(recyclerView: RecyclerView?) {
Log.i(TAG, "attachToRecyclerView")
mRecyclerView = recyclerView
super.attachToRecyclerView(recyclerView)
}
override fun createScroller(layoutManager: RecyclerView.LayoutManager?): RecyclerView.SmoothScroller? {
Log.i(TAG, "createScroller")
if (layoutManager !is RecyclerView.SmoothScroller.ScrollVectorProvider) {
return null
}
return object : LinearSmoothScroller(mRecyclerView?.context) {
override fun onTargetFound(targetView: View, state: RecyclerView.State, action: Action) {
if (mRecyclerView == null) return
Log.i(TAG, "onTargetFound")
val snapDistances : IntArray? = calculateDistanceToFinalSnap(mRecyclerView?.layoutManager!!, targetView)
val dx = snapDistances?.get(0) ?: 0
val dy = snapDistances?.get(1) ?: 0
val time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy))) * 10
if (time > 0) {
action.update(dx, dy, time, mInterpolator)
}
}
override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics?): Float {
Log.i(TAG, "calculateSpeedPerPixel")
return MILLISECONDS_PER_INCH / displayMetrics?.densityDpi!!
}
}
}
override fun calculateDistanceToFinalSnap(layoutManager: RecyclerView.LayoutManager, targetView: View): IntArray? {
Log.i(TAG, "calculateDistanceToFinalSnap")
val out = IntArray(2)
if (layoutManager.canScrollHorizontally()) {
out[0] = distanceToCenter(targetView, getHorizontalHelper(layoutManager)!!)
} else {
out[0] = 0
}
if (layoutManager.canScrollVertically()) {
out[1] = distanceToCenter(targetView, getVerticalHelper(layoutManager)!!)
} else {
out[1] = 0
}
return out
}
private fun distanceToCenter(targetView: View, helper: OrientationHelper): Int {
Log.i(TAG, "distanceToCenter")
val childCenter = helper.getDecoratedStart(targetView) + helper.getDecoratedMeasurement(targetView) / 2
val containerCenter = helper.startAfterPadding + helper.totalSpace / 2
return childCenter - containerCenter
}
override fun findSnapView(layoutManager: RecyclerView.LayoutManager?): View? {
Log.i(TAG, "findSnapView")
if (layoutManager!!.canScrollVertically()) {
return findCenterView(layoutManager, getVerticalHelper(layoutManager))
} else if (layoutManager.canScrollHorizontally()) {
return findCenterView(layoutManager, getHorizontalHelper(layoutManager))
}
return null
}
private fun findCenterView(layoutManager: RecyclerView.LayoutManager, helper: OrientationHelper): View? {
Log.i(TAG, "findCenterView")
val childCount = layoutManager.childCount
if (childCount == 0) return null
var closestItemView: View? = null
val center = helper.startAfterPadding + helper.totalSpace / 2
var absClosest = Int.MAX_VALUE
for (i in 0 until childCount) {
val child = layoutManager.getChildAt(i)
val childCenter = child?.left!! + helper.getDecoratedMeasurement(child) / 2
val childDistance = Math.abs(childCenter - center)
if (childDistance < absClosest) {
absClosest = childDistance
closestItemView = child
}
}
return closestItemView
}
override fun findTargetSnapPosition(layoutManager: RecyclerView.LayoutManager?, velocityX: Int, velocityY: Int): Int {
Log.i(TAG, "findTargetSnapPosition")
if (layoutManager !is RecyclerView.SmoothScroller.ScrollVectorProvider) return RecyclerView.NO_POSITION
val itemCount = layoutManager.itemCount
if (itemCount == 0) return RecyclerView.NO_POSITION
val currentView = findSnapView(layoutManager) ?: return RecyclerView.NO_POSITION
val currentPosition = layoutManager.getPosition(currentView)
if (currentPosition == RecyclerView.NO_POSITION) return RecyclerView.NO_POSITION
val vectorProvider = layoutManager as RecyclerView.SmoothScroller.ScrollVectorProvider
val vectorForEnd = vectorProvider.computeScrollVectorForPosition(itemCount - 1) ?: return RecyclerView.NO_POSITION
var maxHorizontalItemViewCount: Int
var maxVerticalItemViewCount: Int
if (layoutManager.canScrollHorizontally()) {
maxHorizontalItemViewCount = estimateNextPositionDiffForFling(layoutManager, getHorizontalHelper(layoutManager), velocityX, 0)
var sign = Math.signum(velocityX.toFloat())
if (sign == 0f) sign = 1f
maxHorizontalItemViewCount = (sign * Math.min(Math.max(Math.abs(maxHorizontalItemViewCount), 0), 2)).toInt()
if (vectorForEnd.x < 0) {
maxHorizontalItemViewCount = - maxHorizontalItemViewCount
}
}else{
maxHorizontalItemViewCount = 0
}
if (layoutManager.canScrollVertically()) {
maxVerticalItemViewCount = estimateNextPositionDiffForFling(layoutManager, getVerticalHelper(layoutManager), 0, velocityY)
var sign = Math.signum(velocityY.toFloat())
if (sign == 0f) sign = 1f
maxVerticalItemViewCount = (sign * Math.min(Math.max(Math.abs(maxVerticalItemViewCount), 0), 2)).toInt()
if (vectorForEnd.y < 0) {
maxVerticalItemViewCount = - maxVerticalItemViewCount
}
}else{
maxVerticalItemViewCount = 0
}
val finalItemCount = if(layoutManager.canScrollHorizontally()){
maxHorizontalItemViewCount
}else{
maxVerticalItemViewCount
}
if (finalItemCount == 0) return RecyclerView.NO_POSITION
var targetPosition = currentPosition + finalItemCount
if (targetPosition < 0) targetPosition = 0
if (targetPosition >= layoutManager.itemCount) targetPosition = layoutManager.itemCount - 1
return targetPosition
}
override fun calculateScrollDistance(velocityX: Int, velocityY: Int): IntArray {
Log.i(TAG, "calculateScrollDistance")
return super.calculateScrollDistance(velocityX, velocityY)
}
private fun estimateNextPositionDiffForFling(layoutManager: RecyclerView.LayoutManager, helper: OrientationHelper, velocityX: Int, velocityY: Int): Int {
Log.i(TAG, "estimateNextPositionDiffForFling")
val distances = calculateScrollDistance(velocityX, velocityY)
val distancePerChild = computeDistancePerChild(layoutManager, helper)
if (distancePerChild <= 0) return 0
val distance = if (Math.abs(distances[0]) > Math.abs(distances[1])) distances[0] else distances[1]
return Math.round(distance / distancePerChild)
}
private fun computeDistancePerChild(layoutManager: RecyclerView.LayoutManager, helper: OrientationHelper): Float {
Log.i(TAG, "computeDistancePerChild")
var minPositionView : View ?= null
var maxPositionView : View ?= null
var minPosition = Integer.MAX_VALUE
var maxPosition = Integer.MIN_VALUE
val itemViewCount = layoutManager.childCount
if (itemViewCount == 0) return INVALID_DISTANCE
for (i in 0 until itemViewCount) {
val child = layoutManager.getChildAt(i) ?: continue
val position = layoutManager.getPosition(child)
if (position == RecyclerView.NO_POSITION) continue
if (position < minPosition) {
minPosition = position
minPositionView = child
}
if (position > maxPosition) {
maxPosition = position
maxPositionView = child
}
}
if (minPositionView == null || maxPositionView == null) return INVALID_DISTANCE
val start = Math.min(helper.getDecoratedStart(minPositionView), helper.getDecoratedStart(maxPositionView))
val end = Math.max(helper.getDecoratedEnd(minPositionView), helper.getDecoratedEnd(maxPositionView))
val distance = end - start
if (distance <= 0) return INVALID_DISTANCE
return 1f * distance / (maxPosition - minPosition + 1)
}
private fun getVerticalHelper(layoutManager: RecyclerView.LayoutManager): OrientationHelper {
Log.i(TAG, "getVerticalHelper")
if (mVerticalHelper == null || mVerticalHelper?.layoutManager != layoutManager) {
mVerticalHelper = OrientationHelper.createVerticalHelper(layoutManager)
}
return mVerticalHelper!!
}
private fun getHorizontalHelper(layoutManager: RecyclerView.LayoutManager): OrientationHelper {
Log.i(TAG, "getHorizontalHelper")
if (mHorizontalHelper == null || mHorizontalHelper!!.layoutManager != layoutManager) {
mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager)
}
return mHorizontalHelper!!
}
}
Activity
代码
const val TAG = "Yang"
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val numberList = List(10){it}
val layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
val mRv = findViewById<RecyclerView>(R.id.recyclerView)
val mAdapter = MyAdapter(numberList)
val mResetBtn = findViewById<Button>(R.id.reset_btn)
val itemDecoration = HorizontalCenterItemDecoration(dpToPx(this, 25f), dpToPx(this, 100f))
val linearSnapHelper = object : MySmoothSnapHelper() {
override fun findSnapView(layoutManager: RecyclerView.LayoutManager?): View? {
val snapView = super.findSnapView(layoutManager)
val snapPosition = snapView?.let {mRv.getChildAdapterPosition(it) }
snapPosition?.let {
if (snapPosition != RecyclerView.NO_POSITION) {
mAdapter.setSelectedPosition(snapPosition)
}
}
return snapView
}
}
linearSnapHelper.attachToRecyclerView(mRv)
mRv.addItemDecoration(itemDecoration)
mRv?.layoutManager = layoutManager
mRv?.adapter = mAdapter
mResetBtn.setOnClickListener {
itemDecoration.scrollToPosition(mRv, 0, true)
}
}
fun dpToPx(context: Context, dp: Float): Int {
val metrics = context.resources.displayMetrics
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, metrics).toInt()
}
}
效果图
