PopupWindowCompat.java revision ac551a4de4413a97241d086d3857c4b5ed636e29
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
17package android.support.v4.widget;
18
19import android.os.Build;
20import android.support.annotation.RequiresApi;
21import android.support.v4.view.GravityCompat;
22import android.support.v4.view.ViewCompat;
23import android.util.Log;
24import android.view.Gravity;
25import android.view.View;
26import android.widget.PopupWindow;
27
28import java.lang.reflect.Field;
29import java.lang.reflect.Method;
30
31/**
32 * Helper for accessing features in PopupWindow in a backwards compatible fashion.
33 */
34public final class PopupWindowCompat {
35
36    static class PopupWindowCompatBaseImpl {
37        private static Method sSetWindowLayoutTypeMethod;
38        private static boolean sSetWindowLayoutTypeMethodAttempted;
39        private static Method sGetWindowLayoutTypeMethod;
40        private static boolean sGetWindowLayoutTypeMethodAttempted;
41
42        public void showAsDropDown(PopupWindow popup, View anchor, int xoff, int yoff,
43                int gravity) {
44            final int hgrav = GravityCompat.getAbsoluteGravity(gravity,
45                    ViewCompat.getLayoutDirection(anchor)) & Gravity.HORIZONTAL_GRAVITY_MASK;
46            if (hgrav == Gravity.RIGHT) {
47                // Flip the location to align the right sides of the popup and
48                // anchor instead of left.
49                xoff -= (popup.getWidth() - anchor.getWidth());
50            }
51            popup.showAsDropDown(anchor, xoff, yoff);
52        }
53
54        public void setOverlapAnchor(PopupWindow popupWindow, boolean overlapAnchor) {
55            // noop
56        }
57
58        public boolean getOverlapAnchor(PopupWindow popupWindow) {
59            return false;
60        }
61
62        public void setWindowLayoutType(PopupWindow popupWindow, int layoutType) {
63            if (!sSetWindowLayoutTypeMethodAttempted) {
64                try {
65                    sSetWindowLayoutTypeMethod = PopupWindow.class.getDeclaredMethod(
66                            "setWindowLayoutType", int.class);
67                    sSetWindowLayoutTypeMethod.setAccessible(true);
68                } catch (Exception e) {
69                    // Reflection method fetch failed. Oh well.
70                }
71                sSetWindowLayoutTypeMethodAttempted = true;
72            }
73
74            if (sSetWindowLayoutTypeMethod != null) {
75                try {
76                    sSetWindowLayoutTypeMethod.invoke(popupWindow, layoutType);
77                } catch (Exception e) {
78                    // Reflection call failed. Oh well.
79                }
80            }
81        }
82
83        public int getWindowLayoutType(PopupWindow popupWindow) {
84            if (!sGetWindowLayoutTypeMethodAttempted) {
85                try {
86                    sGetWindowLayoutTypeMethod = PopupWindow.class.getDeclaredMethod(
87                            "getWindowLayoutType");
88                    sGetWindowLayoutTypeMethod.setAccessible(true);
89                } catch (Exception e) {
90                    // Reflection method fetch failed. Oh well.
91                }
92                sGetWindowLayoutTypeMethodAttempted = true;
93            }
94
95            if (sGetWindowLayoutTypeMethod != null) {
96                try {
97                    return (Integer) sGetWindowLayoutTypeMethod.invoke(popupWindow);
98                } catch (Exception e) {
99                    // Reflection call failed. Oh well.
100                }
101            }
102            return 0;
103        }
104    }
105
106    /**
107     * Interface implementation for devices with at least KitKat APIs.
108     */
109    @RequiresApi(19)
110    static class PopupWindowCompatApi19Impl extends PopupWindowCompatBaseImpl {
111        @Override
112        public void showAsDropDown(PopupWindow popup, View anchor, int xoff, int yoff,
113                int gravity) {
114            popup.showAsDropDown(anchor, xoff, yoff, gravity);
115        }
116    }
117
118    @RequiresApi(21)
119    static class PopupWindowCompatApi21Impl extends PopupWindowCompatApi19Impl {
120        private static final String TAG = "PopupWindowCompatApi21";
121
122        private static Field sOverlapAnchorField;
123
124        static {
125            try {
126                sOverlapAnchorField = PopupWindow.class.getDeclaredField("mOverlapAnchor");
127                sOverlapAnchorField.setAccessible(true);
128            } catch (NoSuchFieldException e) {
129                Log.i(TAG, "Could not fetch mOverlapAnchor field from PopupWindow", e);
130            }
131        }
132
133        @Override
134        public void setOverlapAnchor(PopupWindow popupWindow, boolean overlapAnchor) {
135            if (sOverlapAnchorField != null) {
136                try {
137                    sOverlapAnchorField.set(popupWindow, overlapAnchor);
138                } catch (IllegalAccessException e) {
139                    Log.i(TAG, "Could not set overlap anchor field in PopupWindow", e);
140                }
141            }
142        }
143
144        @Override
145        public boolean getOverlapAnchor(PopupWindow popupWindow) {
146            if (sOverlapAnchorField != null) {
147                try {
148                    return (Boolean) sOverlapAnchorField.get(popupWindow);
149                } catch (IllegalAccessException e) {
150                    Log.i(TAG, "Could not get overlap anchor field in PopupWindow", e);
151                }
152            }
153            return false;
154        }
155    }
156
157    @RequiresApi(23)
158    static class PopupWindowCompatApi23Impl extends PopupWindowCompatApi21Impl {
159        @Override
160        public void setOverlapAnchor(PopupWindow popupWindow, boolean overlapAnchor) {
161            popupWindow.setOverlapAnchor(overlapAnchor);
162        }
163
164        @Override
165        public boolean getOverlapAnchor(PopupWindow popupWindow) {
166            return popupWindow.getOverlapAnchor();
167        }
168
169        @Override
170        public void setWindowLayoutType(PopupWindow popupWindow, int layoutType) {
171            popupWindow.setWindowLayoutType(layoutType);
172        }
173
174        @Override
175        public int getWindowLayoutType(PopupWindow popupWindow) {
176            return popupWindow.getWindowLayoutType();
177        }
178    }
179
180    /**
181     * Select the correct implementation to use for the current platform.
182     */
183    static final PopupWindowCompatBaseImpl IMPL;
184    static {
185        if (Build.VERSION.SDK_INT >= 23) {
186            IMPL = new PopupWindowCompatApi23Impl();
187        } else if (Build.VERSION.SDK_INT >= 21) {
188            IMPL = new PopupWindowCompatApi21Impl();
189        } else if (Build.VERSION.SDK_INT >= 19) {
190            IMPL = new PopupWindowCompatApi19Impl();
191        } else {
192            IMPL = new PopupWindowCompatBaseImpl();
193        }
194    }
195
196    private PopupWindowCompat() {
197        // This class is not publicly instantiable.
198    }
199
200    /**
201     * <p>Display the content view in a popup window anchored to the bottom-left
202     * corner of the anchor view offset by the specified x and y coordinates.
203     * If there is not enough room on screen to show
204     * the popup in its entirety, this method tries to find a parent scroll
205     * view to scroll. If no parent scroll view can be scrolled, the bottom-left
206     * corner of the popup is pinned at the top left corner of the anchor view.</p>
207     * <p>If the view later scrolls to move <code>anchor</code> to a different
208     * location, the popup will be moved correspondingly.</p>
209     *
210     * @param popup the PopupWindow to show
211     * @param anchor the view on which to pin the popup window
212     * @param xoff A horizontal offset from the anchor in pixels
213     * @param yoff A vertical offset from the anchor in pixels
214     * @param gravity Alignment of the popup relative to the anchor
215     */
216    public static void showAsDropDown(PopupWindow popup, View anchor, int xoff, int yoff,
217            int gravity) {
218        IMPL.showAsDropDown(popup, anchor, xoff, yoff, gravity);
219    }
220
221    /**
222     * Sets whether the popup window should overlap its anchor view when
223     * displayed as a drop-down.
224     *
225     * @param overlapAnchor Whether the popup should overlap its anchor.
226     */
227    public static void setOverlapAnchor(PopupWindow popupWindow, boolean overlapAnchor) {
228        IMPL.setOverlapAnchor(popupWindow, overlapAnchor);
229    }
230
231    /**
232     * Returns whether the popup window should overlap its anchor view when
233     * displayed as a drop-down.
234     *
235     * @return Whether the popup should overlap its anchor.
236     */
237    public static boolean getOverlapAnchor(PopupWindow popupWindow) {
238        return IMPL.getOverlapAnchor(popupWindow);
239    }
240
241    /**
242     * Set the layout type for this window. This value will be passed through to
243     * {@link android.view.WindowManager.LayoutParams#type} therefore the value should match any
244     * value {@link android.view.WindowManager.LayoutParams#type} accepts.
245     *
246     * @param layoutType Layout type for this window.
247     *
248     * @see android.view.WindowManager.LayoutParams#type
249     */
250    public static void setWindowLayoutType(PopupWindow popupWindow, int layoutType) {
251        IMPL.setWindowLayoutType(popupWindow, layoutType);
252    }
253
254    /**
255     * Returns the layout type for this window.
256     *
257     * @see #setWindowLayoutType(PopupWindow popupWindow, int)
258     */
259    public static int getWindowLayoutType(PopupWindow popupWindow) {
260        return IMPL.getWindowLayoutType(popupWindow);
261    }
262}
263