1 /*
2 * Copyright 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package androidx.compose.foundation.pager
18
19 import androidx.compose.animation.core.DecayAnimationSpec
20 import androidx.compose.foundation.internal.requirePrecondition
21 import androidx.compose.runtime.Stable
22
23 /**
24 * [PagerSnapDistance] defines the way the [Pager] will treat the distance between the current page
25 * and the page where it will settle.
26 */
27 @Stable
28 interface PagerSnapDistance {
29
30 /**
31 * Provides a chance to change where the [Pager] fling will settle.
32 *
33 * @param startPage The current page right before the fling starts.
34 * @param suggestedTargetPage The proposed target page where this fling will stop. This target
35 * will be the page that will be correctly positioned (snapped) after naturally decaying with
36 * [velocity] using a [DecayAnimationSpec].
37 * @param velocity The initial fling velocity.
38 * @param pageSize The page size for this [Pager] in pixels.
39 * @param pageSpacing The spacing used between pages in pixels.
40 * @return An updated target page where to settle. Note that this value needs to be between 0
41 * and the total count of pages in this pager. If an invalid value is passed, the pager will
42 * coerce within the valid values.
43 */
calculateTargetPagenull44 fun calculateTargetPage(
45 startPage: Int,
46 suggestedTargetPage: Int,
47 velocity: Float,
48 pageSize: Int,
49 pageSpacing: Int
50 ): Int
51
52 companion object {
53 /**
54 * Limits the maximum number of pages that can be flung per fling gesture.
55 *
56 * @param pages The maximum number of extra pages that can be flung at once.
57 */
58 fun atMost(pages: Int): PagerSnapDistance {
59 requirePrecondition(pages >= 0) {
60 "pages should be greater than or equal to 0. You have used $pages."
61 }
62 return PagerSnapDistanceMaxPages(pages)
63 }
64 }
65 }
66
67 /**
68 * Limits the maximum number of pages that can be flung per fling gesture.
69 *
70 * @param pagesLimit The maximum number of extra pages that can be flung at once.
71 */
72 internal class PagerSnapDistanceMaxPages(private val pagesLimit: Int) : PagerSnapDistance {
calculateTargetPagenull73 override fun calculateTargetPage(
74 startPage: Int,
75 suggestedTargetPage: Int,
76 velocity: Float,
77 pageSize: Int,
78 pageSpacing: Int,
79 ): Int {
80 debugLog {
81 "PagerSnapDistanceMaxPages: startPage=$startPage " +
82 "suggestedTargetPage=$suggestedTargetPage " +
83 "velocity=$velocity " +
84 "pageSize=$pageSize " +
85 "pageSpacing$pageSpacing"
86 }
87 val startPageLong = startPage.toLong()
88 val minRange = (startPageLong - pagesLimit).coerceAtLeast(0).toInt()
89 val maxRange = (startPageLong + pagesLimit).coerceAtMost(Int.MAX_VALUE.toLong()).toInt()
90 return suggestedTargetPage.coerceIn(minRange, maxRange)
91 }
92
equalsnull93 override fun equals(other: Any?): Boolean {
94 return if (other is PagerSnapDistanceMaxPages) {
95 this.pagesLimit == other.pagesLimit
96 } else {
97 false
98 }
99 }
100
hashCodenull101 override fun hashCode(): Int {
102 return pagesLimit.hashCode()
103 }
104 }
105
debugLognull106 private inline fun debugLog(generateMsg: () -> String) {
107 if (PagerDebugConfig.PagerSnapDistance) {
108 println("PagerSnapDistance: ${generateMsg()}")
109 }
110 }
111