View.kt revision 57c7350b439c472eb39f55fea4511b530d23f982
1/*
2 * 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
19package androidx.core.view
20
21import android.graphics.Bitmap
22import android.view.View
23import android.view.ViewGroup
24import android.view.ViewTreeObserver
25import android.view.accessibility.AccessibilityEvent
26import androidx.annotation.Px
27import androidx.annotation.RequiresApi
28import androidx.annotation.StringRes
29import androidx.core.graphics.applyCanvas
30
31/**
32 * Performs the given action when this view is next laid out.
33 *
34 * @see doOnLayout
35 */
36inline fun View.doOnNextLayout(crossinline action: (view: View) -> Unit) {
37    addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
38        override fun onLayoutChange(
39            view: View,
40            left: Int,
41            top: Int,
42            right: Int,
43            bottom: Int,
44            oldLeft: Int,
45            oldTop: Int,
46            oldRight: Int,
47            oldBottom: Int
48        ) {
49            view.removeOnLayoutChangeListener(this)
50            action(view)
51        }
52    })
53}
54
55/**
56 * Performs the given action when this view is laid out. If the view has been laid out and it
57 * has not requested a layout, the action will be performed straight away, otherwise the
58 * action will be performed after the view is next laid out.
59 *
60 * @see doOnNextLayout
61 */
62inline fun View.doOnLayout(crossinline action: (view: View) -> Unit) {
63    if (ViewCompat.isLaidOut(this) && !isLayoutRequested) {
64        action(this)
65    } else {
66        doOnNextLayout {
67            action(it)
68        }
69    }
70}
71
72/**
73 * Performs the given action when the view tree is about to be drawn.
74 */
75inline fun View.doOnPreDraw(crossinline action: (view: View) -> Unit) {
76    val vto = viewTreeObserver
77    vto.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
78        override fun onPreDraw(): Boolean {
79            action(this@doOnPreDraw)
80            when {
81                vto.isAlive -> vto.removeOnPreDrawListener(this)
82                else -> viewTreeObserver.removeOnPreDrawListener(this)
83            }
84            return true
85        }
86    })
87}
88
89/**
90 * Sends [AccessibilityEvent] of type [AccessibilityEvent.TYPE_ANNOUNCEMENT].
91 *
92 * @see View.announceForAccessibility
93 */
94@RequiresApi(16)
95inline fun View.announceForAccessibility(@StringRes resource: Int) {
96    val announcement = resources.getString(resource)
97    announceForAccessibility(announcement)
98}
99
100/**
101 * Updates this view's relative padding. This version of the method allows using named parameters
102 * to just set one or more axes.
103 *
104 * @see View.setPaddingRelative
105 */
106@RequiresApi(17)
107inline fun View.updatePaddingRelative(
108    @Px start: Int = paddingStart,
109    @Px top: Int = paddingTop,
110    @Px end: Int = paddingEnd,
111    @Px bottom: Int = paddingBottom
112) {
113    setPaddingRelative(start, top, end, bottom)
114}
115
116/**
117 * Updates this view's padding. This version of the method allows using named parameters
118 * to just set one or more axes.
119 *
120 * @see View.setPadding
121 */
122inline fun View.updatePadding(
123    @Px left: Int = paddingLeft,
124    @Px top: Int = paddingTop,
125    @Px right: Int = paddingRight,
126    @Px bottom: Int = paddingBottom
127) {
128    setPadding(left, top, right, bottom)
129}
130
131/**
132 * Sets the view's padding. This version of the method sets all axes to the provided size.
133 *
134 * @see View.setPadding
135 */
136inline fun View.setPadding(@Px size: Int) {
137    setPadding(size, size, size, size)
138}
139
140/**
141 * Version of [View.postDelayed] which re-orders the parameters, allowing the action to be placed
142 * outside of parentheses.
143 *
144 * ```
145 * view.postDelayed(200) {
146 *     doSomething()
147 * }
148 * ```
149 *
150 * @return the created Runnable
151 */
152inline fun View.postDelayed(delayInMillis: Long, crossinline action: () -> Unit): Runnable {
153    val runnable = Runnable { action() }
154    postDelayed(runnable, delayInMillis)
155    return runnable
156}
157
158/**
159 * Version of [View.postOnAnimationDelayed] which re-orders the parameters, allowing the action
160 * to be placed outside of parentheses.
161 *
162 * ```
163 * view.postOnAnimationDelayed(16) {
164 *     doSomething()
165 * }
166 * ```
167 *
168 * @return the created Runnable
169 */
170@RequiresApi(16)
171inline fun View.postOnAnimationDelayed(
172    delayInMillis: Long,
173    crossinline action: () -> Unit
174): Runnable {
175    val runnable = Runnable { action() }
176    postOnAnimationDelayed(runnable, delayInMillis)
177    return runnable
178}
179
180/**
181 * Return a [Bitmap] representation of this [View].
182 *
183 * The resulting bitmap will be the same width and height as this view's current layout
184 * dimensions. This does not take into account any transformations such as scale or translation.
185 *
186 * Note, this will use the software rendering pipeline to draw the view to the bitmap. This may
187 * result with different drawing to what is rendered on a hardware accelerated canvas (such as
188 * the device screen).
189 *
190 * If this view has not been laid out this method will throw a [IllegalStateException].
191 *
192 * @param config Bitmap config of the desired bitmap. Defaults to [Bitmap.Config.ARGB_8888].
193 */
194fun View.toBitmap(config: Bitmap.Config = Bitmap.Config.ARGB_8888): Bitmap {
195    if (!ViewCompat.isLaidOut(this)) {
196        throw IllegalStateException("View needs to be laid out before calling toBitmap()")
197    }
198    return Bitmap.createBitmap(width, height, config).applyCanvas {
199        translate(-scrollX.toFloat(), -scrollY.toFloat())
200        draw(this)
201    }
202}
203
204/**
205 * Returns true when this view's visibility is [View.VISIBLE], false otherwise.
206 *
207 * ```
208 * if (view.isVisible) {
209 *     // Behavior...
210 * }
211 * ```
212 *
213 * Setting this property to true sets the visibility to [View.VISIBLE], false to [View.GONE].
214 *
215 * ```
216 * view.isVisible = true
217 * ```
218 */
219inline var View.isVisible: Boolean
220    get() = visibility == View.VISIBLE
221    set(value) {
222        visibility = if (value) View.VISIBLE else View.GONE
223    }
224
225/**
226 * Returns true when this view's visibility is [View.INVISIBLE], false otherwise.
227 *
228 * ```
229 * if (view.isInvisible) {
230 *     // Behavior...
231 * }
232 * ```
233 *
234 * Setting this property to true sets the visibility to [View.INVISIBLE], false to [View.VISIBLE].
235 *
236 * ```
237 * view.isInvisible = true
238 * ```
239 */
240inline var View.isInvisible: Boolean
241    get() = visibility == View.INVISIBLE
242    set(value) {
243        visibility = if (value) View.INVISIBLE else View.VISIBLE
244    }
245
246/**
247 * Returns true when this view's visibility is [View.GONE], false otherwise.
248 *
249 * ```
250 * if (view.isGone) {
251 *     // Behavior...
252 * }
253 * ```
254 *
255 * Setting this property to true sets the visibility to [View.GONE], false to [View.VISIBLE].
256 *
257 * ```
258 * view.isGone = true
259 * ```
260 */
261inline var View.isGone: Boolean
262    get() = visibility == View.GONE
263    set(value) {
264        visibility = if (value) View.GONE else View.VISIBLE
265    }
266
267/**
268 * Executes [block] with the View's layoutParams and reassigns the layoutParams with the
269 * updated version.
270 *
271 * @see View.getLayoutParams
272 * @see View.setLayoutParams
273 **/
274inline fun View.updateLayoutParams(block: ViewGroup.LayoutParams.() -> Unit) {
275    updateLayoutParams<ViewGroup.LayoutParams>(block)
276}
277
278/**
279 * Executes [block] with a typed version of the View's layoutParams and reassigns the
280 * layoutParams with the updated version.
281 *
282 * @see View.getLayoutParams
283 * @see View.setLayoutParams
284 **/
285@JvmName("updateLayoutParamsTyped")
286inline fun <reified T : ViewGroup.LayoutParams> View.updateLayoutParams(block: T.() -> Unit) {
287    val params = layoutParams as T
288    block(params)
289    layoutParams = params
290}
291