| /* |
| * Copyright 2023 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 androidx.compose.foundation.pager |
| |
| import androidx.compose.foundation.ExperimentalFoundationApi |
| import androidx.compose.foundation.gestures.Orientation |
| import androidx.compose.foundation.gestures.snapping.SnapPosition |
| import androidx.compose.foundation.gestures.snapping.calculateDistanceToDesiredSnapPosition |
| import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy |
| import androidx.compose.foundation.lazy.layout.LazyLayoutMeasureScope |
| import androidx.compose.foundation.lazy.layout.ObservableScopeInvalidator |
| import androidx.compose.ui.Alignment |
| import androidx.compose.ui.layout.MeasureResult |
| import androidx.compose.ui.layout.Placeable |
| import androidx.compose.ui.unit.Constraints |
| import androidx.compose.ui.unit.Density |
| import androidx.compose.ui.unit.IntOffset |
| import androidx.compose.ui.unit.LayoutDirection |
| import androidx.compose.ui.unit.constrainHeight |
| import androidx.compose.ui.unit.constrainWidth |
| import androidx.compose.ui.util.fastFilter |
| import androidx.compose.ui.util.fastForEach |
| import androidx.compose.ui.util.fastMaxBy |
| import kotlin.math.abs |
| |
| @OptIn(ExperimentalFoundationApi::class) |
| internal fun LazyLayoutMeasureScope.measurePager( |
| pageCount: Int, |
| pagerItemProvider: PagerLazyLayoutItemProvider, |
| mainAxisAvailableSize: Int, |
| beforeContentPadding: Int, |
| afterContentPadding: Int, |
| spaceBetweenPages: Int, |
| currentPage: Int, |
| currentPageOffset: Int, |
| constraints: Constraints, |
| orientation: Orientation, |
| verticalAlignment: Alignment.Vertical?, |
| horizontalAlignment: Alignment.Horizontal?, |
| reverseLayout: Boolean, |
| visualPageOffset: IntOffset, |
| pageAvailableSize: Int, |
| beyondBoundsPageCount: Int, |
| pinnedPages: List<Int>, |
| snapPosition: SnapPosition, |
| placementScopeInvalidator: ObservableScopeInvalidator, |
| layout: (Int, Int, Placeable.PlacementScope.() -> Unit) -> MeasureResult |
| ): PagerMeasureResult { |
| require(beforeContentPadding >= 0) { "negative beforeContentPadding" } |
| require(afterContentPadding >= 0) { "negative afterContentPadding" } |
| val pageSizeWithSpacing = (pageAvailableSize + spaceBetweenPages).coerceAtLeast(0) |
| |
| debugLog { |
| "Starting Measure Pass..." + |
| "\n CurrentPage = $currentPage" + |
| "\n CurrentPageOffset = $currentPageOffset" + |
| "\n SnapPosition = ${snapPosition.string()}" |
| } |
| |
| return if (pageCount <= 0) { |
| PagerMeasureResult( |
| visiblePagesInfo = emptyList(), |
| pageSize = pageAvailableSize, |
| pageSpacing = spaceBetweenPages, |
| afterContentPadding = afterContentPadding, |
| orientation = orientation, |
| viewportStartOffset = -beforeContentPadding, |
| viewportEndOffset = mainAxisAvailableSize + afterContentPadding, |
| measureResult = layout(constraints.minWidth, constraints.minHeight) {}, |
| firstVisiblePage = null, |
| firstVisiblePageScrollOffset = 0, |
| reverseLayout = false, |
| beyondBoundsPageCount = beyondBoundsPageCount, |
| canScrollForward = false, |
| currentPage = null, |
| currentPageOffsetFraction = 0.0f, |
| snapPosition = snapPosition, |
| remeasureNeeded = false |
| ) |
| } else { |
| |
| val childConstraints = Constraints( |
| maxWidth = if (orientation == Orientation.Vertical) { |
| constraints.maxWidth |
| } else { |
| pageAvailableSize |
| }, |
| maxHeight = if (orientation != Orientation.Vertical) { |
| constraints.maxHeight |
| } else { |
| pageAvailableSize |
| } |
| ) |
| |
| var firstVisiblePage = currentPage |
| var firstVisiblePageOffset = currentPageOffset |
| |
| // figure out the first visible page and the firstVisiblePageOffset based on current page |
| // The offset by the scroll event has already been applied to currentPageOffset |
| while (firstVisiblePage > 0 && firstVisiblePageOffset > 0) { |
| firstVisiblePage-- |
| firstVisiblePageOffset -= pageSizeWithSpacing |
| } |
| |
| // the scroll offset is opposite sign to the actual offset |
| val firstVisiblePageScrollOffset = firstVisiblePageOffset * -1 |
| |
| var currentFirstPage = firstVisiblePage |
| var currentFirstPageScrollOffset = firstVisiblePageScrollOffset |
| if (currentFirstPage >= pageCount) { |
| // the data set has been updated and now we have less pages that we were |
| // scrolled to before |
| currentFirstPage = pageCount - 1 |
| currentFirstPageScrollOffset = 0 |
| } |
| |
| debugLog { |
| "Calculated Info:" + |
| "\n FirstVisiblePage=$firstVisiblePage" + |
| "\n firstVisiblePageScrollOffset=$firstVisiblePageScrollOffset" |
| } |
| |
| // this will contain all the measured pages representing the visible pages |
| val visiblePages = ArrayDeque<MeasuredPage>() |
| |
| // define min and max offsets |
| val minOffset = -beforeContentPadding + if (spaceBetweenPages < 0) spaceBetweenPages else 0 |
| val maxOffset = mainAxisAvailableSize |
| |
| // include the start padding so we compose pages in the padding area and neutralise page |
| // spacing (if the spacing is negative this will make sure the previous page is composed) |
| // before starting scrolling forward we will remove it back |
| currentFirstPageScrollOffset += minOffset |
| |
| // max of cross axis sizes of all visible pages |
| var maxCrossAxis = 0 |
| |
| debugLog { "Composing Backwards" } |
| |
| // we had scrolled backward or we compose pages in the start padding area, which means |
| // pages before current firstPageScrollOffset should be visible. compose them and update |
| // firstPageScrollOffset |
| while (currentFirstPageScrollOffset < 0 && currentFirstPage > 0) { |
| val previous = currentFirstPage - 1 |
| val measuredPage = getAndMeasure( |
| index = previous, |
| childConstraints = childConstraints, |
| pagerItemProvider = pagerItemProvider, |
| visualPageOffset = visualPageOffset, |
| orientation = orientation, |
| horizontalAlignment = horizontalAlignment, |
| verticalAlignment = verticalAlignment, |
| layoutDirection = layoutDirection, |
| reverseLayout = reverseLayout, |
| pageAvailableSize = pageAvailableSize |
| ) |
| |
| debugLog { "Composed Page=$previous" } |
| |
| visiblePages.add(0, measuredPage) |
| maxCrossAxis = maxOf(maxCrossAxis, measuredPage.crossAxisSize) |
| currentFirstPageScrollOffset += pageSizeWithSpacing |
| currentFirstPage = previous |
| } |
| |
| if (currentFirstPageScrollOffset < minOffset) { |
| currentFirstPageScrollOffset = minOffset |
| } |
| |
| // neutralize previously added padding as we stopped filling the before content padding |
| currentFirstPageScrollOffset -= minOffset |
| |
| var index = currentFirstPage |
| val maxMainAxis = (maxOffset + afterContentPadding).coerceAtLeast(0) |
| var currentMainAxisOffset = -currentFirstPageScrollOffset |
| |
| // will be set to true if we composed some items only to know their size and apply scroll, |
| // while in the end this item will not end up in the visible viewport. we will need an |
| // extra remeasure in order to dispose such items. |
| var remeasureNeeded = false |
| |
| // first we need to skip pages we already composed while composing backward |
| var indexInVisibleItems = 0 |
| |
| while (indexInVisibleItems < visiblePages.size) { |
| if (currentMainAxisOffset >= maxMainAxis) { |
| // this item is out of the bounds and will not be visible. |
| visiblePages.removeAt(indexInVisibleItems) |
| remeasureNeeded = true |
| } else { |
| index++ |
| currentMainAxisOffset += pageSizeWithSpacing |
| indexInVisibleItems++ |
| } |
| } |
| |
| debugLog { "Composing Forward Starting at Index=$index" } |
| // then composing visible pages forward until we fill the whole viewport. |
| // we want to have at least one page in visiblePages even if in fact all the pages are |
| // offscreen, this can happen if the content padding is larger than the available size. |
| while (index < pageCount && |
| (currentMainAxisOffset < maxMainAxis || |
| currentMainAxisOffset <= 0 || // filling beforeContentPadding area |
| visiblePages.isEmpty()) |
| ) { |
| val measuredPage = getAndMeasure( |
| index = index, |
| childConstraints = childConstraints, |
| pagerItemProvider = pagerItemProvider, |
| visualPageOffset = visualPageOffset, |
| orientation = orientation, |
| horizontalAlignment = horizontalAlignment, |
| verticalAlignment = verticalAlignment, |
| layoutDirection = layoutDirection, |
| reverseLayout = reverseLayout, |
| pageAvailableSize = pageAvailableSize |
| ) |
| |
| debugLog { "Composed Page=$index at $currentFirstPageScrollOffset" } |
| |
| // do not add space to the last page |
| currentMainAxisOffset += if (index == pageCount - 1) { |
| pageAvailableSize |
| } else { |
| pageSizeWithSpacing |
| } |
| |
| if (currentMainAxisOffset <= minOffset && index != pageCount - 1) { |
| // this page is offscreen and will not be visible. advance currentFirstPage |
| currentFirstPage = index + 1 |
| currentFirstPageScrollOffset -= pageSizeWithSpacing |
| remeasureNeeded = true |
| } else { |
| maxCrossAxis = maxOf(maxCrossAxis, measuredPage.crossAxisSize) |
| visiblePages.add(measuredPage) |
| } |
| |
| index++ |
| } |
| |
| // we didn't fill the whole viewport with pages starting from firstVisiblePage. |
| // lets try to scroll back if we have enough pages before firstVisiblePage. |
| if (currentMainAxisOffset < maxOffset) { |
| val toScrollBack = maxOffset - currentMainAxisOffset |
| currentFirstPageScrollOffset -= toScrollBack |
| currentMainAxisOffset += toScrollBack |
| while (currentFirstPageScrollOffset < beforeContentPadding && |
| currentFirstPage > 0 |
| ) { |
| val previousIndex = currentFirstPage - 1 |
| val measuredPage = getAndMeasure( |
| index = previousIndex, |
| childConstraints = childConstraints, |
| pagerItemProvider = pagerItemProvider, |
| visualPageOffset = visualPageOffset, |
| orientation = orientation, |
| horizontalAlignment = horizontalAlignment, |
| verticalAlignment = verticalAlignment, |
| layoutDirection = layoutDirection, |
| reverseLayout = reverseLayout, |
| pageAvailableSize = pageAvailableSize |
| ) |
| visiblePages.add(0, measuredPage) |
| maxCrossAxis = maxOf(maxCrossAxis, measuredPage.crossAxisSize) |
| currentFirstPageScrollOffset += pageSizeWithSpacing |
| currentFirstPage = previousIndex |
| } |
| |
| if (currentFirstPageScrollOffset < 0) { |
| currentMainAxisOffset += currentFirstPageScrollOffset |
| currentFirstPageScrollOffset = 0 |
| } |
| } |
| |
| // the initial offset for pages from visiblePages list |
| require(currentFirstPageScrollOffset >= 0) { "invalid currentFirstPageScrollOffset" } |
| val visiblePagesScrollOffset = -currentFirstPageScrollOffset |
| |
| var firstPage = visiblePages.first() |
| |
| // even if we compose pages to fill before content padding we should ignore pages fully |
| // located there for the state's scroll position calculation (first page + first offset) |
| if (beforeContentPadding > 0 || spaceBetweenPages < 0) { |
| for (i in visiblePages.indices) { |
| val size = pageSizeWithSpacing |
| if (currentFirstPageScrollOffset != 0 && size <= currentFirstPageScrollOffset && |
| i != visiblePages.lastIndex |
| ) { |
| currentFirstPageScrollOffset -= size |
| firstPage = visiblePages[i + 1] |
| } else { |
| break |
| } |
| } |
| } |
| |
| // Compose extra pages before |
| val extraPagesBefore = createPagesBeforeList( |
| currentFirstPage = currentFirstPage, |
| beyondBoundsPageCount = beyondBoundsPageCount, |
| pinnedPages = pinnedPages |
| ) { |
| getAndMeasure( |
| index = it, |
| childConstraints = childConstraints, |
| pagerItemProvider = pagerItemProvider, |
| visualPageOffset = visualPageOffset, |
| orientation = orientation, |
| horizontalAlignment = horizontalAlignment, |
| verticalAlignment = verticalAlignment, |
| layoutDirection = layoutDirection, |
| reverseLayout = reverseLayout, |
| pageAvailableSize = pageAvailableSize |
| ) |
| } |
| |
| // Update maxCrossAxis with extra pages |
| extraPagesBefore.fastForEach { |
| maxCrossAxis = maxOf(maxCrossAxis, it.crossAxisSize) |
| } |
| |
| // Compose pages after last page |
| val extraPagesAfter = createPagesAfterList( |
| currentLastPage = visiblePages.last().index, |
| pagesCount = pageCount, |
| beyondBoundsPageCount = beyondBoundsPageCount, |
| pinnedPages = pinnedPages |
| ) { |
| getAndMeasure( |
| index = it, |
| childConstraints = childConstraints, |
| pagerItemProvider = pagerItemProvider, |
| visualPageOffset = visualPageOffset, |
| orientation = orientation, |
| horizontalAlignment = horizontalAlignment, |
| verticalAlignment = verticalAlignment, |
| layoutDirection = layoutDirection, |
| reverseLayout = reverseLayout, |
| pageAvailableSize = pageAvailableSize |
| ) |
| } |
| |
| // Update maxCrossAxis with extra pages |
| extraPagesAfter.fastForEach { |
| maxCrossAxis = maxOf(maxCrossAxis, it.crossAxisSize) |
| } |
| |
| val noExtraPages = firstPage == visiblePages.first() && |
| extraPagesBefore.isEmpty() && |
| extraPagesAfter.isEmpty() |
| |
| val layoutWidth = constraints |
| .constrainWidth( |
| if (orientation == Orientation.Vertical) |
| maxCrossAxis |
| else |
| currentMainAxisOffset |
| ) |
| |
| val layoutHeight = constraints |
| .constrainHeight( |
| if (orientation == Orientation.Vertical) |
| currentMainAxisOffset |
| else |
| maxCrossAxis |
| ) |
| |
| val positionedPages = calculatePagesOffsets( |
| pages = visiblePages, |
| extraPagesBefore = extraPagesBefore, |
| extraPagesAfter = extraPagesAfter, |
| layoutWidth = layoutWidth, |
| layoutHeight = layoutHeight, |
| finalMainAxisOffset = currentMainAxisOffset, |
| maxOffset = maxOffset, |
| pagesScrollOffset = visiblePagesScrollOffset, |
| orientation = orientation, |
| reverseLayout = reverseLayout, |
| density = this, |
| pageAvailableSize = pageAvailableSize, |
| spaceBetweenPages = spaceBetweenPages |
| ) |
| |
| val visiblePagesInfo = if (noExtraPages) positionedPages else positionedPages.fastFilter { |
| (it.index >= visiblePages.first().index && it.index <= visiblePages.last().index) |
| } |
| |
| val newCurrentPage = |
| calculateNewCurrentPage( |
| if (orientation == Orientation.Vertical) layoutHeight else layoutWidth, |
| visiblePagesInfo, |
| beforeContentPadding, |
| afterContentPadding, |
| pageSizeWithSpacing, |
| snapPosition |
| ) |
| |
| val snapOffset = snapPosition.position( |
| mainAxisAvailableSize, |
| pageAvailableSize, |
| beforeContentPadding, |
| afterContentPadding, |
| newCurrentPage?.index ?: 0 |
| ) |
| |
| val currentPagePositionOffset = (newCurrentPage?.offset ?: 0) |
| |
| val currentPageOffsetFraction = if (pageSizeWithSpacing == 0) { |
| 0.0f |
| } else { |
| ((snapOffset - currentPagePositionOffset) / (pageSizeWithSpacing.toFloat())).coerceIn( |
| MinPageOffset, |
| MaxPageOffset |
| ) |
| } |
| |
| debugLog { |
| "Finished Measure Pass" + |
| "\n Final currentPage=${newCurrentPage?.index} " + |
| "\n Final currentPageScrollOffset=$currentPagePositionOffset" + |
| "\n Final currentPageScrollOffsetFraction=$currentPageOffsetFraction" |
| } |
| |
| return PagerMeasureResult( |
| firstVisiblePage = firstPage, |
| firstVisiblePageScrollOffset = currentFirstPageScrollOffset, |
| measureResult = layout(layoutWidth, layoutHeight) { |
| positionedPages.fastForEach { |
| it.place(this) |
| } |
| // we attach it during the placement so PagerState can trigger re-placement |
| placementScopeInvalidator.attachToScope() |
| }, |
| viewportStartOffset = -beforeContentPadding, |
| viewportEndOffset = maxOffset + afterContentPadding, |
| visiblePagesInfo = visiblePagesInfo, |
| reverseLayout = reverseLayout, |
| orientation = orientation, |
| pageSize = pageAvailableSize, |
| pageSpacing = spaceBetweenPages, |
| afterContentPadding = afterContentPadding, |
| beyondBoundsPageCount = beyondBoundsPageCount, |
| canScrollForward = index < pageCount || currentMainAxisOffset > maxOffset, |
| currentPage = newCurrentPage, |
| currentPageOffsetFraction = currentPageOffsetFraction, |
| snapPosition = snapPosition, |
| remeasureNeeded = remeasureNeeded |
| ) |
| } |
| } |
| |
| private fun createPagesAfterList( |
| currentLastPage: Int, |
| pagesCount: Int, |
| beyondBoundsPageCount: Int, |
| pinnedPages: List<Int>, |
| getAndMeasure: (Int) -> MeasuredPage |
| ): List<MeasuredPage> { |
| var list: MutableList<MeasuredPage>? = null |
| |
| val end = minOf(currentLastPage + beyondBoundsPageCount, pagesCount - 1) |
| |
| for (i in currentLastPage + 1..end) { |
| if (list == null) list = mutableListOf() |
| list.add(getAndMeasure(i)) |
| } |
| |
| pinnedPages.fastForEach { pageIndex -> |
| if (pageIndex in (end + 1) until pagesCount) { |
| if (list == null) list = mutableListOf() |
| list?.add(getAndMeasure(pageIndex)) |
| } |
| } |
| |
| return list ?: emptyList() |
| } |
| |
| private fun createPagesBeforeList( |
| currentFirstPage: Int, |
| beyondBoundsPageCount: Int, |
| pinnedPages: List<Int>, |
| getAndMeasure: (Int) -> MeasuredPage |
| ): List<MeasuredPage> { |
| var list: MutableList<MeasuredPage>? = null |
| |
| val start = maxOf(0, currentFirstPage - beyondBoundsPageCount) |
| |
| for (i in currentFirstPage - 1 downTo start) { |
| if (list == null) list = mutableListOf() |
| list.add(getAndMeasure(i)) |
| } |
| |
| pinnedPages.fastForEach { pageIndex -> |
| if (pageIndex < start) { |
| if (list == null) list = mutableListOf() |
| list?.add(getAndMeasure(pageIndex)) |
| } |
| } |
| |
| return list ?: emptyList() |
| } |
| |
| @OptIn(ExperimentalFoundationApi::class) |
| private fun calculateNewCurrentPage( |
| viewportSize: Int, |
| visiblePagesInfo: List<MeasuredPage>, |
| beforeContentPadding: Int, |
| afterContentPadding: Int, |
| itemSize: Int, |
| snapPosition: SnapPosition |
| ): MeasuredPage? { |
| return visiblePagesInfo.fastMaxBy { |
| -abs( |
| calculateDistanceToDesiredSnapPosition( |
| mainAxisViewPortSize = viewportSize, |
| beforeContentPadding = beforeContentPadding, |
| afterContentPadding = afterContentPadding, |
| itemSize = itemSize, |
| itemOffset = it.offset, |
| itemIndex = it.index, |
| snapPosition = snapPosition |
| ) |
| ) |
| } |
| } |
| |
| @OptIn(ExperimentalFoundationApi::class) |
| private fun LazyLayoutMeasureScope.getAndMeasure( |
| index: Int, |
| childConstraints: Constraints, |
| pagerItemProvider: PagerLazyLayoutItemProvider, |
| visualPageOffset: IntOffset, |
| orientation: Orientation, |
| horizontalAlignment: Alignment.Horizontal?, |
| verticalAlignment: Alignment.Vertical?, |
| layoutDirection: LayoutDirection, |
| reverseLayout: Boolean, |
| pageAvailableSize: Int |
| ): MeasuredPage { |
| val key = pagerItemProvider.getKey(index) |
| val placeable = measure(index, childConstraints) |
| |
| return MeasuredPage( |
| index = index, |
| placeables = placeable, |
| visualOffset = visualPageOffset, |
| horizontalAlignment = horizontalAlignment, |
| verticalAlignment = verticalAlignment, |
| layoutDirection = layoutDirection, |
| reverseLayout = reverseLayout, |
| size = pageAvailableSize, |
| orientation = orientation, |
| key = key |
| ) |
| } |
| |
| @OptIn(ExperimentalFoundationApi::class) |
| private fun LazyLayoutMeasureScope.calculatePagesOffsets( |
| pages: List<MeasuredPage>, |
| extraPagesBefore: List<MeasuredPage>, |
| extraPagesAfter: List<MeasuredPage>, |
| layoutWidth: Int, |
| layoutHeight: Int, |
| finalMainAxisOffset: Int, |
| maxOffset: Int, |
| pagesScrollOffset: Int, |
| orientation: Orientation, |
| reverseLayout: Boolean, |
| density: Density, |
| spaceBetweenPages: Int, |
| pageAvailableSize: Int |
| ): MutableList<MeasuredPage> { |
| val pageSizeWithSpacing = (pageAvailableSize + spaceBetweenPages) |
| val mainAxisLayoutSize = if (orientation == Orientation.Vertical) layoutHeight else layoutWidth |
| val hasSpareSpace = finalMainAxisOffset < minOf(mainAxisLayoutSize, maxOffset) |
| if (hasSpareSpace) { |
| check(pagesScrollOffset == 0) { "non-zero pagesScrollOffset=$pagesScrollOffset" } |
| } |
| val positionedPages = |
| ArrayList<MeasuredPage>(pages.size + extraPagesBefore.size + extraPagesAfter.size) |
| |
| if (hasSpareSpace) { |
| require(extraPagesBefore.isEmpty() && extraPagesAfter.isEmpty()) { "No extra pages" } |
| |
| val pagesCount = pages.size |
| fun Int.reverseAware() = |
| if (!reverseLayout) this else pagesCount - this - 1 |
| |
| val sizes = IntArray(pagesCount) { pageAvailableSize } |
| val offsets = IntArray(pagesCount) { 0 } |
| |
| val arrangement = spacedBy(spaceBetweenPages.toDp()) |
| if (orientation == Orientation.Vertical) { |
| with(arrangement) { density.arrange(mainAxisLayoutSize, sizes, offsets) } |
| } else { |
| with(arrangement) { |
| // Enforces Ltr layout direction as it is mirrored with placeRelative later. |
| density.arrange(mainAxisLayoutSize, sizes, LayoutDirection.Ltr, offsets) |
| } |
| } |
| |
| val reverseAwareOffsetIndices = |
| if (!reverseLayout) offsets.indices else offsets.indices.reversed() |
| for (index in reverseAwareOffsetIndices) { |
| val absoluteOffset = offsets[index] |
| // when reverseLayout == true, offsets are stored in the reversed order to pages |
| val page = pages[index.reverseAware()] |
| val relativeOffset = if (reverseLayout) { |
| // inverse offset to align with scroll direction for positioning |
| mainAxisLayoutSize - absoluteOffset - page.size |
| } else { |
| absoluteOffset |
| } |
| page.position(relativeOffset, layoutWidth, layoutHeight) |
| positionedPages.add(page) |
| } |
| } else { |
| var currentMainAxis = pagesScrollOffset |
| extraPagesBefore.fastForEach { |
| currentMainAxis -= pageSizeWithSpacing |
| it.position(currentMainAxis, layoutWidth, layoutHeight) |
| positionedPages.add(it) |
| } |
| |
| currentMainAxis = pagesScrollOffset |
| pages.fastForEach { |
| it.position(currentMainAxis, layoutWidth, layoutHeight) |
| positionedPages.add(it) |
| currentMainAxis += pageSizeWithSpacing |
| } |
| |
| extraPagesAfter.fastForEach { |
| it.position(currentMainAxis, layoutWidth, layoutHeight) |
| positionedPages.add(it) |
| currentMainAxis += pageSizeWithSpacing |
| } |
| } |
| return positionedPages |
| } |
| |
| @OptIn(ExperimentalFoundationApi::class) |
| private fun SnapPosition.string(): String { |
| return when (this) { |
| SnapPosition.Start -> "Start" |
| SnapPosition.End -> "End" |
| SnapPosition.Center -> "Center" |
| else -> "Custom" |
| } |
| } |
| |
| internal const val MinPageOffset = -0.5f |
| internal const val MaxPageOffset = 0.5f |
| |
| private inline fun debugLog(generateMsg: () -> String) { |
| if (PagerDebugConfig.MeasureLogic) { |
| println("PagerMeasure: ${generateMsg()}") |
| } |
| } |