Android RecyclerView SnapHelper无法显示第一个/最后一个项目
我有一个Android RecyclerView SnapHelper无法显示第一个/最后一个项目,android,android-recyclerview,kotlin,Android,Android Recyclerview,Kotlin,我有一个RecyclerView,它连接到一个LinearSnapHelper以捕捉到中心项目。当我滚动到第一个或最后一个项目时,这些项目不再完全可见。下图显示了此问题。如何解决 当紧挨着第一个/最后一个的项目中心靠近容器中心时,会发生此问题。因此,我们应该对快照功能进行一些更改,以忽略这种情况。由于我们需要LinearSnapHelper类中的一些字段,我们可以复制其源代码,并对findCenterView方法进行如下更改: MyLinearSnapHelper.kt /* * Copyri
RecyclerView
,它连接到一个LinearSnapHelper
以捕捉到中心项目。当我滚动到第一个或最后一个项目时,这些项目不再完全可见。下图显示了此问题。如何解决
当紧挨着第一个/最后一个的项目中心靠近容器中心时,会发生此问题。因此,我们应该对快照功能进行一些更改,以忽略这种情况。由于我们需要
LinearSnapHelper
类中的一些字段,我们可以复制其源代码,并对findCenterView
方法进行如下更改:
MyLinearSnapHelper.kt
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.aminography.view.component
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.OrientationHelper
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.SnapHelper
import android.view.View
/**
* Implementation of the [SnapHelper] supporting snapping in either vertical or horizontal
* orientation.
*
*
* The implementation will snap the center of the target child view to the center of
* the attached [RecyclerView]. If you intend to change this behavior then override
* [SnapHelper.calculateDistanceToFinalSnap].
*/
class MyLinearSnapHelper : SnapHelper() {
// Orientation helpers are lazily created per LayoutManager.
private var mVerticalHelper: OrientationHelper? = null
private var mHorizontalHelper: OrientationHelper? = null
override fun calculateDistanceToFinalSnap(
layoutManager: RecyclerView.LayoutManager, targetView: View): IntArray? {
val out = IntArray(2)
if (layoutManager.canScrollHorizontally()) {
out[0] = distanceToCenter(layoutManager, targetView,
getHorizontalHelper(layoutManager))
} else {
out[0] = 0
}
if (layoutManager.canScrollVertically()) {
out[1] = distanceToCenter(layoutManager, targetView,
getVerticalHelper(layoutManager))
} else {
out[1] = 0
}
return out
}
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
// deltaJumps sign comes from the velocity which may not match the order of children in
// the LayoutManager. To overcome this, we ask for a vector from the LayoutManager to
// get the direction.
val vectorForEnd = vectorProvider.computeScrollVectorForPosition(itemCount - 1)
?: // cannot get a vector for the given position.
return RecyclerView.NO_POSITION
var vDeltaJump: Int
var hDeltaJump: Int
if (layoutManager.canScrollHorizontally()) {
hDeltaJump = estimateNextPositionDiffForFling(layoutManager,
getHorizontalHelper(layoutManager), velocityX, 0)
if (vectorForEnd.x < 0) {
hDeltaJump = -hDeltaJump
}
} else {
hDeltaJump = 0
}
if (layoutManager.canScrollVertically()) {
vDeltaJump = estimateNextPositionDiffForFling(layoutManager,
getVerticalHelper(layoutManager), 0, velocityY)
if (vectorForEnd.y < 0) {
vDeltaJump = -vDeltaJump
}
} else {
vDeltaJump = 0
}
val deltaJump = if (layoutManager.canScrollVertically()) vDeltaJump else hDeltaJump
if (deltaJump == 0) {
return RecyclerView.NO_POSITION
}
var targetPos = currentPosition + deltaJump
if (targetPos < 0) {
targetPos = 0
}
if (targetPos >= itemCount) {
targetPos = itemCount - 1
}
return targetPos
}
override fun findSnapView(layoutManager: RecyclerView.LayoutManager): View? {
if (layoutManager.canScrollVertically()) {
return findCenterView(layoutManager, getVerticalHelper(layoutManager))
} else if (layoutManager.canScrollHorizontally()) {
return findCenterView(layoutManager, getHorizontalHelper(layoutManager))
}
return null
}
private fun distanceToCenter(layoutManager: RecyclerView.LayoutManager,
targetView: View, helper: OrientationHelper): Int {
val childCenter = helper.getDecoratedStart(targetView) + helper.getDecoratedMeasurement(targetView) / 2
val containerCenter: Int = if (layoutManager.clipToPadding) {
helper.startAfterPadding + helper.totalSpace / 2
} else {
helper.end / 2
}
return childCenter - containerCenter
}
/**
* Estimates a position to which SnapHelper will try to scroll to in response to a fling.
*
* @param layoutManager The [RecyclerView.LayoutManager] associated with the attached
* [RecyclerView].
* @param helper The [OrientationHelper] that is created from the LayoutManager.
* @param velocityX The velocity on the x axis.
* @param velocityY The velocity on the y axis.
*
* @return The diff between the target scroll position and the current position.
*/
private fun estimateNextPositionDiffForFling(layoutManager: RecyclerView.LayoutManager,
helper: OrientationHelper, velocityX: Int, velocityY: Int): Int {
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)
}
/**
* Return the child view that is currently closest to the center of this parent.
*
* @param layoutManager The [RecyclerView.LayoutManager] associated with the attached
* [RecyclerView].
* @param helper The relevant [OrientationHelper] for the attached [RecyclerView].
*
* @return the child view that is currently closest to the center of this parent.
*/
private fun findCenterView(layoutManager: RecyclerView.LayoutManager,
helper: OrientationHelper): View? {
// ----- Added by aminography
if (layoutManager is LinearLayoutManager) {
if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0) {
return layoutManager.getChildAt(0)
} else if (layoutManager.findLastCompletelyVisibleItemPosition() == layoutManager.itemCount - 1) {
return layoutManager.getChildAt(layoutManager.itemCount - 1)
}
}
// -----
val childCount = layoutManager.childCount
if (childCount == 0) {
return null
}
var closestChild: View? = null
val center: Int = if (layoutManager.clipToPadding) {
helper.startAfterPadding + helper.totalSpace / 2
} else {
helper.end / 2
}
var absClosest = Integer.MAX_VALUE
for (i in 0 until childCount) {
val child = layoutManager.getChildAt(i)
val childCenter = helper.getDecoratedStart(child) + helper.getDecoratedMeasurement(child) / 2
val absDistance = Math.abs(childCenter - center)
/** if child center is closer than previous closest, set it as closest */
if (absDistance < absClosest) {
absClosest = absDistance
closestChild = child
}
}
return closestChild
}
/**
* Computes an average pixel value to pass a single child.
*
*
* Returns a negative value if it cannot be calculated.
*
* @param layoutManager The [RecyclerView.LayoutManager] associated with the attached
* [RecyclerView].
* @param helper The relevant [OrientationHelper] for the attached
* [RecyclerView.LayoutManager].
*
* @return A float value that is the average number of pixels needed to scroll by one view in
* the relevant direction.
*/
private fun computeDistancePerChild(layoutManager: RecyclerView.LayoutManager,
helper: OrientationHelper): Float {
var minPosView: View? = null
var maxPosView: View? = null
var minPos = Integer.MAX_VALUE
var maxPos = Integer.MIN_VALUE
val childCount = layoutManager.childCount
if (childCount == 0) {
return INVALID_DISTANCE
}
for (i in 0 until childCount) {
val child = layoutManager.getChildAt(i)
val pos = layoutManager.getPosition(child!!)
if (pos == RecyclerView.NO_POSITION) {
continue
}
if (pos < minPos) {
minPos = pos
minPosView = child
}
if (pos > maxPos) {
maxPos = pos
maxPosView = child
}
}
if (minPosView == null || maxPosView == null) {
return INVALID_DISTANCE
}
val start = Math.min(helper.getDecoratedStart(minPosView),
helper.getDecoratedStart(maxPosView))
val end = Math.max(helper.getDecoratedEnd(minPosView),
helper.getDecoratedEnd(maxPosView))
val distance = end - start
return if (distance == 0) {
INVALID_DISTANCE
} else 1f * distance / (maxPos - minPos + 1)
}
private fun getVerticalHelper(layoutManager: RecyclerView.LayoutManager): OrientationHelper {
if (mVerticalHelper == null || mVerticalHelper!!.layoutManager !== layoutManager) {
mVerticalHelper = OrientationHelper.createVerticalHelper(layoutManager)
}
return mVerticalHelper!!
}
private fun getHorizontalHelper(
layoutManager: RecyclerView.LayoutManager): OrientationHelper {
if (mHorizontalHelper == null || mHorizontalHelper!!.layoutManager !== layoutManager) {
mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager)
}
return mHorizontalHelper!!
}
companion object {
private const val INVALID_DISTANCE = 1f
}
}
/*
*版权所有(C)2016安卓开源项目
*
*根据Apache许可证2.0版(以下简称“许可证”)获得许可;
*除非遵守许可证,否则不得使用此文件。
*您可以通过以下方式获得许可证副本:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*除非适用法律要求或书面同意,软件
*根据许可证进行的分发是按“原样”进行分发的,
*无任何明示或暗示的保证或条件。
*请参阅许可证以了解管理权限和权限的特定语言
*许可证下的限制。
*/
包com.aminography.view.component
导入android.support.v7.widget.LinearLayoutManager
导入android.support.v7.widget.OrientationHelper
导入android.support.v7.widget.RecyclerView
导入android.support.v7.widget.SnapHelper
导入android.view.view
/**
*支持垂直或水平捕捉的[SnapHelper]的实现
*方向。
*
*
*该实现将目标子视图的中心捕捉到视图的中心
*附件[RecyclerView]。如果要更改此行为,请重写
*[SnapHelper.CalculateInstanceOfInnalSnap]。
*/
类MyLinearSnapHelper:SnapHelper(){
//每个LayoutManager都会延迟创建方向帮助器。
私有变量mVerticalHelper:OrientationHelper?=null
私有变量mHorizontalHelper:OrientionHelper?=null
覆盖有趣的CalculateInstanceOfInnalSnap(
layoutManager:RecyclerView.layoutManager,targetView:View):是否在阵列中{
val out=IntArray(2)
if(layoutManager.canscroll()){
out[0]=距离到中心(layoutManager、targetView、,
getHorizontalHelper(布局管理器))
}否则{
out[0]=0
}
if(layoutManager.canScrollVertical()){
out[1]=距离到中心(layoutManager、targetView、,
getVerticalHelper(布局管理器))
}否则{
out[1]=0
}
返回
}
覆盖有趣的findTargetSnapPosition(layoutManager:RecyclerView.layoutManager,velocityX:Int,
速度y:Int):Int{
if(layoutManager!是RecyclerView.SmoothScroller.ScrollVectorProvider){
返回RecyclerView.NO\u位置
}
val itemCount=layoutManager.itemCount
如果(itemCount==0){
返回RecyclerView.NO\u位置
}
val currentView=findSnapView(布局管理器)?:返回RecyclerView.NO\u位置
val currentPosition=layoutManager.getPosition(currentView)
如果(currentPosition==RecyclerView.NO_位置){
返回RecyclerView.NO\u位置
}
val vectorProvider=layoutManager作为RecyclerView.SmoothScroller.ScrollVectorProvider
//deltaJumps符号来自于速度,它可能与儿童在运动中的顺序不匹配
//为了克服这个问题,我们需要一个从LayoutManager到
//找到方向。
val vectorForEnd=vectorProvider.computeScrollVectorForPosition(itemCount-1)
?://无法获取给定位置的向量。
返回RecyclerView.NO\u位置
变量vDeltaJump:Int
var hDeltaJump:Int
if(layoutManager.canscroll()){
hDeltaJump=估计下一个位置的差异(layoutManager,
getHorizontalHelper(布局管理器),velocityX,0)
if(向量forend.x<0){
hDeltaJump=-hDeltaJump
}
}否则{
hDeltaJump=0
}
if(layoutManager.canScrollVertical()){
vDeltaJump=估计下一个位置差异(layoutManager,
getVerticalHelper(布局管理器),0,velocityY)
if(向量forend.y<0){
vDeltaJump=-vDeltaJump
}
}否则{
vDeltaJump=0
}
val deltaJump=if(layoutManager.canscrollvertical())vDeltaJump else hDeltaJump
如果(deltaJump==0){
返回RecyclerView.NO\u位置
}
var targetPos=currentPosition+deltaJump
如果(目标位置<0){
targetPos=0
}
如果(targetPos>=itemCount){
targetPos=项目计数-1
}
返回目标位置
}
覆盖有趣的findSnapView(layoutManager:RecyclerView.layoutManager):视图{
if(layoutManager.canScrollVertical()){
返回FindCenter视图(layoutManager、getVerticalHelper(layoutManager))
}else if(layoutManager.canscroll()){
返回FindCenter视图(layoutManager、getHorizontalHelper(layoutManager))
}
返回空
}
私人娱乐距离中心(layoutManager:RecyclerView.layoutManager,
targetView:视图,辅助对象:方向辅助对象):Int{
val childCenter=helper.getDecoratedStart(targetView)+helper.getDecoratedMeasurement(targetView)/2
val containerCenter:Int=if(layoutManager.cliptoppadding){
helper.startAfterPadding+helper.totalSpace/2
}否则{
private class PagerSelectSnapHelper : LinearSnapHelper() {
override fun findSnapView(layoutManager: RecyclerView.LayoutManager): View? {
// Use existing LinearSnapHelper but override when the itemDecoration calculations are off
val snapView = super.findSnapView(layoutManager)
return if (!snapView.isViewInCenterOfParent(layoutManager.width)) {
val endView = layoutManager.findViewByPosition(layoutManager.itemCount - 1)
val startView = layoutManager.findViewByPosition(0)
when {
endView.isViewInCenterOfParent(layoutManager.width) -> endView
startView.isViewInCenterOfParent(layoutManager.width) -> startView
else -> snapView
}
} else {
snapView
}
}
private fun View?.isViewInCenterOfParent(parentWidth: Int): Boolean {
if (this == null || width == 0) {
return false
}
val parentCenter = parentWidth / 2
return left < parentCenter && parentCenter < right
}
}
class CarouselSnapHelper : LinearSnapHelper() {
override fun findSnapView(layoutManager: RecyclerView.LayoutManager): View? {
val linearLayoutManager = layoutManager as? LinearLayoutManager
?: return super.findSnapView(layoutManager)
return linearLayoutManager
.takeIf { isValidSnap(it) }
?.run { super.findSnapView(layoutManager) }
}
private fun isValidSnap(linearLayoutManager: LinearLayoutManager) =
linearLayoutManager.findFirstCompletelyVisibleItemPosition() != 0 &&
linearLayoutManager.findLastCompletelyVisibleItemPosition() != linearLayoutManager.itemCount - 1
}
public class CustomSnapHelper extends LinearSnapHelper {
@Override
public View findSnapView(RecyclerView.LayoutManager layoutManager) {
if(layoutManager instanceof LinearLayoutManager){
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
if(needToDoSnap(linearLayoutManager)==false){
return null;
}
}
return super.findSnapView(layoutManager);
}
public boolean needToDoSnap(LinearLayoutManager linearLayoutManager){
return linearLayoutManager.findFirstCompletelyVisibleItemPosition()!=0&&linearLayoutManager.findLastCompletelyVisibleItemPosition()!=linearLayoutManager.getItemCount()-1;
}
}
CustomSnapHelper mSnapHelper = new CustomSnapHelper();
mSnapHelper.attachToRecyclerView(mRecyclerView);