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