blob: 330263407c7bdb95cfb8e518c81f76c1b0dbfbf9 [file] [log] [blame]
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.core.view;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.ClipData;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.Display;
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeProvider;
import androidx.annotation.FloatRange;
import androidx.annotation.IdRes;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.core.view.accessibility.AccessibilityNodeProviderCompat;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Helper for accessing features in {@link View}.
*/
public class ViewCompat {
private static final String TAG = "ViewCompat";
/** @hide */
@RestrictTo(LIBRARY_GROUP)
@IntDef({View.FOCUS_LEFT, View.FOCUS_UP, View.FOCUS_RIGHT, View.FOCUS_DOWN,
View.FOCUS_FORWARD, View.FOCUS_BACKWARD})
@Retention(RetentionPolicy.SOURCE)
public @interface FocusDirection {}
/** @hide */
@RestrictTo(LIBRARY_GROUP)
@IntDef({View.FOCUS_LEFT, View.FOCUS_UP, View.FOCUS_RIGHT, View.FOCUS_DOWN})
@Retention(RetentionPolicy.SOURCE)
public @interface FocusRealDirection {}
/** @hide */
@RestrictTo(LIBRARY_GROUP)
@IntDef({View.FOCUS_FORWARD, View.FOCUS_BACKWARD})
@Retention(RetentionPolicy.SOURCE)
public @interface FocusRelativeDirection {}
@IntDef({OVER_SCROLL_ALWAYS, OVER_SCROLL_IF_CONTENT_SCROLLS, OVER_SCROLL_NEVER})
@Retention(RetentionPolicy.SOURCE)
private @interface OverScroll {}
/**
* Always allow a user to over-scroll this view, provided it is a
* view that can scroll.
* @deprecated Use {@link View#OVER_SCROLL_ALWAYS} directly. This constant will be removed in
* a future release.
*/
@Deprecated
public static final int OVER_SCROLL_ALWAYS = 0;
/**
* Allow a user to over-scroll this view only if the content is large
* enough to meaningfully scroll, provided it is a view that can scroll.
* @deprecated Use {@link View#OVER_SCROLL_IF_CONTENT_SCROLLS} directly. This constant will be
* removed in a future release.
*/
@Deprecated
public static final int OVER_SCROLL_IF_CONTENT_SCROLLS = 1;
/**
* Never allow a user to over-scroll this view.
* @deprecated Use {@link View#OVER_SCROLL_NEVER} directly. This constant will be removed in
* a future release.
*/
@Deprecated
public static final int OVER_SCROLL_NEVER = 2;
@TargetApi(Build.VERSION_CODES.O)
@IntDef({
View.IMPORTANT_FOR_AUTOFILL_AUTO,
View.IMPORTANT_FOR_AUTOFILL_YES,
View.IMPORTANT_FOR_AUTOFILL_NO,
View.IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS,
View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS
})
@Retention(RetentionPolicy.SOURCE)
private @interface AutofillImportance {}
@IntDef({
IMPORTANT_FOR_ACCESSIBILITY_AUTO,
IMPORTANT_FOR_ACCESSIBILITY_YES,
IMPORTANT_FOR_ACCESSIBILITY_NO,
IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
})
@Retention(RetentionPolicy.SOURCE)
private @interface ImportantForAccessibility {}
/**
* Automatically determine whether a view is important for accessibility.
*/
public static final int IMPORTANT_FOR_ACCESSIBILITY_AUTO = 0x00000000;
/**
* The view is important for accessibility.
*/
public static final int IMPORTANT_FOR_ACCESSIBILITY_YES = 0x00000001;
/**
* The view is not important for accessibility.
*/
public static final int IMPORTANT_FOR_ACCESSIBILITY_NO = 0x00000002;
/**
* The view is not important for accessibility, nor are any of its
* descendant views.
*/
public static final int IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS = 0x00000004;
@IntDef({
ACCESSIBILITY_LIVE_REGION_NONE,
ACCESSIBILITY_LIVE_REGION_POLITE,
ACCESSIBILITY_LIVE_REGION_ASSERTIVE
})
@Retention(RetentionPolicy.SOURCE)
private @interface AccessibilityLiveRegion {}
/**
* Live region mode specifying that accessibility services should not
* automatically announce changes to this view. This is the default live
* region mode for most views.
* <p>
* Use with {@link ViewCompat#setAccessibilityLiveRegion(View, int)}.
*/
public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0x00000000;
/**
* Live region mode specifying that accessibility services should announce
* changes to this view.
* <p>
* Use with {@link ViewCompat#setAccessibilityLiveRegion(View, int)}.
*/
public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 0x00000001;
/**
* Live region mode specifying that accessibility services should interrupt
* ongoing speech to immediately announce changes to this view.
* <p>
* Use with {@link ViewCompat#setAccessibilityLiveRegion(View, int)}.
*/
public static final int ACCESSIBILITY_LIVE_REGION_ASSERTIVE = 0x00000002;
@IntDef({View.LAYER_TYPE_NONE, View.LAYER_TYPE_SOFTWARE, View.LAYER_TYPE_HARDWARE})
@Retention(RetentionPolicy.SOURCE)
private @interface LayerType {}
/**
* Indicates that the view does not have a layer.
*
* @deprecated Use {@link View#LAYER_TYPE_NONE} directly.
*/
@Deprecated
public static final int LAYER_TYPE_NONE = 0;
/**
* <p>Indicates that the view has a software layer. A software layer is backed
* by a bitmap and causes the view to be rendered using Android's software
* rendering pipeline, even if hardware acceleration is enabled.</p>
*
* <p>Software layers have various usages:</p>
* <p>When the application is not using hardware acceleration, a software layer
* is useful to apply a specific color filter and/or blending mode and/or
* translucency to a view and all its children.</p>
* <p>When the application is using hardware acceleration, a software layer
* is useful to render drawing primitives not supported by the hardware
* accelerated pipeline. It can also be used to cache a complex view tree
* into a texture and reduce the complexity of drawing operations. For instance,
* when animating a complex view tree with a translation, a software layer can
* be used to render the view tree only once.</p>
* <p>Software layers should be avoided when the affected view tree updates
* often. Every update will require to re-render the software layer, which can
* potentially be slow (particularly when hardware acceleration is turned on
* since the layer will have to be uploaded into a hardware texture after every
* update.)</p>
*
* @deprecated Use {@link View#LAYER_TYPE_SOFTWARE} directly.
*/
@Deprecated
public static final int LAYER_TYPE_SOFTWARE = 1;
/**
* <p>Indicates that the view has a hardware layer. A hardware layer is backed
* by a hardware specific texture (generally Frame Buffer Objects or FBO on
* OpenGL hardware) and causes the view to be rendered using Android's hardware
* rendering pipeline, but only if hardware acceleration is turned on for the
* view hierarchy. When hardware acceleration is turned off, hardware layers
* behave exactly as {@link View#LAYER_TYPE_SOFTWARE software layers}.</p>
*
* <p>A hardware layer is useful to apply a specific color filter and/or
* blending mode and/or translucency to a view and all its children.</p>
* <p>A hardware layer can be used to cache a complex view tree into a
* texture and reduce the complexity of drawing operations. For instance,
* when animating a complex view tree with a translation, a hardware layer can
* be used to render the view tree only once.</p>
* <p>A hardware layer can also be used to increase the rendering quality when
* rotation transformations are applied on a view. It can also be used to
* prevent potential clipping issues when applying 3D transforms on a view.</p>
*
* @deprecated Use {@link View#LAYER_TYPE_HARDWARE} directly.
*/
@Deprecated
public static final int LAYER_TYPE_HARDWARE = 2;
@IntDef({
LAYOUT_DIRECTION_LTR,
LAYOUT_DIRECTION_RTL,
LAYOUT_DIRECTION_INHERIT,
LAYOUT_DIRECTION_LOCALE})
@Retention(RetentionPolicy.SOURCE)
private @interface LayoutDirectionMode {}
@IntDef({
LAYOUT_DIRECTION_LTR,
LAYOUT_DIRECTION_RTL
})
@Retention(RetentionPolicy.SOURCE)
private @interface ResolvedLayoutDirectionMode {}
/**
* Horizontal layout direction of this view is from Left to Right.
*/
public static final int LAYOUT_DIRECTION_LTR = 0;
/**
* Horizontal layout direction of this view is from Right to Left.
*/
public static final int LAYOUT_DIRECTION_RTL = 1;
/**
* Horizontal layout direction of this view is inherited from its parent.
* Use with {@link #setLayoutDirection}.
*/
public static final int LAYOUT_DIRECTION_INHERIT = 2;
/**
* Horizontal layout direction of this view is from deduced from the default language
* script for the locale. Use with {@link #setLayoutDirection}.
*/
public static final int LAYOUT_DIRECTION_LOCALE = 3;
/**
* Bits of {@link #getMeasuredWidthAndState} and
* {@link #getMeasuredWidthAndState} that provide the actual measured size.
*
* @deprecated Use {@link View#MEASURED_SIZE_MASK} directly.
*/
@Deprecated
public static final int MEASURED_SIZE_MASK = 0x00ffffff;
/**
* Bits of {@link #getMeasuredWidthAndState} and
* {@link #getMeasuredWidthAndState} that provide the additional state bits.
*
* @deprecated Use {@link View#MEASURED_STATE_MASK} directly.
*/
@Deprecated
public static final int MEASURED_STATE_MASK = 0xff000000;
/**
* Bit shift of {@link #MEASURED_STATE_MASK} to get to the height bits
* for functions that combine both width and height into a single int,
* such as {@link #getMeasuredState} and the childState argument of
* {@link #resolveSizeAndState(int, int, int)}.
*
* @deprecated Use {@link View#MEASURED_HEIGHT_STATE_SHIFT} directly.
*/
@Deprecated
public static final int MEASURED_HEIGHT_STATE_SHIFT = 16;
/**
* Bit of {@link #getMeasuredWidthAndState} and
* {@link #getMeasuredWidthAndState} that indicates the measured size
* is smaller that the space the view would like to have.
*
* @deprecated Use {@link View#MEASURED_STATE_TOO_SMALL} directly.
*/
@Deprecated
public static final int MEASURED_STATE_TOO_SMALL = 0x01000000;
/**
* @hide
*/
@IntDef(value = {SCROLL_AXIS_NONE, SCROLL_AXIS_HORIZONTAL, SCROLL_AXIS_VERTICAL}, flag = true)
@Retention(RetentionPolicy.SOURCE)
@RestrictTo(LIBRARY_GROUP)
public @interface ScrollAxis {}
/**
* Indicates no axis of view scrolling.
*/
public static final int SCROLL_AXIS_NONE = 0;
/**
* Indicates scrolling along the horizontal axis.
*/
public static final int SCROLL_AXIS_HORIZONTAL = 1 << 0;
/**
* Indicates scrolling along the vertical axis.
*/
public static final int SCROLL_AXIS_VERTICAL = 1 << 1;
/**
* @hide
*/
@IntDef({TYPE_TOUCH, TYPE_NON_TOUCH})
@Retention(RetentionPolicy.SOURCE)
@RestrictTo(LIBRARY_GROUP)
public @interface NestedScrollType {}
/**
* Indicates that the input type for the gesture is from a user touching the screen.
*/
public static final int TYPE_TOUCH = 0;
/**
* Indicates that the input type for the gesture is caused by something which is not a user
* touching a screen. This is usually from a fling which is settling.
*/
public static final int TYPE_NON_TOUCH = 1;
/** @hide */
@RestrictTo(LIBRARY_GROUP)
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true,
value = {
SCROLL_INDICATOR_TOP,
SCROLL_INDICATOR_BOTTOM,
SCROLL_INDICATOR_LEFT,
SCROLL_INDICATOR_RIGHT,
SCROLL_INDICATOR_START,
SCROLL_INDICATOR_END,
})
public @interface ScrollIndicators {}
/**
* Scroll indicator direction for the top edge of the view.
*
* @see #setScrollIndicators(View, int)
* @see #setScrollIndicators(View, int, int)
* @see #getScrollIndicators(View)
*/
public static final int SCROLL_INDICATOR_TOP = 0x1;
/**
* Scroll indicator direction for the bottom edge of the view.
*
* @see #setScrollIndicators(View, int)
* @see #setScrollIndicators(View, int, int)
* @see #getScrollIndicators(View)
*/
public static final int SCROLL_INDICATOR_BOTTOM = 0x2;
/**
* Scroll indicator direction for the left edge of the view.
*
* @see #setScrollIndicators(View, int)
* @see #setScrollIndicators(View, int, int)
* @see #getScrollIndicators(View)
*/
public static final int SCROLL_INDICATOR_LEFT = 0x4;
/**
* Scroll indicator direction for the right edge of the view.
*
* @see #setScrollIndicators(View, int)
* @see #setScrollIndicators(View, int, int)
* @see #getScrollIndicators(View)
*/
public static final int SCROLL_INDICATOR_RIGHT = 0x8;
/**
* Scroll indicator direction for the starting edge of the view.
*
* @see #setScrollIndicators(View, int)
* @see #setScrollIndicators(View, int, int)
* @see #getScrollIndicators(View)
*/
public static final int SCROLL_INDICATOR_START = 0x10;
/**
* Scroll indicator direction for the ending edge of the view.
*
* @see #setScrollIndicators(View, int)
* @see #setScrollIndicators(View, int, int)
* @see #getScrollIndicators(View)
*/
public static final int SCROLL_INDICATOR_END = 0x20;
private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
private static Field sMinWidthField;
private static boolean sMinWidthFieldFetched;
private static Field sMinHeightField;
private static boolean sMinHeightFieldFetched;
private static Method sDispatchStartTemporaryDetach;
private static Method sDispatchFinishTemporaryDetach;
private static boolean sTempDetachBound;
private static WeakHashMap<View, String> sTransitionNameMap;
private static WeakHashMap<View, ViewPropertyAnimatorCompat> sViewPropertyAnimatorMap = null;
private static Method sChildrenDrawingOrderMethod;
private static Field sAccessibilityDelegateField;
private static boolean sAccessibilityDelegateCheckFailed = false;
private static ThreadLocal<Rect> sThreadLocalRect;
private static Rect getEmptyTempRect() {
if (sThreadLocalRect == null) {
sThreadLocalRect = new ThreadLocal<>();
}
Rect rect = sThreadLocalRect.get();
if (rect == null) {
rect = new Rect();
sThreadLocalRect.set(rect);
}
rect.setEmpty();
return rect;
}
/**
* Check if this view can be scrolled horizontally in a certain direction.
*
* @param view The View against which to invoke the method.
* @param direction Negative to check scrolling left, positive to check scrolling right.
* @return true if this view can be scrolled in the specified direction, false otherwise.
*
* @deprecated Use {@link View#canScrollHorizontally(int)} directly.
*/
@Deprecated
public static boolean canScrollHorizontally(View view, int direction) {
return view.canScrollHorizontally(direction);
}
/**
* Check if this view can be scrolled vertically in a certain direction.
*
* @param view The View against which to invoke the method.
* @param direction Negative to check scrolling up, positive to check scrolling down.
* @return true if this view can be scrolled in the specified direction, false otherwise.
*
* @deprecated Use {@link View#canScrollVertically(int)} directly.
*/
@Deprecated
public static boolean canScrollVertically(View view, int direction) {
return view.canScrollVertically(direction);
}
/**
* Returns the over-scroll mode for this view. The result will be
* one of {@link #OVER_SCROLL_ALWAYS} (default), {@link #OVER_SCROLL_IF_CONTENT_SCROLLS}
* (allow over-scrolling only if the view content is larger than the container),
* or {@link #OVER_SCROLL_NEVER}.
*
* @param v The View against which to invoke the method.
* @return This view's over-scroll mode.
* @deprecated Call {@link View#getOverScrollMode()} directly. This method will be
* removed in a future release.
*/
@Deprecated
@OverScroll
public static int getOverScrollMode(View v) {
//noinspection ResourceType
return v.getOverScrollMode();
}
/**
* Set the over-scroll mode for this view. Valid over-scroll modes are
* {@link #OVER_SCROLL_ALWAYS} (default), {@link #OVER_SCROLL_IF_CONTENT_SCROLLS}
* (allow over-scrolling only if the view content is larger than the container),
* or {@link #OVER_SCROLL_NEVER}.
*
* Setting the over-scroll mode of a view will have an effect only if the
* view is capable of scrolling.
*
* @param v The View against which to invoke the method.
* @param overScrollMode The new over-scroll mode for this view.
* @deprecated Call {@link View#setOverScrollMode(int)} directly. This method will be
* removed in a future release.
*/
@Deprecated
public static void setOverScrollMode(View v, @OverScroll int overScrollMode) {
v.setOverScrollMode(overScrollMode);
}
/**
* Called from {@link View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)}
* giving a chance to this View to populate the accessibility event with its
* text content. While this method is free to modify event
* attributes other than text content, doing so should normally be performed in
* {@link View#onInitializeAccessibilityEvent(AccessibilityEvent)}.
* <p>
* Example: Adding formatted date string to an accessibility event in addition
* to the text added by the super implementation:
* <pre> public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
* super.onPopulateAccessibilityEvent(event);
* final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY;
* String selectedDateUtterance = DateUtils.formatDateTime(mContext,
* mCurrentDate.getTimeInMillis(), flags);
* event.getText().add(selectedDateUtterance);
* }</pre>
* <p>
* If an {@link AccessibilityDelegateCompat} has been specified via calling
* {@link ViewCompat#setAccessibilityDelegate(View, AccessibilityDelegateCompat)} its
* {@link AccessibilityDelegateCompat#onPopulateAccessibilityEvent(View, AccessibilityEvent)}
* is responsible for handling this call.
* </p>
* <p class="note"><strong>Note:</strong> Always call the super implementation before adding
* information to the event, in case the default implementation has basic information to add.
* </p>
*
* @param v The View against which to invoke the method.
* @param event The accessibility event which to populate.
*
* @see View#sendAccessibilityEvent(int)
* @see View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)
*
* @deprecated Call {@link View#onPopulateAccessibilityEvent(AccessibilityEvent)} directly.
* This method will be removed in a future release.
*/
@Deprecated
public static void onPopulateAccessibilityEvent(View v, AccessibilityEvent event) {
v.onPopulateAccessibilityEvent(event);
}
/**
* Initializes an {@link AccessibilityEvent} with information about
* this View which is the event source. In other words, the source of
* an accessibility event is the view whose state change triggered firing
* the event.
* <p>
* Example: Setting the password property of an event in addition
* to properties set by the super implementation:
* <pre> public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
* super.onInitializeAccessibilityEvent(event);
* event.setPassword(true);
* }</pre>
* <p>
* If an {@link AccessibilityDelegateCompat} has been specified via calling
* {@link ViewCompat#setAccessibilityDelegate(View, AccessibilityDelegateCompat)}, its
* {@link AccessibilityDelegateCompat#onInitializeAccessibilityEvent(View, AccessibilityEvent)}
* is responsible for handling this call.
*
* @param v The View against which to invoke the method.
* @param event The event to initialize.
*
* @see View#sendAccessibilityEvent(int)
* @see View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)
*
* @deprecated Call {@link View#onInitializeAccessibilityEvent(AccessibilityEvent)} directly.
* This method will be removed in a future release.
*/
@Deprecated
public static void onInitializeAccessibilityEvent(View v, AccessibilityEvent event) {
v.onInitializeAccessibilityEvent(event);
}
/**
* Initializes an {@link AccessibilityNodeInfoCompat} with information
* about this view. The base implementation sets:
* <ul>
* <li>{@link AccessibilityNodeInfoCompat#setParent(View)},</li>
* <li>{@link AccessibilityNodeInfoCompat#setBoundsInParent(Rect)},</li>
* <li>{@link AccessibilityNodeInfoCompat#setBoundsInScreen(Rect)},</li>
* <li>{@link AccessibilityNodeInfoCompat#setPackageName(CharSequence)},</li>
* <li>{@link AccessibilityNodeInfoCompat#setClassName(CharSequence)},</li>
* <li>{@link AccessibilityNodeInfoCompat#setContentDescription(CharSequence)},</li>
* <li>{@link AccessibilityNodeInfoCompat#setEnabled(boolean)},</li>
* <li>{@link AccessibilityNodeInfoCompat#setClickable(boolean)},</li>
* <li>{@link AccessibilityNodeInfoCompat#setFocusable(boolean)},</li>
* <li>{@link AccessibilityNodeInfoCompat#setFocused(boolean)},</li>
* <li>{@link AccessibilityNodeInfoCompat#setLongClickable(boolean)},</li>
* <li>{@link AccessibilityNodeInfoCompat#setSelected(boolean)},</li>
* </ul>
* <p>
* If an {@link AccessibilityDelegateCompat} has been specified via calling
* {@link ViewCompat#setAccessibilityDelegate(View, AccessibilityDelegateCompat)}, its
* {@link AccessibilityDelegateCompat#onInitializeAccessibilityNodeInfo(View, AccessibilityNodeInfoCompat)}
* method is responsible for handling this call.
*
* @param v The View against which to invoke the method.
* @param info The instance to initialize.
*/
public static void onInitializeAccessibilityNodeInfo(@NonNull View v,
AccessibilityNodeInfoCompat info) {
v.onInitializeAccessibilityNodeInfo(info.unwrap());
}
/**
* Sets a delegate for implementing accessibility support via composition
* (as opposed to inheritance). For more details, see
* {@link AccessibilityDelegateCompat}.
* <p>
* <strong>Note:</strong> On platform versions prior to
* {@link android.os.Build.VERSION_CODES#M API 23}, delegate methods on
* views in the {@code android.widget.*} package are called <i>before</i>
* host methods. This prevents certain properties such as class name from
* being modified by overriding
* {@link AccessibilityDelegateCompat#onInitializeAccessibilityNodeInfo(View, AccessibilityNodeInfoCompat)},
* as any changes will be overwritten by the host class.
* <p>
* Starting in {@link android.os.Build.VERSION_CODES#M API 23}, delegate
* methods are called <i>after</i> host methods, which all properties to be
* modified without being overwritten by the host class.
*
* @param delegate the object to which accessibility method calls should be
* delegated
* @see AccessibilityDelegateCompat
*/
public static void setAccessibilityDelegate(@NonNull View v,
AccessibilityDelegateCompat delegate) {
v.setAccessibilityDelegate(delegate == null ? null : delegate.getBridge());
}
/**
* Sets the hints that help an {@link android.service.autofill.AutofillService} determine how
* to autofill the view with the user's data.
*
* <p>Typically, there is only one way to autofill a view, but there could be more than one.
* For example, if the application accepts either an username or email address to identify
* an user.
*
* <p>These hints are not validated by the Android System, but passed "as is" to the service.
* Hence, they can have any value, but it's recommended to use the {@code AUTOFILL_HINT_}
* constants such as:
* {@link View#AUTOFILL_HINT_USERNAME}, {@link View#AUTOFILL_HINT_PASSWORD},
* {@link View#AUTOFILL_HINT_EMAIL_ADDRESS},
* {@link View#AUTOFILL_HINT_NAME},
* {@link View#AUTOFILL_HINT_PHONE},
* {@link View#AUTOFILL_HINT_POSTAL_ADDRESS}, {@link View#AUTOFILL_HINT_POSTAL_CODE},
* {@link View#AUTOFILL_HINT_CREDIT_CARD_NUMBER},
* {@link View#AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE},
* {@link View#AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE},
* {@link View#AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY},
* {@link View#AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH} or
* {@link View#AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR}.
*
* <p>This method is only supported on API >= 26.
* On API 25 and below, it is a no-op</p>
*
* @param autofillHints The autofill hints to set. If the array is emtpy, {@code null} is set.
* @attr ref android.R.styleable#View_autofillHints
*/
public static void setAutofillHints(@NonNull View v, @Nullable String... autofillHints) {
if (Build.VERSION.SDK_INT >= 26) {
v.setAutofillHints(autofillHints);
}
}
/**
* Gets the mode for determining whether this view is important for autofill.
*
* <p>See {@link #setImportantForAutofill(View, int)} and {@link #isImportantForAutofill(View)}
* for more info about this mode.
*
* <p>This method is only supported on API >= 26.
* On API 25 and below, it will always return {@link View#IMPORTANT_FOR_AUTOFILL_AUTO}.</p>
*
* @return {@link View#IMPORTANT_FOR_AUTOFILL_AUTO} by default, or value passed to
* {@link #setImportantForAutofill(View, int)}.
*
* @attr ref android.R.styleable#View_importantForAutofill
*/
@SuppressLint("InlinedApi")
public static @AutofillImportance int getImportantForAutofill(@NonNull View v) {
if (Build.VERSION.SDK_INT >= 26) {
return v.getImportantForAutofill();
}
return View.IMPORTANT_FOR_AUTOFILL_AUTO;
}
/**
* Sets the mode for determining whether this view is considered important for autofill.
*
* <p>The platform determines the importance for autofill automatically but you
* can use this method to customize the behavior. For example:
*
* <ol>
* <li>When the view contents is irrelevant for autofill (for example, a text field used in a
* "Captcha" challenge), it should be {@link View#IMPORTANT_FOR_AUTOFILL_NO}.
* <li>When both the view and its children are irrelevant for autofill (for example, the root
* view of an activity containing a spreadhseet editor), it should be
* {@link View#IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS}.
* <li>When the view content is relevant for autofill but its children aren't (for example,
* a credit card expiration date represented by a custom view that overrides the proper
* autofill methods and has 2 children representing the month and year), it should
* be {@link View#IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS}.
* </ol>
*
* <p><b>NOTE:</strong> setting the mode as does {@link View#IMPORTANT_FOR_AUTOFILL_NO} or
* {@link View#IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS} does not guarantee the view (and
* its children) will be always be considered not important; for example, when the user
* explicitly makes an autofill request, all views are considered important. See
* {@link #isImportantForAutofill(View)} for more details about how the View's importance for
* autofill is used.
*
* <p>This method is only supported on API >= 26.
* On API 25 and below, it is a no-op</p>
*
*
* @param mode {@link View#IMPORTANT_FOR_AUTOFILL_AUTO},
* {@link View#IMPORTANT_FOR_AUTOFILL_YES},
* {@link View#IMPORTANT_FOR_AUTOFILL_NO},
* {@link View#IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS},
* or {@link View#IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS}.
*
* @attr ref android.R.styleable#View_importantForAutofill
*/
public static void setImportantForAutofill(@NonNull View v, @AutofillImportance int mode) {
if (Build.VERSION.SDK_INT >= 26) {
v.setImportantForAutofill(mode);
}
}
/**
* Hints the Android System whether the {@link android.app.assist.AssistStructure.ViewNode}
* associated with this view is considered important for autofill purposes.
*
* <p>Generally speaking, a view is important for autofill if:
* <ol>
* <li>The view can be autofilled by an {@link android.service.autofill.AutofillService}.
* <li>The view contents can help an {@link android.service.autofill.AutofillService}
* determine how other views can be autofilled.
* <ol>
*
* <p>For example, view containers should typically return {@code false} for performance reasons
* (since the important info is provided by their children), but if its properties have relevant
* information (for example, a resource id called {@code credentials}, it should return
* {@code true}. On the other hand, views representing labels or editable fields should
* typically return {@code true}, but in some cases they could return {@code false}
* (for example, if they're part of a "Captcha" mechanism).
*
* <p>The value returned by this method depends on the value returned by
* {@link #getImportantForAutofill(View)}:
*
* <ol>
* <li>if it returns {@link View#IMPORTANT_FOR_AUTOFILL_YES} or
* {@link View#IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS},
* then it returns {@code true}
* <li>if it returns {@link View#IMPORTANT_FOR_AUTOFILL_NO} or
* {@link View#IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS},
* then it returns {@code false}
* <li>if it returns {@link View#IMPORTANT_FOR_AUTOFILL_AUTO},
* then it uses some simple heuristics that can return {@code true}
* in some cases (like a container with a resource id), but {@code false} in most.
* <li>otherwise, it returns {@code false}.
* </ol>
*
* <p>When a view is considered important for autofill:
* <ul>
* <li>The view might automatically trigger an autofill request when focused on.
* <li>The contents of the view are included in the {@link android.view.ViewStructure}
* used in an autofill request.
* </ul>
*
* <p>On the other hand, when a view is considered not important for autofill:
* <ul>
* <li>The view never automatically triggers autofill requests, but it can trigger a manual
* request through {@link android.view.autofill.AutofillManager#requestAutofill(View)}.
* <li>The contents of the view are not included in the {@link android.view.ViewStructure}
* used in an autofill request, unless the request has the
* {@link View#AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS} flag.
* </ul>
*
* <p>This method is only supported on API >= 26.
* On API 25 and below, it will always return {@code true}.</p>
*
* @return whether the view is considered important for autofill.
*
* @see #setImportantForAutofill(View, int)
* @see View#IMPORTANT_FOR_AUTOFILL_AUTO
* @see View#IMPORTANT_FOR_AUTOFILL_YES
* @see View#IMPORTANT_FOR_AUTOFILL_NO
* @see View#IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS
* @see View#IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS
* @see android.view.autofill.AutofillManager#requestAutofill(View)
*/
public static boolean isImportantForAutofill(@NonNull View v) {
if (Build.VERSION.SDK_INT >= 26) {
return v.isImportantForAutofill();
}
return true;
}
/**
* Checks whether provided View has an accessibility delegate attached to it.
*
* @param v The View instance to check
* @return True if the View has an accessibility delegate
*/
public static boolean hasAccessibilityDelegate(@NonNull View v) {
if (sAccessibilityDelegateCheckFailed) {
return false; // View implementation might have changed.
}
if (sAccessibilityDelegateField == null) {
try {
sAccessibilityDelegateField = View.class
.getDeclaredField("mAccessibilityDelegate");
sAccessibilityDelegateField.setAccessible(true);
} catch (Throwable t) {
sAccessibilityDelegateCheckFailed = true;
return false;
}
}
try {
return sAccessibilityDelegateField.get(v) != null;
} catch (Throwable t) {
sAccessibilityDelegateCheckFailed = true;
return false;
}
}
/**
* Indicates whether the view is currently tracking transient state that the
* app should not need to concern itself with saving and restoring, but that
* the framework should take special note to preserve when possible.
*
* @param view View to check for transient state
* @return true if the view has transient state
*/
public static boolean hasTransientState(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 16) {
return view.hasTransientState();
}
return false;
}
/**
* Set whether this view is currently tracking transient state that the
* framework should attempt to preserve when possible.
*
* @param view View tracking transient state
* @param hasTransientState true if this view has transient state
*/
public static void setHasTransientState(@NonNull View view, boolean hasTransientState) {
if (Build.VERSION.SDK_INT >= 16) {
view.setHasTransientState(hasTransientState);
}
}
/**
* <p>Cause an invalidate to happen on the next animation time step, typically the
* next display frame.</p>
*
* <p>This method can be invoked from outside of the UI thread
* only when this View is attached to a window.</p>
*
* @param view View to invalidate
*/
public static void postInvalidateOnAnimation(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 16) {
view.postInvalidateOnAnimation();
} else {
view.postInvalidate();
}
}
/**
* <p>Cause an invalidate of the specified area to happen on the next animation
* time step, typically the next display frame.</p>
*
* <p>This method can be invoked from outside of the UI thread
* only when this View is attached to a window.</p>
*
* @param view View to invalidate
* @param left The left coordinate of the rectangle to invalidate.
* @param top The top coordinate of the rectangle to invalidate.
* @param right The right coordinate of the rectangle to invalidate.
* @param bottom The bottom coordinate of the rectangle to invalidate.
*/
public static void postInvalidateOnAnimation(@NonNull View view, int left, int top,
int right, int bottom) {
if (Build.VERSION.SDK_INT >= 16) {
view.postInvalidateOnAnimation(left, top, right, bottom);
} else {
view.postInvalidate(left, top, right, bottom);
}
}
/**
* <p>Causes the Runnable to execute on the next animation time step.
* The runnable will be run on the user interface thread.</p>
*
* <p>This method can be invoked from outside of the UI thread
* only when this View is attached to a window.</p>
*
* @param view View to post this Runnable to
* @param action The Runnable that will be executed.
*/
public static void postOnAnimation(@NonNull View view, Runnable action) {
if (Build.VERSION.SDK_INT >= 16) {
view.postOnAnimation(action);
} else {
view.postDelayed(action, ValueAnimator.getFrameDelay());
}
}
/**
* <p>Causes the Runnable to execute on the next animation time step,
* after the specified amount of time elapses.
* The runnable will be run on the user interface thread.</p>
*
* <p>This method can be invoked from outside of the UI thread
* only when this View is attached to a window.</p>
*
* @param view The view to post this Runnable to
* @param action The Runnable that will be executed.
* @param delayMillis The delay (in milliseconds) until the Runnable
* will be executed.
*/
public static void postOnAnimationDelayed(@NonNull View view, Runnable action,
long delayMillis) {
if (Build.VERSION.SDK_INT >= 16) {
view.postOnAnimationDelayed(action, delayMillis);
} else {
view.postDelayed(action, ValueAnimator.getFrameDelay() + delayMillis);
}
}
/**
* Gets the mode for determining whether this View is important for accessibility
* which is if it fires accessibility events and if it is reported to
* accessibility services that query the screen.
*
* @param view The view whose property to get.
* @return The mode for determining whether a View is important for accessibility.
*
* @see #IMPORTANT_FOR_ACCESSIBILITY_YES
* @see #IMPORTANT_FOR_ACCESSIBILITY_NO
* @see #IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
* @see #IMPORTANT_FOR_ACCESSIBILITY_AUTO
*/
@ImportantForAccessibility
public static int getImportantForAccessibility(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 16) {
return view.getImportantForAccessibility();
}
return IMPORTANT_FOR_ACCESSIBILITY_AUTO;
}
/**
* Sets how to determine whether this view is important for accessibility
* which is if it fires accessibility events and if it is reported to
* accessibility services that query the screen.
* <p>
* <em>Note:</em> If the current platform version does not support the
* {@link #IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS} mode, then
* {@link #IMPORTANT_FOR_ACCESSIBILITY_NO} will be used as it is the
* closest terms of semantics.
* </p>
*
* @param view The view whose property to set.
* @param mode How to determine whether this view is important for accessibility.
*
* @see #IMPORTANT_FOR_ACCESSIBILITY_YES
* @see #IMPORTANT_FOR_ACCESSIBILITY_NO
* @see #IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
* @see #IMPORTANT_FOR_ACCESSIBILITY_AUTO
*/
public static void setImportantForAccessibility(@NonNull View view,
@ImportantForAccessibility int mode) {
if (Build.VERSION.SDK_INT >= 19) {
view.setImportantForAccessibility(mode);
} else if (Build.VERSION.SDK_INT >= 16) {
// IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS is not available
// on this platform so replace with IMPORTANT_FOR_ACCESSIBILITY_NO
// which is closer semantically.
if (mode == IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) {
mode = IMPORTANT_FOR_ACCESSIBILITY_NO;
}
//noinspection WrongConstant
view.setImportantForAccessibility(mode);
}
}
/**
* Computes whether this view should be exposed for accessibility. In
* general, views that are interactive or provide information are exposed
* while views that serve only as containers are hidden.
* <p>
* If an ancestor of this view has importance
* {@link #IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS}, this method
* returns <code>false</code>.
* <p>
* Otherwise, the value is computed according to the view's
* {@link #getImportantForAccessibility(View)} value:
* <ol>
* <li>{@link #IMPORTANT_FOR_ACCESSIBILITY_NO} or
* {@link #IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS}, return <code>false
* </code>
* <li>{@link #IMPORTANT_FOR_ACCESSIBILITY_YES}, return <code>true</code>
* <li>{@link #IMPORTANT_FOR_ACCESSIBILITY_AUTO}, return <code>true</code> if
* view satisfies any of the following:
* <ul>
* <li>Is actionable, e.g. {@link View#isClickable()},
* {@link View#isLongClickable()}, or {@link View#isFocusable()}
* <li>Has an {@link AccessibilityDelegateCompat}
* <li>Has an interaction listener, e.g. {@link View.OnTouchListener},
* {@link View.OnKeyListener}, etc.
* <li>Is an accessibility live region, e.g.
* {@link #getAccessibilityLiveRegion(View)} is not
* {@link #ACCESSIBILITY_LIVE_REGION_NONE}.
* </ul>
* </ol>
* <p>
* <em>Note:</em> Prior to API 21, this method will always return {@code true}.
*
* @return Whether the view is exposed for accessibility.
* @see #setImportantForAccessibility(View, int)
* @see #getImportantForAccessibility(View)
*/
public static boolean isImportantForAccessibility(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 21) {
return view.isImportantForAccessibility();
}
return true;
}
/**
* Performs the specified accessibility action on the view. For
* possible accessibility actions look at {@link AccessibilityNodeInfoCompat}.
* <p>
* If an {@link AccessibilityDelegateCompat} has been specified via calling
* {@link #setAccessibilityDelegate(View, AccessibilityDelegateCompat)} its
* {@link AccessibilityDelegateCompat#performAccessibilityAction(View, int, Bundle)}
* is responsible for handling this call.
* </p>
*
* @param action The action to perform.
* @param arguments Optional action arguments.
* @return Whether the action was performed.
*/
public static boolean performAccessibilityAction(@NonNull View view, int action,
Bundle arguments) {
if (Build.VERSION.SDK_INT >= 16) {
return view.performAccessibilityAction(action, arguments);
}
return false;
}
/**
* Gets the provider for managing a virtual view hierarchy rooted at this View
* and reported to {@link android.accessibilityservice.AccessibilityService}s
* that explore the window content.
* <p>
* If this method returns an instance, this instance is responsible for managing
* {@link AccessibilityNodeInfoCompat}s describing the virtual sub-tree rooted at
* this View including the one representing the View itself. Similarly the returned
* instance is responsible for performing accessibility actions on any virtual
* view or the root view itself.
* </p>
* <p>
* If an {@link AccessibilityDelegateCompat} has been specified via calling
* {@link #setAccessibilityDelegate(View, AccessibilityDelegateCompat)} its
* {@link AccessibilityDelegateCompat#getAccessibilityNodeProvider(View)}
* is responsible for handling this call.
* </p>
*
* @param view The view whose property to get.
* @return The provider.
*
* @see AccessibilityNodeProviderCompat
*/
public static AccessibilityNodeProviderCompat getAccessibilityNodeProvider(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 16) {
AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
if (provider != null) {
return new AccessibilityNodeProviderCompat(provider);
}
}
return null;
}
/**
* The opacity of the view. This is a value from 0 to 1, where 0 means the view is
* completely transparent and 1 means the view is completely opaque.
*
* <p>By default this is 1.0f.
* @return The opacity of the view.
*
* @deprecated Use {@link View#getAlpha()} directly.
*/
@Deprecated
public static float getAlpha(View view) {
return view.getAlpha();
}
/**
* <p>Specifies the type of layer backing this view. The layer can be
* {@link View#LAYER_TYPE_NONE disabled}, {@link View#LAYER_TYPE_SOFTWARE software} or
* {@link View#LAYER_TYPE_HARDWARE hardware}.</p>
*
* <p>A layer is associated with an optional {@link android.graphics.Paint}
* instance that controls how the layer is composed on screen. The following
* properties of the paint are taken into account when composing the layer:</p>
* <ul>
* <li>{@link android.graphics.Paint#getAlpha() Translucency (alpha)}</li>
* <li>{@link android.graphics.Paint#getXfermode() Blending mode}</li>
* <li>{@link android.graphics.Paint#getColorFilter() Color filter}</li>
* </ul>
*
* <p>If this view has an alpha value set to < 1.0 by calling
* setAlpha(float), the alpha value of the layer's paint is replaced by
* this view's alpha value. Calling setAlpha(float) is therefore
* equivalent to setting a hardware layer on this view and providing a paint with
* the desired alpha value.<p>
*
* <p>Refer to the documentation of {@link View#LAYER_TYPE_NONE disabled},
* {@link View#LAYER_TYPE_SOFTWARE software} and {@link View#LAYER_TYPE_HARDWARE hardware}
* for more information on when and how to use layers.</p>
*
* @param view View to set the layer type for
* @param layerType The type of layer to use with this view, must be one of
* {@link View#LAYER_TYPE_NONE}, {@link View#LAYER_TYPE_SOFTWARE} or
* {@link View#LAYER_TYPE_HARDWARE}
* @param paint The paint used to compose the layer. This argument is optional
* and can be null. It is ignored when the layer type is
* {@link View#LAYER_TYPE_NONE}
*
* @deprecated Use {@link View#setLayerType(int, Paint)} directly.
*/
@Deprecated
public static void setLayerType(View view, @LayerType int layerType, Paint paint) {
view.setLayerType(layerType, paint);
}
/**
* Indicates what type of layer is currently associated with this view. By default
* a view does not have a layer, and the layer type is {@link View#LAYER_TYPE_NONE}.
* Refer to the documentation of
* {@link #setLayerType(android.view.View, int, android.graphics.Paint)}
* for more information on the different types of layers.
*
* @param view The view to fetch the layer type from
* @return {@link View#LAYER_TYPE_NONE}, {@link View#LAYER_TYPE_SOFTWARE} or
* {@link View#LAYER_TYPE_HARDWARE}
*
* @see #setLayerType(android.view.View, int, android.graphics.Paint)
* @see View#LAYER_TYPE_NONE
* @see View#LAYER_TYPE_SOFTWARE
* @see View#LAYER_TYPE_HARDWARE
*
* @deprecated Use {@link View#getLayerType()} directly.
*/
@Deprecated
@LayerType
public static int getLayerType(View view) {
//noinspection ResourceType
return view.getLayerType();
}
/**
* Gets the id of a view for which a given view serves as a label for
* accessibility purposes.
*
* @param view The view on which to invoke the corresponding method.
* @return The labeled view id.
*/
public static int getLabelFor(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 17) {
return view.getLabelFor();
}
return 0;
}
/**
* Sets the id of a view for which a given view serves as a label for
* accessibility purposes.
*
* @param view The view on which to invoke the corresponding method.
* @param labeledId The labeled view id.
*/
public static void setLabelFor(@NonNull View view, @IdRes int labeledId) {
if (Build.VERSION.SDK_INT >= 17) {
view.setLabelFor(labeledId);
}
}
/**
* Updates the {@link Paint} object used with the current layer (used only if the current
* layer type is not set to {@link View#LAYER_TYPE_NONE}). Changed properties of the Paint
* provided to {@link #setLayerType(android.view.View, int, android.graphics.Paint)}
* will be used the next time the View is redrawn, but
* {@link #setLayerPaint(android.view.View, android.graphics.Paint)}
* must be called to ensure that the view gets redrawn immediately.
*
* <p>A layer is associated with an optional {@link android.graphics.Paint}
* instance that controls how the layer is composed on screen. The following
* properties of the paint are taken into account when composing the layer:</p>
* <ul>
* <li>{@link android.graphics.Paint#getAlpha() Translucency (alpha)}</li>
* <li>{@link android.graphics.Paint#getXfermode() Blending mode}</li>
* <li>{@link android.graphics.Paint#getColorFilter() Color filter}</li>
* </ul>
*
* <p>If this view has an alpha value set to < 1.0 by calling
* View#setAlpha(float), the alpha value of the layer's paint is replaced by
* this view's alpha value. Calling View#setAlpha(float) is therefore
* equivalent to setting a hardware layer on this view and providing a paint with
* the desired alpha value.</p>
*
* @param view View to set a layer paint for
* @param paint The paint used to compose the layer. This argument is optional
* and can be null. It is ignored when the layer type is
* {@link View#LAYER_TYPE_NONE}
*
* @see #setLayerType(View, int, android.graphics.Paint)
*/
public static void setLayerPaint(@NonNull View view, Paint paint) {
if (Build.VERSION.SDK_INT >= 17) {
view.setLayerPaint(paint);
} else {
// Make sure the paint is correct; this will be cheap if it's the same
// instance as was used to call setLayerType earlier.
view.setLayerType(view.getLayerType(), paint);
// This is expensive, but the only way to accomplish this before JB-MR1.
view.invalidate();
}
}
/**
* Returns the resolved layout direction for this view.
*
* @param view View to get layout direction for
* @return {@link #LAYOUT_DIRECTION_RTL} if the layout direction is RTL or returns
* {@link #LAYOUT_DIRECTION_LTR} if the layout direction is not RTL.
*
* For compatibility, this will return {@link #LAYOUT_DIRECTION_LTR} if API version
* is lower than Jellybean MR1 (API 17)
*/
@ResolvedLayoutDirectionMode
public static int getLayoutDirection(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 17) {
return view.getLayoutDirection();
}
return LAYOUT_DIRECTION_LTR;
}
/**
* Set the layout direction for this view. This will propagate a reset of layout direction
* resolution to the view's children and resolve layout direction for this view.
*
* @param view View to set layout direction for
* @param layoutDirection the layout direction to set. Should be one of:
*
* {@link #LAYOUT_DIRECTION_LTR},
* {@link #LAYOUT_DIRECTION_RTL},
* {@link #LAYOUT_DIRECTION_INHERIT},
* {@link #LAYOUT_DIRECTION_LOCALE}.
*
* Resolution will be done if the value is set to LAYOUT_DIRECTION_INHERIT. The resolution
* proceeds up the parent chain of the view to get the value. If there is no parent, then it
* will return the default {@link #LAYOUT_DIRECTION_LTR}.
*/
public static void setLayoutDirection(@NonNull View view,
@LayoutDirectionMode int layoutDirection) {
if (Build.VERSION.SDK_INT >= 17) {
view.setLayoutDirection(layoutDirection);
}
}
/**
* Gets the parent for accessibility purposes. Note that the parent for
* accessibility is not necessary the immediate parent. It is the first
* predecessor that is important for accessibility.
*
* @param view View to retrieve parent for
* @return The parent for use in accessibility inspection
*/
public static ViewParent getParentForAccessibility(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 16) {
return view.getParentForAccessibility();
}
return view.getParent();
}
/**
* Finds the first descendant view with the given ID, the view itself if the ID matches
* {@link View#getId()}, or throws an IllegalArgumentException if the ID is invalid or there
* is no matching view in the hierarchy.
* <p>
* <strong>Note:</strong> In most cases -- depending on compiler support --
* the resulting view is automatically cast to the target class type. If
* the target class type is unconstrained, an explicit cast may be
* necessary.
*
* @param id the ID to search for
* @return a view with given ID
* @see View#findViewById(int)
*/
@SuppressWarnings("TypeParameterUnusedInFormals")
@NonNull
public static <T extends View> T requireViewById(@NonNull View view, @IdRes int id) {
// TODO: use and link to View#requireViewById() directly, once available
T targetView = view.findViewById(id);
if (targetView == null) {
throw new IllegalArgumentException("ID does not reference a View inside this View");
}
return targetView;
}
/**
* Indicates whether this View is opaque. An opaque View guarantees that it will
* draw all the pixels overlapping its bounds using a fully opaque color.
*
* @return True if this View is guaranteed to be fully opaque, false otherwise.
* @deprecated Use {@link View#isOpaque()} directly. This method will be
* removed in a future release.
*/
@Deprecated
public static boolean isOpaque(View view) {
return view.isOpaque();
}
/**
* Utility to reconcile a desired size and state, with constraints imposed
* by a MeasureSpec. Will take the desired size, unless a different size
* is imposed by the constraints. The returned value is a compound integer,
* with the resolved size in the {@link #MEASURED_SIZE_MASK} bits and
* optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the resulting
* size is smaller than the size the view wants to be.
*
* @param size How big the view wants to be
* @param measureSpec Constraints imposed by the parent
* @return Size information bit mask as defined by
* {@link #MEASURED_SIZE_MASK} and {@link #MEASURED_STATE_TOO_SMALL}.
*
* @deprecated Use {@link View#resolveSizeAndState(int, int, int)} directly.
*/
@Deprecated
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
return View.resolveSizeAndState(size, measureSpec, childMeasuredState);
}
/**
* Return the full width measurement information for this view as computed
* by the most recent call to {@link android.view.View#measure(int, int)}.
* This result is a bit mask as defined by {@link #MEASURED_SIZE_MASK} and
* {@link #MEASURED_STATE_TOO_SMALL}.
* This should be used during measurement and layout calculations only. Use
* {@link android.view.View#getWidth()} to see how wide a view is after layout.
*
* @return The measured width of this view as a bit mask.
*
* @deprecated Use {@link View#getMeasuredWidth()} directly.
*/
@Deprecated
public static int getMeasuredWidthAndState(View view) {
return view.getMeasuredWidthAndState();
}
/**
* Return the full height measurement information for this view as computed
* by the most recent call to {@link android.view.View#measure(int, int)}.
* This result is a bit mask as defined by {@link #MEASURED_SIZE_MASK} and
* {@link #MEASURED_STATE_TOO_SMALL}.
* This should be used during measurement and layout calculations only. Use
* {@link android.view.View#getHeight()} to see how wide a view is after layout.
*
* @return The measured width of this view as a bit mask.
*
* @deprecated Use {@link View#getMeasuredHeightAndState()} directly.
*/
@Deprecated
public static int getMeasuredHeightAndState(View view) {
return view.getMeasuredHeightAndState();
}
/**
* Return only the state bits of {@link #getMeasuredWidthAndState}
* and {@link #getMeasuredHeightAndState}, combined into one integer.
* The width component is in the regular bits {@link #MEASURED_STATE_MASK}
* and the height component is at the shifted bits
* {@link #MEASURED_HEIGHT_STATE_SHIFT}>>{@link #MEASURED_STATE_MASK}.
*
* @deprecated Use {@link View#getMeasuredState()} directly.
*/
@Deprecated
public static int getMeasuredState(View view) {
return view.getMeasuredState();
}
/**
* Merge two states as returned by {@link #getMeasuredState(View)}.
* @param curState The current state as returned from a view or the result
* of combining multiple views.
* @param newState The new view state to combine.
* @return Returns a new integer reflecting the combination of the two
* states.
*
* @deprecated Use {@link View#combineMeasuredStates(int, int)} directly.
*/
@Deprecated
public static int combineMeasuredStates(int curState, int newState) {
return View.combineMeasuredStates(curState, newState);
}
/**
* Gets the live region mode for the specified View.
*
* @param view The view from which to obtain the live region mode
* @return The live region mode for the view.
*
* @see ViewCompat#setAccessibilityLiveRegion(View, int)
*/
@AccessibilityLiveRegion
public static int getAccessibilityLiveRegion(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 19) {
return view.getAccessibilityLiveRegion();
}
return ACCESSIBILITY_LIVE_REGION_NONE;
}
/**
* Sets the live region mode for the specified view. This indicates to
* accessibility services whether they should automatically notify the user
* about changes to the view's content description or text, or to the
* content descriptions or text of the view's children (where applicable).
* <p>
* For example, in a login screen with a TextView that displays an "incorrect
* password" notification, that view should be marked as a live region with
* mode {@link #ACCESSIBILITY_LIVE_REGION_POLITE}.
* <p>
* To disable change notifications for this view, use
* {@link #ACCESSIBILITY_LIVE_REGION_NONE}. This is the default live region
* mode for most views.
* <p>
* To indicate that the user should be notified of changes, use
* {@link #ACCESSIBILITY_LIVE_REGION_POLITE}.
* <p>
* If the view's changes should interrupt ongoing speech and notify the user
* immediately, use {@link #ACCESSIBILITY_LIVE_REGION_ASSERTIVE}.
*
* @param view The view on which to set the live region mode
* @param mode The live region mode for this view, one of:
* <ul>
* <li>{@link #ACCESSIBILITY_LIVE_REGION_NONE}
* <li>{@link #ACCESSIBILITY_LIVE_REGION_POLITE}
* <li>{@link #ACCESSIBILITY_LIVE_REGION_ASSERTIVE}
* </ul>
*/
public static void setAccessibilityLiveRegion(@NonNull View view,
@AccessibilityLiveRegion int mode) {
if (Build.VERSION.SDK_INT >= 19) {
view.setAccessibilityLiveRegion(mode);
}
}
/**
* Returns the start padding of the specified view depending on its resolved layout direction.
* If there are inset and enabled scrollbars, this value may include the space
* required to display the scrollbars as well.
*
* @param view The view to get padding for
* @return the start padding in pixels
*/
@Px
public static int getPaddingStart(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 17) {
return view.getPaddingStart();
}
return view.getPaddingLeft();
}
/**
* Returns the end padding of the specified view depending on its resolved layout direction.
* If there are inset and enabled scrollbars, this value may include the space
* required to display the scrollbars as well.
*
* @param view The view to get padding for
* @return the end padding in pixels
*/
@Px
public static int getPaddingEnd(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 17) {
return view.getPaddingEnd();
}
return view.getPaddingRight();
}
/**
* Sets the relative padding. The view may add on the space required to display
* the scrollbars, depending on the style and visibility of the scrollbars.
* So the values returned from {@link #getPaddingStart}, {@link View#getPaddingTop},
* {@link #getPaddingEnd} and {@link View#getPaddingBottom} may be different
* from the values set in this call.
*
* @param view The view on which to set relative padding
* @param start the start padding in pixels
* @param top the top padding in pixels
* @param end the end padding in pixels
* @param bottom the bottom padding in pixels
*/
public static void setPaddingRelative(@NonNull View view, @Px int start, @Px int top,
@Px int end, @Px int bottom) {
if (Build.VERSION.SDK_INT >= 17) {
view.setPaddingRelative(start, top, end, bottom);
} else {
view.setPadding(start, top, end, bottom);
}
}
private static void bindTempDetach() {
try {
sDispatchStartTemporaryDetach = View.class.getDeclaredMethod(
"dispatchStartTemporaryDetach");
sDispatchFinishTemporaryDetach = View.class.getDeclaredMethod(
"dispatchFinishTemporaryDetach");
} catch (NoSuchMethodException e) {
Log.e(TAG, "Couldn't find method", e);
}
sTempDetachBound = true;
}
/**
* Notify a view that it is being temporarily detached.
*/
public static void dispatchStartTemporaryDetach(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 24) {
view.dispatchStartTemporaryDetach();
} else {
if (!sTempDetachBound) {
bindTempDetach();
}
if (sDispatchStartTemporaryDetach != null) {
try {
sDispatchStartTemporaryDetach.invoke(view);
} catch (Exception e) {
Log.d(TAG, "Error calling dispatchStartTemporaryDetach", e);
}
} else {
// Try this instead
view.onStartTemporaryDetach();
}
}
}
/**
* Notify a view that its temporary detach has ended; the view is now reattached.
*/
public static void dispatchFinishTemporaryDetach(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 24) {
view.dispatchFinishTemporaryDetach();
} else {
if (!sTempDetachBound) {
bindTempDetach();
}
if (sDispatchFinishTemporaryDetach != null) {
try {
sDispatchFinishTemporaryDetach.invoke(view);
} catch (Exception e) {
Log.d(TAG, "Error calling dispatchFinishTemporaryDetach", e);
}
} else {
// Try this instead
view.onFinishTemporaryDetach();
}
}
}
/**
* The horizontal location of this view relative to its {@link View#getLeft() left} position.
* This position is post-layout, in addition to wherever the object's
* layout placed it.
*
* @return The horizontal position of this view relative to its left position, in pixels.
*
* @deprecated Use {@link View#getTranslationX()} directly.
*/
@Deprecated
public static float getTranslationX(View view) {
return view.getTranslationX();
}
/**
* The vertical location of this view relative to its {@link View#getTop() top} position.
* This position is post-layout, in addition to wherever the object's
* layout placed it.
*
* @return The vertical position of this view relative to its top position, in pixels.
*
* @deprecated Use {@link View#getTranslationY()} directly.
*/
@Deprecated
public static float getTranslationY(View view) {
return view.getTranslationY();
}
/**
* The transform matrix of this view, which is calculated based on the current
* rotation, scale, and pivot properties.
* <p>
*
* @param view The view whose Matrix will be returned
* @return The current transform matrix for the view
*
* @see #getRotation(View)
* @see #getScaleX(View)
* @see #getScaleY(View)
* @see #getPivotX(View)
* @see #getPivotY(View)
*
* @deprecated Use {@link View#getMatrix()} directly.
*/
@Deprecated
@Nullable
public static Matrix getMatrix(View view) {
return view.getMatrix();
}
/**
* Returns the minimum width of the view.
*
* <p>Prior to API 16, this method may return 0 on some platforms.</p>
*
* @return the minimum width the view will try to be.
*/
public static int getMinimumWidth(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 16) {
return view.getMinimumWidth();
}
if (!sMinWidthFieldFetched) {
try {
sMinWidthField = View.class.getDeclaredField("mMinWidth");
sMinWidthField.setAccessible(true);
} catch (NoSuchFieldException e) {
// Couldn't find the field. Abort!
}
sMinWidthFieldFetched = true;
}
if (sMinWidthField != null) {
try {
return (int) sMinWidthField.get(view);
} catch (Exception e) {
// Field get failed. Oh well...
}
}
// We failed, return 0
return 0;
}
/**
* Returns the minimum height of the view.
*
* <p>Prior to API 16, this method may return 0 on some platforms.</p>
*
* @return the minimum height the view will try to be.
*/
public static int getMinimumHeight(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 16) {
return view.getMinimumHeight();
}
if (!sMinHeightFieldFetched) {
try {
sMinHeightField = View.class.getDeclaredField("mMinHeight");
sMinHeightField.setAccessible(true);
} catch (NoSuchFieldException e) {
// Couldn't find the field. Abort!
}
sMinHeightFieldFetched = true;
}
if (sMinHeightField != null) {
try {
return (int) sMinHeightField.get(view);
} catch (Exception e) {
// Field get failed. Oh well...
}
}
// We failed, return 0
return 0;
}
/**
* This method returns a ViewPropertyAnimator object, which can be used to animate
* specific properties on this View.
*
* @return ViewPropertyAnimator The ViewPropertyAnimator associated with this View.
*/
@NonNull
public static ViewPropertyAnimatorCompat animate(@NonNull View view) {
if (sViewPropertyAnimatorMap == null) {
sViewPropertyAnimatorMap = new WeakHashMap<>();
}
ViewPropertyAnimatorCompat vpa = sViewPropertyAnimatorMap.get(view);
if (vpa == null) {
vpa = new ViewPropertyAnimatorCompat(view);
sViewPropertyAnimatorMap.put(view, vpa);
}
return vpa;
}
/**
* Sets the horizontal location of this view relative to its left position.
* This effectively positions the object post-layout, in addition to wherever the object's
* layout placed it.
*
* @param value The horizontal position of this view relative to its left position,
* in pixels.
*
* @deprecated Use {@link View#setTranslationX(float)} directly.
*/
@Deprecated
public static void setTranslationX(View view, float value) {
view.setTranslationX(value);
}
/**
* Sets the vertical location of this view relative to its top position.
* This effectively positions the object post-layout, in addition to wherever the object's
* layout placed it.
*
* @param value The vertical position of this view relative to its top position,
* in pixels.
*
* @attr name android:translationY
*
* @deprecated Use {@link View#setTranslationY(float)} directly.
*/
@Deprecated
public static void setTranslationY(View view, float value) {
view.setTranslationY(value);
}
/**
* <p>Sets the opacity of the view. This is a value from 0 to 1, where 0 means the view is
* completely transparent and 1 means the view is completely opaque.</p>
*
* <p> Note that setting alpha to a translucent value (0 < alpha < 1) can have significant
* performance implications, especially for large views. It is best to use the alpha property
* sparingly and transiently, as in the case of fading animations.</p>
*
* @param value The opacity of the view.
*
* @deprecated Use {@link View#setAlpha(float)} directly.
*/
@Deprecated
public static void setAlpha(View view, @FloatRange(from=0.0, to=1.0) float value) {
view.setAlpha(value);
}
/**
* Sets the visual x position of this view, in pixels. This is equivalent to setting the
* {@link #setTranslationX(View, float) translationX} property to be the difference between
* the x value passed in and the current left property of the view as determined
* by the layout bounds.
*
* @param value The visual x position of this view, in pixels.
*
* @deprecated Use {@link View#setX(float)} directly.
*/
@Deprecated
public static void setX(View view, float value) {
view.setX(value);
}
/**
* Sets the visual y position of this view, in pixels. This is equivalent to setting the
* {@link #setTranslationY(View, float) translationY} property to be the difference between
* the y value passed in and the current top property of the view as determined by the
* layout bounds.
*
* @param value The visual y position of this view, in pixels.
*
* @deprecated Use {@link View#setY(float)} directly.
*/
@Deprecated
public static void setY(View view, float value) {
view.setY(value);
}
/**
* Sets the degrees that the view is rotated around the pivot point. Increasing values
* result in clockwise rotation.
*
* @param value The degrees of rotation.
*
* @deprecated Use {@link View#setRotation(float)} directly.
*/
@Deprecated
public static void setRotation(View view, float value) {
view.setRotation(value);
}
/**
* Sets the degrees that the view is rotated around the horizontal axis through the pivot point.
* Increasing values result in clockwise rotation from the viewpoint of looking down the
* x axis.
*
* @param value The degrees of X rotation.
*
* @deprecated Use {@link View#setRotationX(float)} directly.
*/
@Deprecated
public static void setRotationX(View view, float value) {
view.setRotationX(value);
}
/**
* Sets the degrees that the view is rotated around the vertical axis through the pivot point.
* Increasing values result in counter-clockwise rotation from the viewpoint of looking
* down the y axis.
*
* @param value The degrees of Y rotation.
*
* @deprecated Use {@link View#setRotationY(float)} directly.
*/
@Deprecated
public static void setRotationY(View view, float value) {
view.setRotationY(value);
}
/**
* Sets the amount that the view is scaled in x around the pivot point, as a proportion of
* the view's unscaled width. A value of 1 means that no scaling is applied.
*
* @param value The scaling factor.
*
* @deprecated Use {@link View#setScaleX(float)} directly.
*/
@Deprecated
public static void setScaleX(View view, float value) {
view.setScaleX(value);
}
/**
* Sets the amount that the view is scaled in Y around the pivot point, as a proportion of
* the view's unscaled width. A value of 1 means that no scaling is applied.
*
* @param value The scaling factor.
*
* @deprecated Use {@link View#setScaleY(float)} directly.
*/
@Deprecated
public static void setScaleY(View view, float value) {
view.setScaleY(value);
}
/**
* The x location of the point around which the view is
* {@link #setRotation(View, float) rotated} and {@link #setScaleX(View, float) scaled}.
*
* @deprecated Use {@link View#getPivotX()} directly.
*/
@Deprecated
public static float getPivotX(View view) {
return view.getPivotX();
}
/**
* Sets the x location of the point around which the view is
* {@link #setRotation(View, float) rotated} and {@link #setScaleX(View, float) scaled}.
* By default, the pivot point is centered on the object.
* Setting this property disables this behavior and causes the view to use only the
* explicitly set pivotX and pivotY values.
*
* @param value The x location of the pivot point.
*
* @deprecated Use {@link View#setPivotX(float)} directly.
*/
@Deprecated
public static void setPivotX(View view, float value) {
view.setPivotX(value);
}
/**
* The y location of the point around which the view is {@link #setRotation(View,
* float) rotated} and {@link #setScaleY(View, float) scaled}.
*
* @return The y location of the pivot point.
*
* @deprecated Use {@link View#getPivotY()} directly.
*/
@Deprecated
public static float getPivotY(View view) {
return view.getPivotY();
}
/**
* Sets the y location of the point around which the view is
* {@link #setRotation(View, float) rotated} and {@link #setScaleY(View, float) scaled}.
* By default, the pivot point is centered on the object.
* Setting this property disables this behavior and causes the view to use only the
* explicitly set pivotX and pivotY values.
*
* @param value The y location of the pivot point.
*
* @deprecated Use {@link View#setPivotX(float)} directly.
*/
@Deprecated
public static void setPivotY(View view, float value) {
view.setPivotY(value);
}
/**
* @deprecated Use {@link View#getRotation()} directly.
*/
@Deprecated
public static float getRotation(View view) {
return view.getRotation();
}
/**
* @deprecated Use {@link View#getRotationX()} directly.
*/
@Deprecated
public static float getRotationX(View view) {
return view.getRotationX();
}
/**
* @deprecated Use {@link View#getRotationY()} directly.
*/
@Deprecated
public static float getRotationY(View view) {
return view.getRotationY();
}
/**
* @deprecated Use {@link View#getScaleX()} directly.
*/
@Deprecated
public static float getScaleX(View view) {
return view.getScaleX();
}
/**
* @deprecated Use {@link View#getScaleY()} directly.
*/
@Deprecated
public static float getScaleY(View view) {
return view.getScaleY();
}
/**
* @deprecated Use {@link View#getX()} directly.
*/
@Deprecated
public static float getX(View view) {
return view.getX();
}
/**
* @deprecated Use {@link View#getY()} directly.
*/
@Deprecated
public static float getY(View view) {
return view.getY();
}
/**
* Sets the base elevation of this view, in pixels.
*/
public static void setElevation(@NonNull View view, float elevation) {
if (Build.VERSION.SDK_INT >= 21) {
view.setElevation(elevation);
}
}
/**
* The base elevation of this view relative to its parent, in pixels.
*
* @return The base depth position of the view, in pixels.
*/
public static float getElevation(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 21) {
return view.getElevation();
}
return 0f;
}
/**
* Sets the depth location of this view relative to its {@link #getElevation(View) elevation}.
*/
public static void setTranslationZ(@NonNull View view, float translationZ) {
if (Build.VERSION.SDK_INT >= 21) {
view.setTranslationZ(translationZ);
}
}
/**
* The depth location of this view relative to its {@link #getElevation(View) elevation}.
*
* @return The depth of this view relative to its elevation.
*/
public static float getTranslationZ(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 21) {
return view.getTranslationZ();
}
return 0f;
}
/**
* Sets the name of the View to be used to identify Views in Transitions.
* Names should be unique in the View hierarchy.
*
* @param view The View against which to invoke the method.
* @param transitionName The name of the View to uniquely identify it for Transitions.
*/
public static void setTransitionName(@NonNull View view, String transitionName) {
if (Build.VERSION.SDK_INT >= 21) {
view.setTransitionName(transitionName);
} else {
if (sTransitionNameMap == null) {
sTransitionNameMap = new WeakHashMap<>();
}
sTransitionNameMap.put(view, transitionName);
}
}
/**
* Returns the name of the View to be used to identify Views in Transitions.
* Names should be unique in the View hierarchy.
*
* <p>This returns null if the View has not been given a name.</p>
*
* @param view The View against which to invoke the method.
* @return The name used of the View to be used to identify Views in Transitions or null
* if no name has been given.
*/
@Nullable
public static String getTransitionName(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 21) {
return view.getTransitionName();
}
if (sTransitionNameMap == null) {
return null;
}
return sTransitionNameMap.get(view);
}
/**
* Returns the current system UI visibility that is currently set for the entire window.
*/
public static int getWindowSystemUiVisibility(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 16) {
return view.getWindowSystemUiVisibility();
}
return 0;
}
/**
* Ask that a new dispatch of {@code View.onApplyWindowInsets(WindowInsets)} be performed. This
* falls back to {@code View.requestFitSystemWindows()} where available.
*/
public static void requestApplyInsets(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 20) {
view.requestApplyInsets();
} else if (Build.VERSION.SDK_INT >= 16) {
view.requestFitSystemWindows();
}
}
/**
* Tells the ViewGroup whether to draw its children in the order defined by the method
* {@code ViewGroup.getChildDrawingOrder(int, int)}.
*
* @param enabled true if the order of the children when drawing is determined by
* {@link ViewGroup#getChildDrawingOrder(int, int)}, false otherwise
*
* <p>Prior to API 7 this will have no effect.</p>
*
* @deprecated Use {@link ViewGroup#setChildrenDrawingOrderEnabled(boolean)} directly.
*/
@Deprecated
public static void setChildrenDrawingOrderEnabled(ViewGroup viewGroup, boolean enabled) {
if (sChildrenDrawingOrderMethod == null) {
try {
sChildrenDrawingOrderMethod = ViewGroup.class
.getDeclaredMethod("setChildrenDrawingOrderEnabled", boolean.class);
} catch (NoSuchMethodException e) {
Log.e(TAG, "Unable to find childrenDrawingOrderEnabled", e);
}
sChildrenDrawingOrderMethod.setAccessible(true);
}
try {
sChildrenDrawingOrderMethod.invoke(viewGroup, enabled);
} catch (IllegalAccessException e) {
Log.e(TAG, "Unable to invoke childrenDrawingOrderEnabled", e);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Unable to invoke childrenDrawingOrderEnabled", e);
} catch (InvocationTargetException e) {
Log.e(TAG, "Unable to invoke childrenDrawingOrderEnabled", e);
}
}
/**
* Returns true if this view should adapt to fit system window insets. This method will always
* return false before API 16 (Jellybean).
*/
public static boolean getFitsSystemWindows(@NonNull View v) {
if (Build.VERSION.SDK_INT >= 16) {
return v.getFitsSystemWindows();
}
return false;
}
/**
* Sets whether or not this view should account for system screen decorations
* such as the status bar and inset its content; that is, controlling whether
* the default implementation of {@link View#fitSystemWindows(Rect)} will be
* executed. See that method for more details.
*
* @deprecated Use {@link View#setFitsSystemWindows(boolean)} directly.
*/
@Deprecated
public static void setFitsSystemWindows(View view, boolean fitSystemWindows) {
view.setFitsSystemWindows(fitSystemWindows);
}
/**
* On API 11 devices and above, call <code>Drawable.jumpToCurrentState()</code>
* on all Drawable objects associated with this view.
* <p>
* On API 21 and above, also calls <code>StateListAnimator#jumpToCurrentState()</code>
* if there is a StateListAnimator attached to this view.
*
* @deprecated Use {@link View#jumpDrawablesToCurrentState()} directly.
*/
@Deprecated
public static void jumpDrawablesToCurrentState(View v) {
v.jumpDrawablesToCurrentState();
}
/**
* Set an {@link OnApplyWindowInsetsListener} to take over the policy for applying
* window insets to this view. This will only take effect on devices with API 21 or above.
*/
public static void setOnApplyWindowInsetsListener(@NonNull View v,
final OnApplyWindowInsetsListener listener) {
if (Build.VERSION.SDK_INT >= 21) {
if (listener == null) {
v.setOnApplyWindowInsetsListener(null);
return;
}
v.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
@Override
@RequiresApi(21) // TODO remove https://issuetracker.google.com/issues/76458979
public WindowInsets onApplyWindowInsets(View view, WindowInsets insets) {
WindowInsetsCompat compatInsets = WindowInsetsCompat.wrap(insets);
compatInsets = listener.onApplyWindowInsets(view, compatInsets);
return (WindowInsets) WindowInsetsCompat.unwrap(compatInsets);
}
});
}
}
/**
* Called when the view should apply {@link WindowInsetsCompat} according to its internal policy.
*
* <p>Clients may supply an {@link OnApplyWindowInsetsListener} to a view. If one is set
* it will be called during dispatch instead of this method. The listener may optionally
* call this method from its own implementation if it wishes to apply the view's default
* insets policy in addition to its own.</p>
*
* @param view The View against which to invoke the method.
* @param insets Insets to apply
* @return The supplied insets with any applied insets consumed
*/
public static WindowInsetsCompat onApplyWindowInsets(@NonNull View view,
WindowInsetsCompat insets) {
if (Build.VERSION.SDK_INT >= 21) {
WindowInsets unwrapped = (WindowInsets) WindowInsetsCompat.unwrap(insets);
WindowInsets result = view.onApplyWindowInsets(unwrapped);
if (result != unwrapped) {
unwrapped = new WindowInsets(result);
}
return WindowInsetsCompat.wrap(unwrapped);
}
return insets;
}
/**
* Request to apply the given window insets to this view or another view in its subtree.
*
* <p>This method should be called by clients wishing to apply insets corresponding to areas
* obscured by window decorations or overlays. This can include the status and navigation bars,
* action bars, input methods and more. New inset categories may be added in the future.
* The method returns the insets provided minus any that were applied by this view or its
* children.</p>
*
* @param insets Insets to apply
* @return The provided insets minus the insets that were consumed
*/
public static WindowInsetsCompat dispatchApplyWindowInsets(@NonNull View view,
WindowInsetsCompat insets) {
if (Build.VERSION.SDK_INT >= 21) {
WindowInsets unwrapped = (WindowInsets) WindowInsetsCompat.unwrap(insets);
WindowInsets result = view.dispatchApplyWindowInsets(unwrapped);
if (result != unwrapped) {
unwrapped = new WindowInsets(result);
}
return WindowInsetsCompat.wrap(unwrapped);
}
return insets;
}
/**
* Controls whether the entire hierarchy under this view will save its
* state when a state saving traversal occurs from its parent.
*
* @param enabled Set to false to <em>disable</em> state saving, or true
* (the default) to allow it.
*
* @deprecated Use {@link View#setSaveFromParentEnabled(boolean)} directly.
*/
@Deprecated
public static void setSaveFromParentEnabled(View v, boolean enabled) {
v.setSaveFromParentEnabled(enabled);
}
/**
* Changes the activated state of this view. A view can be activated or not.
* Note that activation is not the same as selection. Selection is
* a transient property, representing the view (hierarchy) the user is
* currently interacting with. Activation is a longer-term state that the
* user can move views in and out of.
*
* @param activated true if the view must be activated, false otherwise
*
* @deprecated Use {@link View#setActivated(boolean)} directly.
*/
@Deprecated
public static void setActivated(View view, boolean activated) {
view.setActivated(activated);
}
/**
* Returns whether this View has content which overlaps.
*
* <p>This function, intended to be overridden by specific View types, is an optimization when
* alpha is set on a view. If rendering overlaps in a view with alpha < 1, that view is drawn to
* an offscreen buffer and then composited into place, which can be expensive. If the view has
* no overlapping rendering, the view can draw each primitive with the appropriate alpha value
* directly. An example of overlapping rendering is a TextView with a background image, such as
* a Button. An example of non-overlapping rendering is a TextView with no background, or an
* ImageView with only the foreground image. The default implementation returns true; subclasses
* should override if they have cases which can be optimized.</p>
*
* @return true if the content in this view might overlap, false otherwise.
*/
public static boolean hasOverlappingRendering(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 16) {
return view.hasOverlappingRendering();
}
return true;
}
/**
* Return if the padding as been set through relative values
* {@code View.setPaddingRelative(int, int, int, int)} or thru
*
* @return true if the padding is relative or false if it is not.
*/
public static boolean isPaddingRelative(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 17) {
return view.isPaddingRelative();
}
return false;
}
/**
* Set the background of the {@code view} to a given Drawable, or remove the background. If the
* background has padding, {@code view}'s padding is set to the background's padding. However,
* when a background is removed, this View's padding isn't touched. If setting the padding is
* desired, please use{@code setPadding(int, int, int, int)}.
*/
public static void setBackground(@NonNull View view, @Nullable Drawable background) {
if (Build.VERSION.SDK_INT >= 16) {
view.setBackground(background);
} else {
view.setBackgroundDrawable(background);
}
}
/**
* Return the tint applied to the background drawable, if specified.
* <p>
* Only returns meaningful info when running on API v21 or newer, or if {@code view}
* implements the {@code TintableBackgroundView} interface.
*/
public static ColorStateList getBackgroundTintList(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 21) {
return view.getBackgroundTintList();
}
return (view instanceof TintableBackgroundView)
? ((TintableBackgroundView) view).getSupportBackgroundTintList()
: null;
}
/**
* Applies a tint to the background drawable.
* <p>
* This will always take effect when running on API v21 or newer. When running on platforms
* previous to API v21, it will only take effect if {@code view} implements the
* {@code TintableBackgroundView} interface.
*/
public static void setBackgroundTintList(@NonNull View view, ColorStateList tintList) {
if (Build.VERSION.SDK_INT >= 21) {
view.setBackgroundTintList(tintList);
if (Build.VERSION.SDK_INT == 21) {
// Work around a bug in L that did not update the state of the background
// after applying the tint
Drawable background = view.getBackground();
boolean hasTint = (view.getBackgroundTintList() != null)
|| (view.getBackgroundTintMode() != null);
if ((background != null) && hasTint) {
if (background.isStateful()) {
background.setState(view.getDrawableState());
}
view.setBackground(background);
}
}
} else if (view instanceof TintableBackgroundView) {
((TintableBackgroundView) view).setSupportBackgroundTintList(tintList);
}
}
/**
* Return the blending mode used to apply the tint to the background
* drawable, if specified.
* <p>
* Only returns meaningful info when running on API v21 or newer, or if {@code view}
* implements the {@code TintableBackgroundView} interface.
*/
public static PorterDuff.Mode getBackgroundTintMode(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 21) {
return view.getBackgroundTintMode();
}
return (view instanceof TintableBackgroundView)
? ((TintableBackgroundView) view).getSupportBackgroundTintMode()
: null;
}
/**
* Specifies the blending mode used to apply the tint specified by
* {@link #setBackgroundTintList(android.view.View, android.content.res.ColorStateList)} to
* the background drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}.
* <p>
* This will always take effect when running on API v21 or newer. When running on platforms
* previous to API v21, it will only take effect if {@code view} implement the
* {@code TintableBackgroundView} interface.
*/
public static void setBackgroundTintMode(@NonNull View view, PorterDuff.Mode mode) {
if (Build.VERSION.SDK_INT >= 21) {
view.setBackgroundTintMode(mode);
if (Build.VERSION.SDK_INT == 21) {
// Work around a bug in L that did not update the state of the background
// after applying the tint
Drawable background = view.getBackground();
boolean hasTint = (view.getBackgroundTintList() != null)
|| (view.getBackgroundTintMode() != null);
if ((background != null) && hasTint) {
if (background.isStateful()) {
background.setState(view.getDrawableState());
}
view.setBackground(background);
}
}
} else if (view instanceof TintableBackgroundView) {
((TintableBackgroundView) view).setSupportBackgroundTintMode(mode);
}
}
// TODO: getters for various view properties (rotation, etc)
/**
* Enable or disable nested scrolling for this view.
*
* <p>If this property is set to true the view will be permitted to initiate nested
* scrolling operations with a compatible parent view in the current hierarchy. If this
* view does not implement nested scrolling this will have no effect. Disabling nested scrolling
* while a nested scroll is in progress has the effect of
* {@link #stopNestedScroll(View) stopping} the nested scroll.</p>
*
* @param enabled true to enable nested scrolling, false to disable
*
* @see #isNestedScrollingEnabled(View)
*/
@SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
public static void setNestedScrollingEnabled(@NonNull View view, boolean enabled) {
if (Build.VERSION.SDK_INT >= 21) {
view.setNestedScrollingEnabled(enabled);
} else {
if (view instanceof NestedScrollingChild) {
((NestedScrollingChild) view).setNestedScrollingEnabled(enabled);
}
}
}
/**
* Returns true if nested scrolling is enabled for this view.
*
* <p>If nested scrolling is enabled and this View class implementation supports it,
* this view will act as a nested scrolling child view when applicable, forwarding data
* about the scroll operation in progress to a compatible and cooperating nested scrolling
* parent.</p>
*
* @return true if nested scrolling is enabled
*
* @see #setNestedScrollingEnabled(View, boolean)
*/
@SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
public static boolean isNestedScrollingEnabled(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 21) {
return view.isNestedScrollingEnabled();
}
if (view instanceof NestedScrollingChild) {
return ((NestedScrollingChild) view).isNestedScrollingEnabled();
}
return false;
}
/**
* Begin a nestable scroll operation along the given axes.
*
* <p>This version of the method just calls {@link #startNestedScroll(View, int, int)} using
* the touch input type.</p>
*
* @param axes Flags consisting of a combination of {@link ViewCompat#SCROLL_AXIS_HORIZONTAL}
* and/or {@link ViewCompat#SCROLL_AXIS_VERTICAL}.
* @return true if a cooperative parent was found and nested scrolling has been enabled for
* the current gesture.
*/
@SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
public static boolean startNestedScroll(@NonNull View view, @ScrollAxis int axes) {
if (Build.VERSION.SDK_INT >= 21) {
return view.startNestedScroll(axes);
}
if (view instanceof NestedScrollingChild) {
return ((NestedScrollingChild) view).startNestedScroll(axes);
}
return false;
}
/**
* Stop a nested scroll in progress.
*
* <p>This version of the method just calls {@link #stopNestedScroll(View, int)} using the
* touch input type.</p>
*
* @see #startNestedScroll(View, int)
*/
@SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
public static void stopNestedScroll(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 21) {
view.stopNestedScroll();
} else if (view instanceof NestedScrollingChild) {
((NestedScrollingChild) view).stopNestedScroll();
}
}
/**
* Returns true if this view has a nested scrolling parent.
*
* <p>This version of the method just calls {@link #hasNestedScrollingParent(View, int)}
* using the touch input type.</p>
*
* @return whether this view has a nested scrolling parent
*/
@SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
public static boolean hasNestedScrollingParent(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 21) {
return view.hasNestedScrollingParent();
}
if (view instanceof NestedScrollingChild) {
return ((NestedScrollingChild) view).hasNestedScrollingParent();
}
return false;
}
/**
* Dispatch one step of a nested scroll in progress.
*
* <p>This version of the method just calls
* {@link #dispatchNestedScroll(View, int, int, int, int, int[], int)} using the touch input
* type.</p>
*
* @param dxConsumed Horizontal distance in pixels consumed by this view during this scroll step
* @param dyConsumed Vertical distance in pixels consumed by this view during this scroll step
* @param dxUnconsumed Horizontal scroll distance in pixels not consumed by this view
* @param dyUnconsumed Horizontal scroll distance in pixels not consumed by this view
* @param offsetInWindow Optional. If not null, on return this will contain the offset
* in local view coordinates of this view from before this operation
* to after it completes. View implementations may use this to adjust
* expected input coordinate tracking.
* @return true if the event was dispatched, false if it could not be dispatched.
*/
@SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
public static boolean dispatchNestedScroll(@NonNull View view, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow) {
if (Build.VERSION.SDK_INT >= 21) {
return view.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
offsetInWindow);
}
if (view instanceof NestedScrollingChild) {
return ((NestedScrollingChild) view).dispatchNestedScroll(dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed, offsetInWindow);
}
return false;
}
/**
* Dispatch one step of a nested scroll in progress before this view consumes any portion of it.
*
* <p>This version of the method just calls
* {@link #dispatchNestedPreScroll(View, int, int, int[], int[], int)} using the touch input
* type.</p>
*
* @param dx Horizontal scroll distance in pixels
* @param dy Vertical scroll distance in pixels
* @param consumed Output. If not null, consumed[0] will contain the consumed component of dx
* and consumed[1] the consumed dy.
* @param offsetInWindow Optional. If not null, on return this will contain the offset
* in local view coordinates of this view from before this operation
* to after it completes. View implementations may use this to adjust
* expected input coordinate tracking.
* @return true if the parent consumed some or all of the scroll delta
*/
@SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
public static boolean dispatchNestedPreScroll(@NonNull View view, int dx, int dy,
@Nullable int[] consumed, @Nullable int[] offsetInWindow) {
if (Build.VERSION.SDK_INT >= 21) {
return view.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}
if (view instanceof NestedScrollingChild) {
return ((NestedScrollingChild) view).dispatchNestedPreScroll(dx, dy, consumed,
offsetInWindow);
}
return false;
}
/**
* Begin a nestable scroll operation along the given axes.
*
* <p>A view starting a nested scroll promises to abide by the following contract:</p>
*
* <p>The view will call startNestedScroll upon initiating a scroll operation. In the case
* of a touch scroll this corresponds to the initial {@link MotionEvent#ACTION_DOWN}.
* In the case of touch scrolling the nested scroll will be terminated automatically in
* the same manner as {@link ViewParent#requestDisallowInterceptTouchEvent(boolean)}.
* In the event of programmatic scrolling the caller must explicitly call
* {@link #stopNestedScroll(View)} to indicate the end of the nested scroll.</p>
*
* <p>If <code>startNestedScroll</code> returns true, a cooperative parent was found.
* If it returns false the caller may ignore the rest of this contract until the next scroll.
* Calling startNestedScroll while a nested scroll is already in progress will return true.</p>
*
* <p>At each incremental step of the scroll the caller should invoke
* {@link #dispatchNestedPreScroll(View, int, int, int[], int[]) dispatchNestedPreScroll}
* once it has calculated the requested scrolling delta. If it returns true the nested scrolling
* parent at least partially consumed the scroll and the caller should adjust the amount it
* scrolls by.</p>
*
* <p>After applying the remainder of the scroll delta the caller should invoke
* {@link #dispatchNestedScroll(View, int, int, int, int, int[]) dispatchNestedScroll}, passing
* both the delta consumed and the delta unconsumed. A nested scrolling parent may treat
* these values differently. See
* {@link NestedScrollingParent#onNestedScroll(View, int, int, int, int)}.
* </p>
*
* @param axes Flags consisting of a combination of {@link ViewCompat#SCROLL_AXIS_HORIZONTAL}
* and/or {@link ViewCompat#SCROLL_AXIS_VERTICAL}.
* @param type the type of input which cause this scroll event
* @return true if a cooperative parent was found and nested scrolling has been enabled for
* the current gesture.
*
* @see #stopNestedScroll(View)
* @see #dispatchNestedPreScroll(View, int, int, int[], int[])
* @see #dispatchNestedScroll(View, int, int, int, int, int[])
*/
public static boolean startNestedScroll(@NonNull View view, @ScrollAxis int axes,
@NestedScrollType int type) {
if (view instanceof NestedScrollingChild2) {
return ((NestedScrollingChild2) view).startNestedScroll(axes, type);
} else if (type == ViewCompat.TYPE_TOUCH) {
return startNestedScroll(view, axes);
}
return false;
}
/**
* Stop a nested scroll in progress.
*
* <p>Calling this method when a nested scroll is not currently in progress is harmless.</p>
*
* @param type the type of input which cause this scroll event
* @see #startNestedScroll(View, int)
*/
public static void stopNestedScroll(@NonNull View view, @NestedScrollType int type) {
if (view instanceof NestedScrollingChild2) {
((NestedScrollingChild2) view).stopNestedScroll(type);
} else if (type == ViewCompat.TYPE_TOUCH) {
stopNestedScroll(view);
}
}
/**
* Returns true if this view has a nested scrolling parent.
*
* <p>The presence of a nested scrolling parent indicates that this view has initiated
* a nested scroll and it was accepted by an ancestor view further up the view hierarchy.</p>
*
* @param type the type of input which cause this scroll event
* @return whether this view has a nested scrolling parent
*/
public static boolean hasNestedScrollingParent(@NonNull View view, @NestedScrollType int type) {
if (view instanceof NestedScrollingChild2) {
((NestedScrollingChild2) view).hasNestedScrollingParent(type);
} else if (type == ViewCompat.TYPE_TOUCH) {
return hasNestedScrollingParent(view);
}
return false;
}
/**
* Dispatch one step of a nested scroll in progress.
*
* <p>Implementations of views that support nested scrolling should call this to report
* info about a scroll in progress to the current nested scrolling parent. If a nested scroll
* is not currently in progress or nested scrolling is not
* {@link #isNestedScrollingEnabled(View) enabled} for this view this method does nothing.</p>
*
* <p>Compatible View implementations should also call
* {@link #dispatchNestedPreScroll(View, int, int, int[], int[]) dispatchNestedPreScroll} before
* consuming a component of the scroll event themselves.</p>
*
* @param dxConsumed Horizontal distance in pixels consumed by this view during this scroll step
* @param dyConsumed Vertical distance in pixels consumed by this view during this scroll step
* @param dxUnconsumed Horizontal scroll distance in pixels not consumed by this view
* @param dyUnconsumed Horizontal scroll distance in pixels not consumed by this view
* @param offsetInWindow Optional. If not null, on return this will contain the offset
* in local view coordinates of this view from before this operation
* to after it completes. View implementations may use this to adjust
* expected input coordinate tracking.
* @param type the type of input which cause this scroll event
* @return true if the event was dispatched, false if it could not be dispatched.
* @see #dispatchNestedPreScroll(View, int, int, int[], int[])
*/
public static boolean dispatchNestedScroll(@NonNull View view, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow,
@NestedScrollType int type) {
if (view instanceof NestedScrollingChild2) {
return ((NestedScrollingChild2) view).dispatchNestedScroll(dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed, offsetInWindow, type);
} else if (type == ViewCompat.TYPE_TOUCH) {
return dispatchNestedScroll(view, dxConsumed, dyConsumed, dxUnconsumed,
dyUnconsumed, offsetInWindow);
}
return false;
}
/**
* Dispatch one step of a nested scroll in progress before this view consumes any portion of it.
*
* <p>Nested pre-scroll events are to nested scroll events what touch intercept is to touch.
* <code>dispatchNestedPreScroll</code> offers an opportunity for the parent view in a nested
* scrolling operation to consume some or all of the scroll operation before the child view
* consumes it.</p>
*
* @param dx Horizontal scroll distance in pixels
* @param dy Vertical scroll distance in pixels
* @param consumed Output. If not null, consumed[0] will contain the consumed component of dx
* and consumed[1] the consumed dy.
* @param offsetInWindow Optional. If not null, on return this will contain the offset
* in local view coordinates of this view from before this operation
* to after it completes. View implementations may use this to adjust
* expected input coordinate tracking.
* @param type the type of input which cause this scroll event
* @return true if the parent consumed some or all of the scroll delta
* @see #dispatchNestedScroll(View, int, int, int, int, int[])
*/
public static boolean dispatchNestedPreScroll(@NonNull View view, int dx, int dy,
@Nullable int[] consumed, @Nullable int[] offsetInWindow, @NestedScrollType int type) {
if (view instanceof NestedScrollingChild2) {
return ((NestedScrollingChild2) view).dispatchNestedPreScroll(dx, dy, consumed,
offsetInWindow, type);
} else if (type == ViewCompat.TYPE_TOUCH) {
return dispatchNestedPreScroll(view, dx, dy, consumed, offsetInWindow);
}
return false;
}
/**
* Dispatch a fling to a nested scrolling parent.
*
* <p>This method should be used to indicate that a nested scrolling child has detected
* suitable conditions for a fling. Generally this means that a touch scroll has ended with a
* {@link VelocityTracker velocity} in the direction of scrolling that meets or exceeds
* the {@link ViewConfiguration#getScaledMinimumFlingVelocity() minimum fling velocity}
* along a scrollable axis.</p>
*
* <p>If a nested scrolling child view would normally fling but it is at the edge of
* its own content, it can use this method to delegate the fling to its nested scrolling
* parent instead. The parent may optionally consume the fling or observe a child fling.</p>
*
* @param velocityX Horizontal fling velocity in pixels per second
* @param velocityY Vertical fling velocity in pixels per second
* @param consumed true if the child consumed the fling, false otherwise
* @return true if the nested scrolling parent consumed or otherwise reacted to the fling
*/
@SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
public static boolean dispatchNestedFling(@NonNull View view, float velocityX, float velocityY,
boolean consumed) {
if (Build.VERSION.SDK_INT >= 21) {
return view.dispatchNestedFling(velocityX, velocityY, consumed);
}
if (view instanceof NestedScrollingChild) {
return ((NestedScrollingChild) view).dispatchNestedFling(velocityX, velocityY,
consumed);
}
return false;
}
/**
* Dispatch a fling to a nested scrolling parent before it is processed by this view.
*
* <p>Nested pre-fling events are to nested fling events what touch intercept is to touch
* and what nested pre-scroll is to nested scroll. <code>dispatchNestedPreFling</code>
* offsets an opportunity for the parent view in a nested fling to fully consume the fling
* before the child view consumes it. If this method returns <code>true</code>, a nested
* parent view consumed the fling and this view should not scroll as a result.</p>
*
* <p>For a better user experience, only one view in a nested scrolling chain should consume
* the fling at a time. If a parent view consumed the fling this method will return false.
* Custom view implementations should account for this in two ways:</p>
*
* <ul>
* <li>If a custom view is paged and needs to settle to a fixed page-point, do not
* call <code>dispatchNestedPreFling</code>; consume the fling and settle to a valid
* position regardless.</li>
* <li>If a nested parent does consume the fling, this view should not scroll at all,
* even to settle back to a valid idle position.</li>
* </ul>
*
* <p>Views should also not offer fling velocities to nested parent views along an axis
* where scrolling is not currently supported; a {@link android.widget.ScrollView ScrollView}
* should not offer a horizontal fling velocity to its parents since scrolling along that
* axis is not permitted and carrying velocity along that motion does not make sense.</p>
*
* @param velocityX Horizontal fling velocity in pixels per second
* @param velocityY Vertical fling velocity in pixels per second
* @return true if a nested scrolling parent consumed the fling
*/
@SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
public static boolean dispatchNestedPreFling(@NonNull View view, float velocityX,
float velocityY) {
if (Build.VERSION.SDK_INT >= 21) {
return view.dispatchNestedPreFling(velocityX, velocityY);
}
if (view instanceof NestedScrollingChild) {
return ((NestedScrollingChild) view).dispatchNestedPreFling(velocityX, velocityY);
}
return false;
}
/**
* Returns whether the view hierarchy is currently undergoing a layout pass. This
* information is useful to avoid situations such as calling {@link View#requestLayout()}
* during a layout pass.
* <p>
* Compatibility:
* <ul>
* <li>API &lt; 18: Always returns {@code false}</li>
* </ul>
*
* @return whether the view hierarchy is currently undergoing a layout pass
*/
public static boolean isInLayout(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 18) {
return view.isInLayout();
}
return false;
}
/**
* Returns true if {@code view} has been through at least one layout since it
* was last attached to or detached from a window.
*/
public static boolean isLaidOut(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 19) {
return view.isLaidOut();
}
return view.getWidth() > 0 && view.getHeight() > 0;
}
/**
* Returns whether layout direction has been resolved.
* <p>
* Compatibility:
* <ul>
* <li>API &lt; 19: Always returns {@code false}</li>
* </ul>
*
* @return true if layout direction has been resolved.
*/
public static boolean isLayoutDirectionResolved(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 19) {
return view.isLayoutDirectionResolved();
}
return false;
}
/**
* The visual z position of this view, in pixels. This is equivalent to the
* {@link #setTranslationZ(View, float) translationZ} property plus the current
* {@link #getElevation(View) elevation} property.
*
* @return The visual z position of this view, in pixels.
*/
public static float getZ(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 21) {
return view.getZ();
}
return 0f;
}
/**
* Sets the visual z position of this view, in pixels. This is equivalent to setting the
* {@link #setTranslationZ(View, float) translationZ} property to be the difference between
* the x value passed in and the current {@link #getElevation(View) elevation} property.
* <p>
* Compatibility:
* <ul>
* <li>API &lt; 21: No-op
* </ul>
*
* @param z The visual z position of this view, in pixels.
*/
public static void setZ(@NonNull View view, float z) {
if (Build.VERSION.SDK_INT >= 21) {
view.setZ(z);
}
}
/**
* Offset this view's vertical location by the specified number of pixels.
*
* @param offset the number of pixels to offset the view by
*/
public static void offsetTopAndBottom(@NonNull View view, int offset) {
if (Build.VERSION.SDK_INT >= 23) {
view.offsetTopAndBottom(offset);
} else if (Build.VERSION.SDK_INT >= 21) {
final Rect parentRect = getEmptyTempRect();
boolean needInvalidateWorkaround = false;
final ViewParent parent = view.getParent();
if (parent instanceof View) {
final View p = (View) parent;
parentRect.set(p.getLeft(), p.getTop(), p.getRight(), p.getBottom());
// If the view currently does not currently intersect the parent (and is therefore
// not displayed) we may need need to invalidate
needInvalidateWorkaround = !parentRect.intersects(view.getLeft(), view.getTop(),
view.getRight(), view.getBottom());
}
// Now offset, invoking the API 14+ implementation (which contains its own workarounds)
compatOffsetTopAndBottom(view, offset);
// The view has now been offset, so let's intersect the Rect and invalidate where
// the View is now displayed
if (needInvalidateWorkaround && parentRect.intersect(view.getLeft(), view.getTop(),
view.getRight(), view.getBottom())) {
((View) parent).invalidate(parentRect);
}
} else {
compatOffsetTopAndBottom(view, offset);
}
}
private static void compatOffsetTopAndBottom(View view, int offset) {
view.offsetTopAndBottom(offset);
if (view.getVisibility() == View.VISIBLE) {
tickleInvalidationFlag(view);
ViewParent parent = view.getParent();
if (parent instanceof View) {
tickleInvalidationFlag((View) parent);
}
}
}
/**
* Offset this view's horizontal location by the specified amount of pixels.
*
* @param offset the number of pixels to offset the view by
*/
public static void offsetLeftAndRight(@NonNull View view, int offset) {
if (Build.VERSION.SDK_INT >= 23) {
view.offsetLeftAndRight(offset);
} else if (Build.VERSION.SDK_INT >= 21) {
final Rect parentRect = getEmptyTempRect();
boolean needInvalidateWorkaround = false;
final ViewParent parent = view.getParent();
if (parent instanceof View) {
final View p = (View) parent;
parentRect.set(p.getLeft(), p.getTop(), p.getRight(), p.getBottom());
// If the view currently does not currently intersect the parent (and is therefore
// not displayed) we may need need to invalidate
needInvalidateWorkaround = !parentRect.intersects(view.getLeft(), view.getTop(),
view.getRight(), view.getBottom());
}
// Now offset, invoking the API 14+ implementation (which contains its own workarounds)
compatOffsetLeftAndRight(view, offset);
// The view has now been offset, so let's intersect the Rect and invalidate where
// the View is now displayed
if (needInvalidateWorkaround && parentRect.intersect(view.getLeft(), view.getTop(),
view.getRight(), view.getBottom())) {
((View) parent).invalidate(parentRect);
}
} else {
compatOffsetLeftAndRight(view, offset);
}
}
private static void compatOffsetLeftAndRight(View view, int offset) {
view.offsetLeftAndRight(offset);
if (view.getVisibility() == View.VISIBLE) {
tickleInvalidationFlag(view);
ViewParent parent = view.getParent();
if (parent instanceof View) {
tickleInvalidationFlag((View) parent);
}
}
}
private static void tickleInvalidationFlag(View view) {
final float y = view.getTranslationY();
view.setTranslationY(y + 1);
view.setTranslationY(y);
}
/**
* Sets a rectangular area on this view to which the view will be clipped
* when it is drawn. Setting the value to null will remove the clip bounds
* and the view will draw normally, using its full bounds.
*
* <p>Prior to API 18 this does nothing.</p>
*
* @param view The view to set clipBounds.
* @param clipBounds The rectangular area, in the local coordinates of
* this view, to which future drawing operations will be clipped.
*/
public static void setClipBounds(@NonNull View view, Rect clipBounds) {
if (Build.VERSION.SDK_INT >= 18) {
view.setClipBounds(clipBounds);
}
}
/**
* Returns a copy of the current {@link #setClipBounds(View, Rect)}.
*
* <p>Prior to API 18 this will return null.</p>
*
* @return A copy of the current clip bounds if clip bounds are set,
* otherwise null.
*/
@Nullable
public static Rect getClipBounds(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 18) {
return view.getClipBounds();
}
return null;
}
/**
* Returns true if the provided view is currently attached to a window.
*/
public static boolean isAttachedToWindow(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 19) {
return view.isAttachedToWindow();
}
return view.getWindowToken() != null;
}
/**
* Returns whether the provided view has an attached {@link View.OnClickListener}.
*
* @return true if there is a listener, false if there is none.
*/
public static boolean hasOnClickListeners(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 15) {
return view.hasOnClickListeners();
}
return false;
}
/**
* Sets the state of all scroll indicators.
* <p>
* See {@link #setScrollIndicators(View, int, int)} for usage information.
*
* @param indicators a bitmask of indicators that should be enabled, or
* {@code 0} to disable all indicators
*
* @see #setScrollIndicators(View, int, int)
* @see #getScrollIndicators(View)
*/
public static void setScrollIndicators(@NonNull View view, @ScrollIndicators int indicators) {
if (Build.VERSION.SDK_INT >= 23) {
view.setScrollIndicators(indicators);
}
}
/**
* Sets the state of the scroll indicators specified by the mask. To change
* all scroll indicators at once, see {@link #setScrollIndicators(View, int)}.
* <p>
* When a scroll indicator is enabled, it will be displayed if the view
* can scroll in the direction of the indicator.
* <p>
* Multiple indicator types may be enabled or disabled by passing the
* logical OR of the desired types. If multiple types are specified, they
* will all be set to the same enabled state.
* <p>
* For example, to enable the top scroll indicatorExample: {@code setScrollIndicators}
*
* @param indicators the indicator direction, or the logical OR of multiple
* indicator directions. One or more of:
* <ul>
* <li>{@link #SCROLL_INDICATOR_TOP}</li>
* <li>{@link #SCROLL_INDICATOR_BOTTOM}</li>
* <li>{@link #SCROLL_INDICATOR_LEFT}</li>
* <li>{@link #SCROLL_INDICATOR_RIGHT}</li>
* <li>{@link #SCROLL_INDICATOR_START}</li>
* <li>{@link #SCROLL_INDICATOR_END}</li>
* </ul>
*
* @see #setScrollIndicators(View, int)
* @see #getScrollIndicators(View)
*/
public static void setScrollIndicators(@NonNull View view, @ScrollIndicators int indicators,
@ScrollIndicators int mask) {
if (Build.VERSION.SDK_INT >= 23) {
view.setScrollIndicators(indicators, mask);
}
}
/**
* Returns a bitmask representing the enabled scroll indicators.
* <p>
* For example, if the top and left scroll indicators are enabled and all
* other indicators are disabled, the return value will be
* {@code ViewCompat.SCROLL_INDICATOR_TOP | ViewCompat.SCROLL_INDICATOR_LEFT}.
* <p>
* To check whether the bottom scroll indicator is enabled, use the value
* of {@code (ViewCompat.getScrollIndicators(view) & ViewCompat.SCROLL_INDICATOR_BOTTOM) != 0}.
*
* @return a bitmask representing the enabled scroll indicators
*/
public static int getScrollIndicators(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 23) {
return view.getScrollIndicators();
}
return 0;
}
/**
* Set the pointer icon for the current view.
* @param pointerIcon A PointerIconCompat instance which will be shown when the mouse hovers.
*/
public static void setPointerIcon(@NonNull View view, PointerIconCompat pointerIcon) {
if (Build.VERSION.SDK_INT >= 24) {
view.setPointerIcon((PointerIcon) (pointerIcon != null
? pointerIcon.getPointerIcon() : null));
}
}
/**
* Gets the logical display to which the view's window has been attached.
* <p>
* Compatibility:
* <ul>
* <li>API &lt; 17: Returns the default display when the view is attached. Otherwise, null.
* </ul>
*
* @return The logical display, or null if the view is not currently attached to a window.
*/
@Nullable
public static Display getDisplay(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 17) {
return view.getDisplay();
}
if (isAttachedToWindow(view)) {
final WindowManager wm = (WindowManager) view.getContext().getSystemService(
Context.WINDOW_SERVICE);
return wm.getDefaultDisplay();
}
return null;
}
/**
* Sets the tooltip for the view.
*
* <p>Prior to API 26 this does nothing. Use TooltipCompat class from v7 appcompat library
* for a compatible tooltip implementation.</p>
*
* @param tooltipText the tooltip text
*/
public static void setTooltipText(@NonNull View view, @Nullable CharSequence tooltipText) {
if (Build.VERSION.SDK_INT >= 26) {
view.setTooltipText(tooltipText);
}
}
/**
* Start the drag and drop operation.
*/
public static boolean startDragAndDrop(@NonNull View v, ClipData data,
View.DragShadowBuilder shadowBuilder, Object localState, int flags) {
if (Build.VERSION.SDK_INT >= 24) {
return v.startDragAndDrop(data, shadowBuilder, localState, flags);
} else {
return v.startDrag(data, shadowBuilder, localState, flags);
}
}
/**
* Cancel the drag and drop operation.
*/
public static void cancelDragAndDrop(@NonNull View v) {
if (Build.VERSION.SDK_INT >= 24) {
v.cancelDragAndDrop();
}
}
/**
* Update the drag shadow while drag and drop is in progress.
*/
public static void updateDragShadow(@NonNull View v, View.DragShadowBuilder shadowBuilder) {
if (Build.VERSION.SDK_INT >= 24) {
v.updateDragShadow(shadowBuilder);
}
}
/**
* Gets the ID of the next keyboard navigation cluster root.
*
* @return the next keyboard navigation cluster ID, or {@link View#NO_ID} if the framework
* should decide automatically or API < 26.
*/
public static int getNextClusterForwardId(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 26) {
return view.getNextClusterForwardId();
}
return View.NO_ID;
}
/**
* Sets the ID of the next keyboard navigation cluster root view. Does nothing if {@code view}
* is not a keyboard navigation cluster or if API < 26.
*
* @param nextClusterForwardId next cluster ID, or {@link View#NO_ID} if the framework
* should decide automatically.
*/
public static void setNextClusterForwardId(@NonNull View view, int nextClusterForwardId) {
if (Build.VERSION.SDK_INT >= 26) {
view.setNextClusterForwardId(nextClusterForwardId);
}
}
/**
* Returns whether {@code view} is a root of a keyboard navigation cluster. Always returns
* {@code false} on API < 26.
*
* @return {@code true} if this view is a root of a cluster, or {@code false} otherwise.
*/
public static boolean isKeyboardNavigationCluster(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 26) {
return view.isKeyboardNavigationCluster();
}
return false;
}
/**
* Set whether {@code view} is a root of a keyboard navigation cluster. Does nothing if
* API < 26.
*
* @param isCluster {@code true} to mark {@code view} as the root of a cluster, {@code false}
* to unmark.
*/
public static void setKeyboardNavigationCluster(@NonNull View view, boolean isCluster) {
if (Build.VERSION.SDK_INT >= 26) {
view.setKeyboardNavigationCluster(isCluster);
}
}
/**
* Returns whether {@code view} should receive focus when the focus is restored for the view
* hierarchy containing it. Returns {@code false} on API < 26.
* <p>
* Focus gets restored for a view hierarchy when the root of the hierarchy gets added to a
* window or serves as a target of cluster navigation.
*
* @return {@code true} if {@code view} is the default-focus view, {@code false} otherwise.
*/
public static boolean isFocusedByDefault(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 26) {
return view.isFocusedByDefault();
}
return false;
}
/**
* Sets whether {@code view} should receive focus when the focus is restored for the view
* hierarchy containing it.
* <p>
* Focus gets restored for a view hierarchy when the root of the hierarchy gets added to a
* window or serves as a target of cluster navigation.
* <p>
* Does nothing on API < 26.
*
* @param isFocusedByDefault {@code true} to set {@code view} as the default-focus view,
* {@code false} otherwise.
*/
public static void setFocusedByDefault(@NonNull View view, boolean isFocusedByDefault) {
if (Build.VERSION.SDK_INT >= 26) {
view.setFocusedByDefault(isFocusedByDefault);
}
}
/**
* Find the nearest keyboard navigation cluster in the specified direction.
* This does not actually give focus to that cluster.
*
* @param currentCluster The starting point of the search. {@code null} means the current
* cluster is not found yet.
* @param direction Direction to look.
*
* @return the nearest keyboard navigation cluster in the specified direction, or {@code null}
* if one can't be found or if API < 26.
*/
public static View keyboardNavigationClusterSearch(@NonNull View view, View currentCluster,
@FocusDirection int direction) {
if (Build.VERSION.SDK_INT >= 26) {
return view.keyboardNavigationClusterSearch(currentCluster, direction);
}
return null;
}
/**
* Adds any keyboard navigation cluster roots that are descendants of {@code view} (
* including {@code view} if it is a cluster root itself) to {@code views}. Does nothing
* on API < 26.
*
* @param views collection of keyboard navigation cluster roots found so far.
* @param direction direction to look.
*/
public static void addKeyboardNavigationClusters(@NonNull View view,
@NonNull Collection<View> views, int direction) {
if (Build.VERSION.SDK_INT >= 26) {
view.addKeyboardNavigationClusters(views, direction);
}
}
/**
* Gives focus to the default-focus view in the view hierarchy rooted at {@code view}.
* If the default-focus view cannot be found or if API < 26, this falls back to calling
* {@link View#requestFocus(int)}.
*
* @return {@code true} if {@code view} or one of its descendants took focus, {@code false}
* otherwise.
*/
public static boolean restoreDefaultFocus(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 26) {
return view.restoreDefaultFocus();
}
return view.requestFocus();
}
/**
* Returns true if this view is focusable or if it contains a reachable View
* for which {@link View#hasExplicitFocusable()} returns {@code true}.
* A "reachable hasExplicitFocusable()" is a view whose parents do not block descendants focus.
* Only {@link View#VISIBLE} views for which {@link View#getFocusable()} would return
* {@link View#FOCUSABLE} are considered focusable.
*
* <p>This method preserves the pre-{@link Build.VERSION_CODES#O} behavior of
* {@link View#hasFocusable()} in that only views explicitly set focusable will cause
* this method to return true. A view set to {@link View#FOCUSABLE_AUTO} that resolves
* to focusable will not.</p>
*
* @return {@code true} if the view is focusable or if the view contains a focusable
* view, {@code false} otherwise
*/
public static boolean hasExplicitFocusable(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 26) {
return view.hasExplicitFocusable();
}
return view.hasFocusable();
}
/**
* Generate a value suitable for use in {@link View#setId(int)}.
* This value will not collide with ID values generated at build time by aapt for R.id.
*
* @return a generated ID value
*/
public static int generateViewId() {
if (Build.VERSION.SDK_INT >= 17) {
return View.generateViewId();
}
for (;;) {
final int result = sNextGeneratedId.get();
// aapt-generated IDs have the high byte nonzero; clamp to the range under that.
int newValue = result + 1;
if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
if (sNextGeneratedId.compareAndSet(result, newValue)) {
return result;
}
}
}
protected ViewCompat() {}
}