1 /*
<lambda>null2 * Copyright (C) 2017 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 @file:Suppress("NOTHING_TO_INLINE") // Aliases to other public API.
18
19 package androidx.core.view
20
21 import android.view.View
22 import android.view.ViewGroup
23 import androidx.annotation.Px
24 import kotlin.collections.removeLast as removeLastKt
25
26 /**
27 * Returns the view at [index].
28 *
29 * @throws IndexOutOfBoundsException if index is less than 0 or greater than or equal to the count.
30 */
31 public operator fun ViewGroup.get(index: Int): View =
32 getChildAt(index) ?: throw IndexOutOfBoundsException("Index: $index, Size: $childCount")
33
34 /** Returns `true` if [view] is found in this view group. */
35 public inline operator fun ViewGroup.contains(view: View): Boolean = indexOfChild(view) != -1
36
37 /** Adds [view] to this view group. */
38 public inline operator fun ViewGroup.plusAssign(view: View): Unit = addView(view)
39
40 /** Removes [view] from this view group. */
41 public inline operator fun ViewGroup.minusAssign(view: View): Unit = removeView(view)
42
43 /** Returns the number of views in this view group. */
44 public inline val ViewGroup.size: Int
45 get() = childCount
46
47 /** Returns true if this view group contains no views. */
48 public inline fun ViewGroup.isEmpty(): Boolean = childCount == 0
49
50 /** Returns true if this view group contains one or more views. */
51 public inline fun ViewGroup.isNotEmpty(): Boolean = childCount != 0
52
53 /** Performs the given action on each view in this view group. */
54 public inline fun ViewGroup.forEach(action: (view: View) -> Unit) {
55 for (index in 0 until childCount) {
56 action(getChildAt(index))
57 }
58 }
59
60 /** Performs the given action on each view in this view group, providing its sequential index. */
forEachIndexednull61 public inline fun ViewGroup.forEachIndexed(action: (index: Int, view: View) -> Unit) {
62 for (index in 0 until childCount) {
63 action(index, getChildAt(index))
64 }
65 }
66
67 /**
68 * Returns an [IntRange] of the valid indices for the children of this view group.
69 *
70 * This can be used for looping:
71 * ```kotlin
72 * for (i in viewGroup.indices.reversed) {
73 * if (viewGroup[i] is SomeView) {
74 * viewGroup.removeViewAt(i)
75 * }
76 * }
77 * ```
78 *
79 * Or to determine if an index is valid:
80 * ```kotlin
81 * if (2 in viewGroup.indices) {
82 * // Do something…
83 * }
84 * ```
85 */
86 public inline val ViewGroup.indices: IntRange
87 get() = 0 until childCount
88
89 /** Returns a [MutableIterator] over the views in this view group. */
iteratornull90 public operator fun ViewGroup.iterator(): MutableIterator<View> =
91 object : MutableIterator<View> {
92 private var index = 0
93
94 override fun hasNext() = index < childCount
95
96 override fun next() = getChildAt(index++) ?: throw IndexOutOfBoundsException()
97
98 override fun remove() = removeViewAt(--index)
99 }
100
101 /**
102 * Returns a [Sequence] over the immediate child views in this view group.
103 *
104 * @see View.allViews
105 * @see ViewGroup.descendants
106 */
107 public val ViewGroup.children: Sequence<View>
108 get() =
109 object : Sequence<View> {
iteratornull110 override fun iterator() = this@children.iterator()
111 }
112
113 /**
114 * Returns a [Sequence] over the child views in this view group recursively.
115 *
116 * This performs a depth-first traversal. A view with no children will return a zero-element
117 * sequence.
118 *
119 * For example, to efficiently filter views within the hierarchy using a predicate:
120 * ```
121 * fun ViewGroup.findViewTreeIterator(predicate: (View) -> Boolean): Sequence<View> {
122 * return sequenceOf(this)
123 * .plus(descendantsTree)
124 * .filter { predicate(it) }
125 * }
126 * ```
127 *
128 * @see View.allViews
129 * @see ViewGroup.children
130 * @see View.ancestors
131 */
132 public val ViewGroup.descendants: Sequence<View>
133 get() = Sequence {
134 TreeIterator(children.iterator()) { child -> (child as? ViewGroup)?.children?.iterator() }
135 }
136
137 /**
138 * Lazy iterator for iterating through an abstract hierarchy.
139 *
140 * @param rootIterator Iterator for root elements of hierarchy
141 * @param getChildIterator Function which returns a child iterator for the current item if the
142 * current item has a child or `null` otherwise
143 */
144 internal class TreeIterator<T>(
145 rootIterator: Iterator<T>,
146 private val getChildIterator: ((T) -> Iterator<T>?)
147 ) : Iterator<T> {
148 private val stack = mutableListOf<Iterator<T>>()
149
150 private var iterator: Iterator<T> = rootIterator
151
hasNextnull152 override fun hasNext(): Boolean {
153 return iterator.hasNext()
154 }
155
nextnull156 override fun next(): T {
157 val item = iterator.next()
158 prepareNextIterator(item)
159 return item
160 }
161
162 /** Calculates next iterator for [item]. */
prepareNextIteratornull163 private fun prepareNextIterator(item: T) {
164 // If current item has a child, then get the child iterator and save the current iterator to
165 // the stack. Otherwise, if current iterator has no more elements then restore the parent
166 // iterator from the stack.
167 val childIterator = getChildIterator(item)
168 if (childIterator != null && childIterator.hasNext()) {
169 stack.add(iterator)
170 iterator = childIterator
171 } else {
172 while (!iterator.hasNext() && stack.isNotEmpty()) {
173 iterator = stack.last()
174 // MutableCollections.removeLast() is shadowed by java.util.list.removeAt()
175 // which was added in sdk 35 making this call unsafe
176 // stack.removeLast()
177 stack.removeLastKt()
178 }
179 }
180 }
181 }
182
183 /**
184 * Sets the margins in the ViewGroup's MarginLayoutParams. This version of the method sets all axes
185 * to the provided size.
186 *
187 * @see ViewGroup.MarginLayoutParams.setMargins
188 */
setMarginsnull189 public inline fun ViewGroup.MarginLayoutParams.setMargins(@Px size: Int) {
190 setMargins(size, size, size, size)
191 }
192
193 /**
194 * Updates the margins in the [ViewGroup]'s [ViewGroup.MarginLayoutParams]. This version of the
195 * method allows using named parameters to just set one or more axes.
196 *
197 * @see ViewGroup.MarginLayoutParams.setMargins
198 */
updateMarginsnull199 public inline fun ViewGroup.MarginLayoutParams.updateMargins(
200 @Px left: Int = leftMargin,
201 @Px top: Int = topMargin,
202 @Px right: Int = rightMargin,
203 @Px bottom: Int = bottomMargin
204 ) {
205 setMargins(left, top, right, bottom)
206 }
207
208 /**
209 * Updates the relative margins in the ViewGroup's MarginLayoutParams. This version of the method
210 * allows using named parameters to just set one or more axes.
211 *
212 * Note that this inline method references platform APIs added in API 17 and may raise runtime
213 * verification warnings on earlier platforms. See Chromium's guide to
214 * [Class Verification Failures](https://chromium.googlesource.com/chromium/src/+/HEAD/build/android/docs/class_verification_failures.md)
215 * for more information.
216 *
217 * @see ViewGroup.MarginLayoutParams.setMargins
218 */
updateMarginsRelativenull219 public inline fun ViewGroup.MarginLayoutParams.updateMarginsRelative(
220 @Px start: Int = marginStart,
221 @Px top: Int = topMargin,
222 @Px end: Int = marginEnd,
223 @Px bottom: Int = bottomMargin
224 ) {
225 marginStart = start
226 topMargin = top
227 marginEnd = end
228 bottomMargin = bottom
229 }
230