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