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