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