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