Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2010 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 android.widget;
     18 
     19 import android.annotation.MenuRes;
     20 import android.annotation.TestApi;
     21 import android.annotation.UnsupportedAppUsage;
     22 import android.content.Context;
     23 import android.view.Gravity;
     24 import android.view.Menu;
     25 import android.view.MenuInflater;
     26 import android.view.MenuItem;
     27 import android.view.View;
     28 import android.view.View.OnTouchListener;
     29 
     30 import com.android.internal.R;
     31 import com.android.internal.view.menu.MenuBuilder;
     32 import com.android.internal.view.menu.MenuPopupHelper;
     33 import com.android.internal.view.menu.ShowableListMenu;
     34 
     35 /**
     36  * A PopupMenu displays a {@link Menu} in a modal popup window anchored to a
     37  * {@link View}. The popup will appear below the anchor view if there is room,
     38  * or above it if there is not. If the IME is visible the popup will not
     39  * overlap it until it is touched. Touching outside of the popup will dismiss
     40  * it.
     41  */
     42 public class PopupMenu {
     43     @UnsupportedAppUsage
     44     private final Context mContext;
     45     private final MenuBuilder mMenu;
     46     private final View mAnchor;
     47     @UnsupportedAppUsage
     48     private final MenuPopupHelper mPopup;
     49 
     50     private OnMenuItemClickListener mMenuItemClickListener;
     51     private OnDismissListener mOnDismissListener;
     52     private OnTouchListener mDragListener;
     53 
     54     /**
     55      * Constructor to create a new popup menu with an anchor view.
     56      *
     57      * @param context Context the popup menu is running in, through which it
     58      *        can access the current theme, resources, etc.
     59      * @param anchor Anchor view for this popup. The popup will appear below
     60      *        the anchor if there is room, or above it if there is not.
     61      */
     62     public PopupMenu(Context context, View anchor) {
     63         this(context, anchor, Gravity.NO_GRAVITY);
     64     }
     65 
     66     /**
     67      * Constructor to create a new popup menu with an anchor view and alignment
     68      * gravity.
     69      *
     70      * @param context Context the popup menu is running in, through which it
     71      *        can access the current theme, resources, etc.
     72      * @param anchor Anchor view for this popup. The popup will appear below
     73      *        the anchor if there is room, or above it if there is not.
     74      * @param gravity The {@link Gravity} value for aligning the popup with its
     75      *        anchor.
     76      */
     77     public PopupMenu(Context context, View anchor, int gravity) {
     78         this(context, anchor, gravity, R.attr.popupMenuStyle, 0);
     79     }
     80 
     81     /**
     82      * Constructor a create a new popup menu with a specific style.
     83      *
     84      * @param context Context the popup menu is running in, through which it
     85      *        can access the current theme, resources, etc.
     86      * @param anchor Anchor view for this popup. The popup will appear below
     87      *        the anchor if there is room, or above it if there is not.
     88      * @param gravity The {@link Gravity} value for aligning the popup with its
     89      *        anchor.
     90      * @param popupStyleAttr An attribute in the current theme that contains a
     91      *        reference to a style resource that supplies default values for
     92      *        the popup window. Can be 0 to not look for defaults.
     93      * @param popupStyleRes A resource identifier of a style resource that
     94      *        supplies default values for the popup window, used only if
     95      *        popupStyleAttr is 0 or can not be found in the theme. Can be 0
     96      *        to not look for defaults.
     97      */
     98     public PopupMenu(Context context, View anchor, int gravity, int popupStyleAttr,
     99             int popupStyleRes) {
    100         mContext = context;
    101         mAnchor = anchor;
    102 
    103         mMenu = new MenuBuilder(context);
    104         mMenu.setCallback(new MenuBuilder.Callback() {
    105             @Override
    106             public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
    107                 if (mMenuItemClickListener != null) {
    108                     return mMenuItemClickListener.onMenuItemClick(item);
    109                 }
    110                 return false;
    111             }
    112 
    113             @Override
    114             public void onMenuModeChange(MenuBuilder menu) {
    115             }
    116         });
    117 
    118         mPopup = new MenuPopupHelper(context, mMenu, anchor, false, popupStyleAttr, popupStyleRes);
    119         mPopup.setGravity(gravity);
    120         mPopup.setOnDismissListener(new PopupWindow.OnDismissListener() {
    121             @Override
    122             public void onDismiss() {
    123                 if (mOnDismissListener != null) {
    124                     mOnDismissListener.onDismiss(PopupMenu.this);
    125                 }
    126             }
    127         });
    128     }
    129 
    130     /**
    131      * Sets the gravity used to align the popup window to its anchor view.
    132      * <p>
    133      * If the popup is showing, calling this method will take effect only
    134      * the next time the popup is shown.
    135      *
    136      * @param gravity the gravity used to align the popup window
    137      * @see #getGravity()
    138      */
    139     public void setGravity(int gravity) {
    140         mPopup.setGravity(gravity);
    141     }
    142 
    143     /**
    144      * @return the gravity used to align the popup window to its anchor view
    145      * @see #setGravity(int)
    146      */
    147     public int getGravity() {
    148         return mPopup.getGravity();
    149     }
    150 
    151     /**
    152      * Returns an {@link OnTouchListener} that can be added to the anchor view
    153      * to implement drag-to-open behavior.
    154      * <p>
    155      * When the listener is set on a view, touching that view and dragging
    156      * outside of its bounds will open the popup window. Lifting will select
    157      * the currently touched list item.
    158      * <p>
    159      * Example usage:
    160      * <pre>
    161      * PopupMenu myPopup = new PopupMenu(context, myAnchor);
    162      * myAnchor.setOnTouchListener(myPopup.getDragToOpenListener());
    163      * </pre>
    164      *
    165      * @return a touch listener that controls drag-to-open behavior
    166      */
    167     public OnTouchListener getDragToOpenListener() {
    168         if (mDragListener == null) {
    169             mDragListener = new ForwardingListener(mAnchor) {
    170                 @Override
    171                 protected boolean onForwardingStarted() {
    172                     show();
    173                     return true;
    174                 }
    175 
    176                 @Override
    177                 protected boolean onForwardingStopped() {
    178                     dismiss();
    179                     return true;
    180                 }
    181 
    182                 @Override
    183                 public ShowableListMenu getPopup() {
    184                     // This will be null until show() is called.
    185                     return mPopup.getPopup();
    186                 }
    187             };
    188         }
    189 
    190         return mDragListener;
    191     }
    192 
    193     /**
    194      * Returns the {@link Menu} associated with this popup. Populate the
    195      * returned Menu with items before calling {@link #show()}.
    196      *
    197      * @return the {@link Menu} associated with this popup
    198      * @see #show()
    199      * @see #getMenuInflater()
    200      */
    201     public Menu getMenu() {
    202         return mMenu;
    203     }
    204 
    205     /**
    206      * @return a {@link MenuInflater} that can be used to inflate menu items
    207      *         from XML into the menu returned by {@link #getMenu()}
    208      * @see #getMenu()
    209      */
    210     public MenuInflater getMenuInflater() {
    211         return new MenuInflater(mContext);
    212     }
    213 
    214     /**
    215      * Inflate a menu resource into this PopupMenu. This is equivalent to
    216      * calling {@code popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu())}.
    217      *
    218      * @param menuRes Menu resource to inflate
    219      */
    220     public void inflate(@MenuRes int menuRes) {
    221         getMenuInflater().inflate(menuRes, mMenu);
    222     }
    223 
    224     /**
    225      * Show the menu popup anchored to the view specified during construction.
    226      *
    227      * @see #dismiss()
    228      */
    229     public void show() {
    230         mPopup.show();
    231     }
    232 
    233     /**
    234      * Dismiss the menu popup.
    235      *
    236      * @see #show()
    237      */
    238     public void dismiss() {
    239         mPopup.dismiss();
    240     }
    241 
    242     /**
    243      * Sets a listener that will be notified when the user selects an item from
    244      * the menu.
    245      *
    246      * @param listener the listener to notify
    247      */
    248     public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
    249         mMenuItemClickListener = listener;
    250     }
    251 
    252     /**
    253      * Sets a listener that will be notified when this menu is dismissed.
    254      *
    255      * @param listener the listener to notify
    256      */
    257     public void setOnDismissListener(OnDismissListener listener) {
    258         mOnDismissListener = listener;
    259     }
    260 
    261     /**
    262      * Sets whether the popup menu's adapter is forced to show icons in the
    263      * menu item views.
    264      * <p>
    265      * Changes take effect on the next call to show().
    266      *
    267      * @param forceShowIcon {@code true} to force icons to be shown, or
    268      *                  {@code false} for icons to be optionally shown
    269      */
    270     public void setForceShowIcon(boolean forceShowIcon) {
    271         mPopup.setForceShowIcon(forceShowIcon);
    272     }
    273 
    274     /**
    275      * Interface responsible for receiving menu item click events if the items
    276      * themselves do not have individual item click listeners.
    277      */
    278     public interface OnMenuItemClickListener {
    279         /**
    280          * This method will be invoked when a menu item is clicked if the item
    281          * itself did not already handle the event.
    282          *
    283          * @param item the menu item that was clicked
    284          * @return {@code true} if the event was handled, {@code false}
    285          *         otherwise
    286          */
    287         boolean onMenuItemClick(MenuItem item);
    288     }
    289 
    290     /**
    291      * Callback interface used to notify the application that the menu has closed.
    292      */
    293     public interface OnDismissListener {
    294         /**
    295          * Called when the associated menu has been dismissed.
    296          *
    297          * @param menu the popup menu that was dismissed
    298          */
    299         void onDismiss(PopupMenu menu);
    300     }
    301 
    302     /**
    303      * Returns the {@link ListView} representing the list of menu items in the currently showing
    304      * menu.
    305      *
    306      * @return The view representing the list of menu items.
    307      * @hide
    308      */
    309     @TestApi
    310     public ListView getMenuListView() {
    311         if (!mPopup.isShowing()) {
    312             return null;
    313         }
    314         return mPopup.getPopup().getListView();
    315     }
    316 }
    317