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.gestures.snapping
18
19 import androidx.compose.runtime.Stable
20
21 /**
22 * Describes the snapping positioning (i.e. final positioning after snapping animation finishes) of
23 * a given snap item in its containing layout.
24 */
25 @Stable
26 interface SnapPosition {
27 /**
28 * Calculates the snap position where items will be aligned to in a snapping container. For
29 * instance, if [SnapPosition.Center] is used, once the snapping finishes the center of one of
30 * the items in the snapping container will be aligned exactly to the center of the snapping
31 * container, that is because the value returned by [position] was calculated as such a way that
32 * when one applies it to the item's current offset it will generate that final positioning.
33 *
34 * The reference point is with respect to the start of the layout (including the content
35 * padding)
36 *
37 * @sample androidx.compose.foundation.samples.SnapFlingBehaviorSnapPosition
38 * @param layoutSize The main axis layout size within which an item can be positioned.
39 * @param itemSize The main axis size for the item being positioned within this snapping layout.
40 * @param beforeContentPadding The content padding in pixels applied before this Layout's
41 * content.
42 * @param afterContentPadding The content padding in pixels applied after this Layout's content.
43 * @param itemIndex The index of the item being positioned.
44 * @param itemCount The total amount of items in the snapping container.
45 * @return The offset of the snap position where items will be aligned to in a snapping
46 * container.
47 */
positionnull48 fun position(
49 layoutSize: Int,
50 itemSize: Int,
51 beforeContentPadding: Int,
52 afterContentPadding: Int,
53 itemIndex: Int,
54 itemCount: Int
55 ): Int
56
57 /** Aligns the center of the item with the center of the containing layout. */
58 object Center : SnapPosition {
59 override fun position(
60 layoutSize: Int,
61 itemSize: Int,
62 beforeContentPadding: Int,
63 afterContentPadding: Int,
64 itemIndex: Int,
65 itemCount: Int
66 ): Int {
67 val availableLayoutSpace = layoutSize - beforeContentPadding - afterContentPadding
68 // we use availableLayoutSpace / 2 as the main anchor point and we discount half
69 // an item size so the item appear aligned with the center of the container.
70 return availableLayoutSpace / 2 - itemSize / 2
71 }
72
73 override fun toString(): String {
74 return "Center"
75 }
76 }
77
78 /** Aligns the start of the item with the start of the containing layout. */
79 object Start : SnapPosition {
positionnull80 override fun position(
81 layoutSize: Int,
82 itemSize: Int,
83 beforeContentPadding: Int,
84 afterContentPadding: Int,
85 itemIndex: Int,
86 itemCount: Int
87 ): Int = 0
88
89 override fun toString(): String {
90 return "Start"
91 }
92 }
93
94 /** Aligns the end of the item with the end of the containing layout. */
95 object End : SnapPosition {
positionnull96 override fun position(
97 layoutSize: Int,
98 itemSize: Int,
99 beforeContentPadding: Int,
100 afterContentPadding: Int,
101 itemIndex: Int,
102 itemCount: Int
103 ): Int {
104 val availableLayoutSpace = layoutSize - beforeContentPadding - afterContentPadding
105 // the snap position for the item is the end of the layout, discounting the item
106 // size
107 return availableLayoutSpace - itemSize
108 }
109
toStringnull110 override fun toString(): String {
111 return "End"
112 }
113 }
114 }
115
calculateDistanceToDesiredSnapPositionnull116 internal fun calculateDistanceToDesiredSnapPosition(
117 mainAxisViewPortSize: Int,
118 beforeContentPadding: Int,
119 afterContentPadding: Int,
120 itemSize: Int,
121 itemOffset: Int,
122 itemIndex: Int,
123 snapPosition: SnapPosition,
124 itemCount: Int
125 ): Float {
126 val desiredDistance =
127 with(snapPosition) {
128 position(
129 mainAxisViewPortSize,
130 itemSize,
131 beforeContentPadding,
132 afterContentPadding,
133 itemIndex,
134 itemCount
135 )
136 }
137 .toFloat()
138
139 return itemOffset - desiredDistance
140 }
141