Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package androidx.core.widget;
     18 
     19 import android.os.Build;
     20 import android.util.Log;
     21 import android.view.Gravity;
     22 import android.view.View;
     23 import android.widget.PopupWindow;
     24 
     25 import androidx.annotation.NonNull;
     26 import androidx.core.view.GravityCompat;
     27 import androidx.core.view.ViewCompat;
     28 
     29 import java.lang.reflect.Field;
     30 import java.lang.reflect.Method;
     31 
     32 /**
     33  * Helper for accessing features in {@link PopupWindow}.
     34  */
     35 public final class PopupWindowCompat {
     36     private static final String TAG = "PopupWindowCompatApi21";
     37 
     38     private static Method sSetWindowLayoutTypeMethod;
     39     private static boolean sSetWindowLayoutTypeMethodAttempted;
     40     private static Method sGetWindowLayoutTypeMethod;
     41     private static boolean sGetWindowLayoutTypeMethodAttempted;
     42 
     43     private static Field sOverlapAnchorField;
     44     private static boolean sOverlapAnchorFieldAttempted;
     45 
     46     private PopupWindowCompat() {
     47         // This class is not publicly instantiable.
     48     }
     49 
     50     /**
     51      * <p>Display the content view in a popup window anchored to the bottom-left
     52      * corner of the anchor view offset by the specified x and y coordinates.
     53      * If there is not enough room on screen to show
     54      * the popup in its entirety, this method tries to find a parent scroll
     55      * view to scroll. If no parent scroll view can be scrolled, the bottom-left
     56      * corner of the popup is pinned at the top left corner of the anchor view.</p>
     57      * <p>If the view later scrolls to move <code>anchor</code> to a different
     58      * location, the popup will be moved correspondingly.</p>
     59      *
     60      * @param popup the PopupWindow to show
     61      * @param anchor the view on which to pin the popup window
     62      * @param xoff A horizontal offset from the anchor in pixels
     63      * @param yoff A vertical offset from the anchor in pixels
     64      * @param gravity Alignment of the popup relative to the anchor
     65      */
     66     public static void showAsDropDown(@NonNull PopupWindow popup, @NonNull View anchor,
     67             int xoff, int yoff, int gravity) {
     68         if (Build.VERSION.SDK_INT >= 19) {
     69             popup.showAsDropDown(anchor, xoff, yoff, gravity);
     70         } else {
     71             int xoff1 = xoff;
     72             final int hgrav = GravityCompat.getAbsoluteGravity(gravity,
     73                     ViewCompat.getLayoutDirection(anchor)) & Gravity.HORIZONTAL_GRAVITY_MASK;
     74             if (hgrav == Gravity.RIGHT) {
     75                 // Flip the location to align the right sides of the popup and
     76                 // anchor instead of left.
     77                 xoff1 -= (popup.getWidth() - anchor.getWidth());
     78             }
     79             popup.showAsDropDown(anchor, xoff1, yoff);
     80         }
     81     }
     82 
     83     /**
     84      * Sets whether the popup window should overlap its anchor view when
     85      * displayed as a drop-down.
     86      *
     87      * @param overlapAnchor Whether the popup should overlap its anchor.
     88      */
     89     public static void setOverlapAnchor(@NonNull PopupWindow popupWindow, boolean overlapAnchor) {
     90         if (Build.VERSION.SDK_INT >= 23) {
     91             popupWindow.setOverlapAnchor(overlapAnchor);
     92         } else if (Build.VERSION.SDK_INT >= 21) {
     93             if (!sOverlapAnchorFieldAttempted) {
     94                 try {
     95                     sOverlapAnchorField = PopupWindow.class.getDeclaredField("mOverlapAnchor");
     96                     sOverlapAnchorField.setAccessible(true);
     97                 } catch (NoSuchFieldException e) {
     98                     Log.i(TAG, "Could not fetch mOverlapAnchor field from PopupWindow", e);
     99                 }
    100                 sOverlapAnchorFieldAttempted = true;
    101             }
    102             if (sOverlapAnchorField != null) {
    103                 try {
    104                     sOverlapAnchorField.set(popupWindow, overlapAnchor);
    105                 } catch (IllegalAccessException e) {
    106                     Log.i(TAG, "Could not set overlap anchor field in PopupWindow", e);
    107                 }
    108             }
    109         }
    110     }
    111 
    112     /**
    113      * Returns whether the popup window should overlap its anchor view when
    114      * displayed as a drop-down.
    115      *
    116      * @return Whether the popup should overlap its anchor.
    117      */
    118     public static boolean getOverlapAnchor(@NonNull PopupWindow popupWindow) {
    119         if (Build.VERSION.SDK_INT >= 23) {
    120             return popupWindow.getOverlapAnchor();
    121         }
    122         if (Build.VERSION.SDK_INT >= 21) {
    123             if (!sOverlapAnchorFieldAttempted) {
    124                 try {
    125                     sOverlapAnchorField = PopupWindow.class.getDeclaredField("mOverlapAnchor");
    126                     sOverlapAnchorField.setAccessible(true);
    127                 } catch (NoSuchFieldException e) {
    128                     Log.i(TAG, "Could not fetch mOverlapAnchor field from PopupWindow", e);
    129                 }
    130                 sOverlapAnchorFieldAttempted = true;
    131             }
    132             if (sOverlapAnchorField != null) {
    133                 try {
    134                     return (Boolean) sOverlapAnchorField.get(popupWindow);
    135                 } catch (IllegalAccessException e) {
    136                     Log.i(TAG, "Could not get overlap anchor field in PopupWindow", e);
    137                 }
    138             }
    139         }
    140         return false;
    141     }
    142 
    143     /**
    144      * Set the layout type for this window. This value will be passed through to
    145      * {@link android.view.WindowManager.LayoutParams#type} therefore the value should match any
    146      * value {@link android.view.WindowManager.LayoutParams#type} accepts.
    147      *
    148      * @param layoutType Layout type for this window.
    149      *
    150      * @see android.view.WindowManager.LayoutParams#type
    151      */
    152     public static void setWindowLayoutType(@NonNull PopupWindow popupWindow, int layoutType) {
    153         if (Build.VERSION.SDK_INT >= 23) {
    154             popupWindow.setWindowLayoutType(layoutType);
    155             return;
    156         }
    157 
    158         if (!sSetWindowLayoutTypeMethodAttempted) {
    159             try {
    160                 sSetWindowLayoutTypeMethod = PopupWindow.class.getDeclaredMethod(
    161                         "setWindowLayoutType", int.class);
    162                 sSetWindowLayoutTypeMethod.setAccessible(true);
    163             } catch (Exception e) {
    164                 // Reflection method fetch failed. Oh well.
    165             }
    166             sSetWindowLayoutTypeMethodAttempted = true;
    167         }
    168         if (sSetWindowLayoutTypeMethod != null) {
    169             try {
    170                 sSetWindowLayoutTypeMethod.invoke(popupWindow, layoutType);
    171             } catch (Exception e) {
    172                 // Reflection call failed. Oh well.
    173             }
    174         }
    175     }
    176 
    177     /**
    178      * Returns the layout type for this window.
    179      *
    180      * @see #setWindowLayoutType(PopupWindow popupWindow, int)
    181      */
    182     public static int getWindowLayoutType(@NonNull PopupWindow popupWindow) {
    183         if (Build.VERSION.SDK_INT >= 23) {
    184             return popupWindow.getWindowLayoutType();
    185         }
    186 
    187         if (!sGetWindowLayoutTypeMethodAttempted) {
    188             try {
    189                 sGetWindowLayoutTypeMethod = PopupWindow.class.getDeclaredMethod(
    190                         "getWindowLayoutType");
    191                 sGetWindowLayoutTypeMethod.setAccessible(true);
    192             } catch (Exception e) {
    193                 // Reflection method fetch failed. Oh well.
    194             }
    195             sGetWindowLayoutTypeMethodAttempted = true;
    196         }
    197         if (sGetWindowLayoutTypeMethod != null) {
    198             try {
    199                 return (Integer) sGetWindowLayoutTypeMethod.invoke(popupWindow);
    200             } catch (Exception e) {
    201                 // Reflection call failed. Oh well.
    202             }
    203         }
    204         return 0;
    205     }
    206 }
    207