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