Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,19 +61,21 @@ open class LoopingLayoutManager : LayoutManager, RecyclerView.SmoothScroller.Scr
*/
public val layoutWidth: Int
get() = width - paddingLeft - paddingRight

/**
* The height of the layout - not the recycler.
* AKA the height of the recycler, minus the padding on the top and bottom.
*/
public val layoutHeight: Int
get() = height - paddingTop - paddingBottom

/** Describes the adapter index of the view in the top/left -most position. */
public var topLeftIndex = 0
private set
private set

/** Describes the adapter index of the view in the bottom/right -most position. */
public var bottomRightIndex = 0
private set
private set

/**
* The adapter index of the view at the edge of the layout where the 0th item was
Expand All @@ -93,16 +95,21 @@ open class LoopingLayoutManager : LayoutManager, RecyclerView.SmoothScroller.Scr
* When the layout manager needs to scroll to a position (via smooth scrolling) it needs some
* method to decide which movement direction to scroll in. This variable stores that method.
*/
public var smoothScrollDirectionDecider: (Int, LoopingLayoutManager, Int) -> Int = ::defaultDecider

public var smoothScrollDirectionDecider: (Int, LoopingLayoutManager, Int) -> Int =
::defaultDecider

/**
* Creates a LoopingLayout manager with the given orientation and reverse layout option.
* @param context Current context, will be used to access resources.
* @param orientation Layout orientation. Should be [HORIZONTAL] or [VERTICAL].
* @param reverseLayout When set to true, lays out items in the opposite direction from default.
*/
@JvmOverloads
public constructor(context: Context, orientation: Int = VERTICAL, reverseLayout: Boolean = false) {
public constructor(
context: Context,
orientation: Int = VERTICAL,
reverseLayout: Boolean = false
) {
this.orientation = orientation
this.reverseLayout = reverseLayout
}
Expand Down Expand Up @@ -166,8 +173,10 @@ open class LoopingLayoutManager : LayoutManager, RecyclerView.SmoothScroller.Scr
}

public override fun generateDefaultLayoutParams(): LayoutParams {
return LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT)
return LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
}

public override fun onSaveInstanceState(): Parcelable? {
Expand All @@ -178,8 +187,9 @@ open class LoopingLayoutManager : LayoutManager, RecyclerView.SmoothScroller.Scr
null
} else {
LayoutRequest(
anchorIndex = getInitialIndex(direction),
scrollOffset = getInitialItem(direction).hiddenSize)
anchorIndex = getInitialIndex(direction),
scrollOffset = getInitialItem(direction).hiddenSize
)
}
}

Expand All @@ -189,7 +199,10 @@ open class LoopingLayoutManager : LayoutManager, RecyclerView.SmoothScroller.Scr
}
}

public override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State) {
public override fun onLayoutChildren(
recycler: RecyclerView.Recycler,
state: RecyclerView.State
) {
layoutRequest.initialize(this, state)

detachAndScrapAttachedViews(recycler)
Expand All @@ -207,10 +220,12 @@ open class LoopingLayoutManager : LayoutManager, RecyclerView.SmoothScroller.Scr
val view = createViewForIndex(index, movementDir, recycler)
val item = getItemForView(movementDir, view)
var layoutRect = getNonScrollingEdges(view)
layoutRect = prevItem?.getPositionOfItemFollowingSelf(item, layoutRect) ?:
item.getPositionOfSelfAsFirst(layoutRect, layoutRequest.scrollOffset)
layoutDecorated(view, layoutRect.left, layoutRect.top,
layoutRect.right, layoutRect.bottom)
layoutRect = prevItem?.getPositionOfItemFollowingSelf(item, layoutRect)
?: item.getPositionOfSelfAsFirst(layoutRect, layoutRequest.scrollOffset)
layoutDecoratedWithMargins(
view, layoutRect.left, layoutRect.top,
layoutRect.right, layoutRect.bottom
)

index = stepIndex(index, movementDir, state, false)
sizeFilled += item.size
Expand Down Expand Up @@ -290,23 +305,27 @@ open class LoopingLayoutManager : LayoutManager, RecyclerView.SmoothScroller.Scr
val newItem = getItemForView(movementDir, newView)
var layoutRect = getNonScrollingEdges(newView)
layoutRect = selectedItem.getPositionOfItemFollowingSelf(newItem, layoutRect)
layoutDecorated(newView, layoutRect.left, layoutRect.top,
layoutRect.right, layoutRect.bottom)
layoutDecoratedWithMargins(
newView, layoutRect.left, layoutRect.top,
layoutRect.right, layoutRect.bottom
)
selectedItem = newItem
}
}

