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