// The amount of extra (i.e not visible) space currently covered by views.
var viewSpace = selectedItem.hiddenSize
while(viewSpace < extraLayoutSpace) {
while (viewSpace < extraLayoutSpace) {
// We don't want the topLeftIndex or bottomRightIndex to reflect non-visible views.
index = stepIndex(index, movementDir, state, updateIndex = false)
val newView = createViewForIndex(index, movementDir, recycler)
val newItem = getItemForView(movementDir, newView)
var layoutRect = getNonScrollingEdges(newView)
layoutRect = selectedItem.getPositionOfItemFollowingSelf(newItem, layoutRect)
layoutDecorated(newView, layoutRect.left, layoutRect.top,
layoutRect.right, layoutRect.bottom)
layoutDecoratedWithMargins(
newView, layoutRect.left, layoutRect.top,
layoutRect.right, layoutRect.bottom
)
selectedItem = newItem
viewSpace += selectedItem.size
}
Expand Down Expand Up @@ -414,10 +433,10 @@ open class LoopingLayoutManager : LayoutManager, RecyclerView.SmoothScroller.Scr
* newest view. This functionality can be disabled by passing "false" to the [updateIndex] parameter.
*/
private fun stepIndex(
index: Int,
movementDir: Int,
state: RecyclerView.State,
updateIndex: Boolean = true
index: Int,
movementDir: Int,
state: RecyclerView.State,
updateIndex: Boolean = true
): Int {
val adapterDirection = getAdapterDirectionFromMovementDirection(movementDir)
val count = state.itemCount
Expand All @@ -435,7 +454,7 @@ open class LoopingLayoutManager : LayoutManager, RecyclerView.SmoothScroller.Scr
}
isTowardsTopLeft && isTowardsLowerIndices -> {
newIndex = index.loopedDecrement(count)
if (updateIndex) topLeftIndex = newIndex
if (updateIndex) topLeftIndex = newIndex
}
isTowardsBottomRight && isTowardsHigherIndices -> {
newIndex = index.loopedIncrement(count)
Expand Down Expand Up @@ -505,7 +524,7 @@ open class LoopingLayoutManager : LayoutManager, RecyclerView.SmoothScroller.Scr
val range = if (movementDir == TOWARDS_TOP_LEFT) {
0 until childCount
} else {
childCount-1 downTo 0
childCount - 1 downTo 0
}

// Ignore hidden views at the start of the range.
Expand All @@ -517,7 +536,7 @@ open class LoopingLayoutManager : LayoutManager, RecyclerView.SmoothScroller.Scr
foundVisibleView = true
}
distanceFromStart++
} else if (foundVisibleView){
} else if (foundVisibleView) {
viewsToRemove.add(i)
}
}
Expand All @@ -528,7 +547,7 @@ open class LoopingLayoutManager : LayoutManager, RecyclerView.SmoothScroller.Scr
removeAndRecycleViewAt(i, recycler)
}

if (viewsToRemove.count() == 0) {
if (viewsToRemove.isEmpty()) {
// If we didn't find anything that needed to be disposed, no indices need to be updated.
return
}
Expand Down Expand Up @@ -779,8 +798,8 @@ open class LoopingLayoutManager : LayoutManager, RecyclerView.SmoothScroller.Scr
* position.
*/
public fun findViewByPosition(
adapterIndex: Int,
strategy: (targetIndex: Int, layoutManager: LoopingLayoutManager) -> View?
adapterIndex: Int,
strategy: (targetIndex: Int, layoutManager: LoopingLayoutManager) -> View?
): View? {
return strategy(adapterIndex, this)
}
Expand Down Expand Up @@ -808,7 +827,7 @@ open class LoopingLayoutManager : LayoutManager, RecyclerView.SmoothScroller.Scr
var lowestIndex = Int.MAX_VALUE;
for (i in 0 until childCount) {
val view = getChildAt(i);
if (view != null && getPosition(view) < lowestIndex && viewIsVisible(view)) {
if (view != null && getPosition(view) < lowestIndex && viewIsVisible(view)) {
lowestIndex = getPosition(view)
}
}
Expand All @@ -822,7 +841,7 @@ open class LoopingLayoutManager : LayoutManager, RecyclerView.SmoothScroller.Scr
var lowestIndex = Int.MAX_VALUE;
for (i in 0 until childCount) {
val view = getChildAt(i);
if (view != null && getPosition(view) < lowestIndex && viewIsFullyVisible(view)) {
if (view != null && getPosition(view) < lowestIndex && viewIsFullyVisible(view)) {
lowestIndex = getPosition(view)
}
}
Expand All @@ -837,7 +856,7 @@ open class LoopingLayoutManager : LayoutManager, RecyclerView.SmoothScroller.Scr
var highestIndex = 0;
for (i in 0 until childCount) {
val view = getChildAt(i)
if (view != null && getPosition(view) > highestIndex && viewIsVisible(view)) {
if (view != null && getPosition(view) > highestIndex && viewIsVisible(view)) {
highestIndex = getPosition(view)
}
}
Expand All @@ -851,7 +870,7 @@ open class LoopingLayoutManager : LayoutManager, RecyclerView.SmoothScroller.Scr
var highestIndex = 0;
for (i in 0 until childCount) {
val view = getChildAt(i)
if (view != null && getPosition(view) > highestIndex && viewIsFullyVisible(view)) {
if (view != null && getPosition(view) > highestIndex && viewIsFullyVisible(view)) {
highestIndex = getPosition(view)
}
}
Expand Down Expand Up @@ -882,12 +901,12 @@ open class LoopingLayoutManager : LayoutManager, RecyclerView.SmoothScroller.Scr
* Note that this change will not be reflected until the next layout call.
*/
public fun scrollToPosition(
adapterIndex: Int,
strategy: (
targetIndex: Int,
layoutManager: LoopingLayoutManager,
itemCount: Int
) -> Int
adapterIndex: Int,
strategy: (
targetIndex: Int,
layoutManager: LoopingLayoutManager,
itemCount: Int
) -> Int
) {
if (viewWithIndexIsFullyVisible(adapterIndex)) return
layoutRequest = LayoutRequest(anchorIndex = adapterIndex, scrollStrategy = strategy)
Expand All @@ -909,9 +928,9 @@ open class LoopingLayoutManager : LayoutManager, RecyclerView.SmoothScroller.Scr
}

public override fun smoothScrollToPosition(
recyclerView: RecyclerView,
state: RecyclerView.State,
position: Int
recyclerView: RecyclerView,
state: RecyclerView.State,
position: Int
) {
val loopingSmoothScroller = LoopingSmoothScroller(recyclerView.context, state)
loopingSmoothScroller.targetPosition = position
Expand Down Expand Up @@ -987,7 +1006,7 @@ open class LoopingLayoutManager : LayoutManager, RecyclerView.SmoothScroller.Scr
get() = getDecoratedRight(view)

public override val size: Int
get() = getDecoratedMeasuredWidth(view)
get() = getDecoratedMeasuredWidthWithMargins(view)

public override fun getPositionOfItemFollowingSelf(item: ListItem, rect: Rect): Rect {
rect.left = followingEdge
Expand Down Expand Up @@ -1016,7 +1035,7 @@ open class LoopingLayoutManager : LayoutManager, RecyclerView.SmoothScroller.Scr
get() = getDecoratedBottom(view)

public override val size: Int
get() = getDecoratedMeasuredHeight(view)
get() = getDecoratedMeasuredHeightWithMargins(view)


public override fun getPositionOfItemFollowingSelf(item: ListItem, rect: Rect): Rect {
Expand Down Expand Up @@ -1044,7 +1063,7 @@ open class LoopingLayoutManager : LayoutManager, RecyclerView.SmoothScroller.Scr
get() = getDecoratedLeft(view)

public override val size: Int
get() = getDecoratedMeasuredWidth(view)
get() = getDecoratedMeasuredWidthWithMargins(view)

public override fun getPositionOfItemFollowingSelf(item: ListItem, rect: Rect): Rect {
rect.right = followingEdge
Expand All @@ -1071,7 +1090,7 @@ open class LoopingLayoutManager : LayoutManager, RecyclerView.SmoothScroller.Scr
get() = getDecoratedTop(view)

public override val size: Int
get() = getDecoratedMeasuredHeight(view)
get() = getDecoratedMeasuredHeightWithMargins(view)

public override fun getPositionOfItemFollowingSelf(item: ListItem, rect: Rect): Rect {
rect.bottom = followingEdge
Expand All @@ -1093,8 +1112,8 @@ open class LoopingLayoutManager : LayoutManager, RecyclerView.SmoothScroller.Scr
* a scroll vector.
*/
private inner class LoopingSmoothScroller(
val context: Context,
val state: RecyclerView.State
val context: Context,
val state: RecyclerView.State
) : LinearSmoothScroller(context) {

/**
Expand Down Expand Up @@ -1184,12 +1203,12 @@ open class LoopingLayoutManager : LayoutManager, RecyclerView.SmoothScroller.Scr
}

public constructor(
anchorIndex: Int = RecyclerView.NO_POSITION,
scrollOffset: Int = 0,
adapterDirection: Int = TOWARDS_LOWER_INDICES,
scrollStrategy: ((Int, LoopingLayoutManager, Int) -> Int)? = null,
layoutManager: LoopingLayoutManager? = null,
state: RecyclerView.State? = null
anchorIndex: Int = RecyclerView.NO_POSITION,
scrollOffset: Int = 0,
adapterDirection: Int = TOWARDS_LOWER_INDICES,
scrollStrategy: ((Int, LoopingLayoutManager, Int) -> Int)? = null,
layoutManager: LoopingLayoutManager? = null,
state: RecyclerView.State? = null
) : this() {
this.anchorIndex = anchorIndex
this.scrollOffset = scrollOffset
Expand All @@ -1199,8 +1218,9 @@ open class LoopingLayoutManager : LayoutManager, RecyclerView.SmoothScroller.Scr
if (layoutManager != null && state != null) initialize(layoutManager, state)

if (!hasBeenInitialized
&& anchorIndex != RecyclerView.NO_POSITION
&& scrollStrategy == null) {
&& anchorIndex != RecyclerView.NO_POSITION
&& scrollStrategy == null
) {
hasBeenInitialized = true
}
}
Expand All @@ -1213,16 +1233,19 @@ open class LoopingLayoutManager : LayoutManager, RecyclerView.SmoothScroller.Scr
hasBeenInitialized = true
// If this is executing a scrollTo, the anchorIndex will be set, but the
// adapterDirection still needs to be decided.
adapterDirection = scrollStrategy?.invoke(anchorIndex, layoutManager, state.itemCount)?.let {
layoutManager.getAdapterDirectionFromMovementDirection(it) }
adapterDirection =
scrollStrategy?.invoke(anchorIndex, layoutManager, state.itemCount)?.let {
layoutManager.getAdapterDirectionFromMovementDirection(it)
}
?: adapterDirection
// If this is an adapter data update, the adapterDirection will be set but the
// anchorIndex and scrollOffset still need to be decided.
if (anchorIndex == RecyclerView.NO_POSITION) {
if (layoutManager.childCount == 0) {
anchorIndex = 0
} else {
val direction = layoutManager.getMovementDirectionFromAdapterDirection(adapterDirection)
val direction =
layoutManager.getMovementDirectionFromAdapterDirection(adapterDirection)
anchorIndex = layoutManager.getInitialIndex(direction)
scrollOffset = layoutManager.getInitialItem(direction).hiddenSize
}
Expand Down Expand Up @@ -1276,6 +1299,7 @@ open class LoopingLayoutManager : LayoutManager, RecyclerView.SmoothScroller.Scr
* top/left.
*/
public const val TOWARDS_TOP_LEFT = -1

/**
* Describes the user scrolling towards the bottom/right of the screen. NOTE: this does
* *not* describe the direction views are moving in. The user is trying to see new views
Expand All @@ -1289,6 +1313,7 @@ open class LoopingLayoutManager : LayoutManager, RecyclerView.SmoothScroller.Scr
* indices.
*/
public const val TOWARDS_LOWER_INDICES = -1

/**
* Describes the direction we need to traverse view indices in to get to larger adapter indices.
* In this case we need to traverse the views forwards (0 -> Max) to get to higher adapter
Expand All @@ -1302,11 +1327,12 @@ open class LoopingLayoutManager : LayoutManager, RecyclerView.SmoothScroller.Scr
*
*/
private const val SCROLL_OFFSET = 100

/**
* A constant returned by the [computeScrollRange] function so that accessibility knows
* the layout is always scrollable.
*/
private const val SCROLL_RANGE = 200
}

}
}
Loading