MenuPopupHelper.java revision ca51e8788a58f2af3525b7214a675f2d0233e5da
14267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell/*
24267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell * Copyright (C) 2010 The Android Open Source Project
34267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell *
44267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell * Licensed under the Apache License, Version 2.0 (the "License");
54267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell * you may not use this file except in compliance with the License.
64267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell * You may obtain a copy of the License at
74267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell *
84267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell *      http://www.apache.org/licenses/LICENSE-2.0
94267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell *
104267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell * Unless required by applicable law or agreed to in writing, software
114267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell * distributed under the License is distributed on an "AS IS" BASIS,
124267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
134267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell * See the License for the specific language governing permissions and
144267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell * limitations under the License.
154267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell */
164267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell
174267534d1c42af847ed0cefd1c88c99f66b36571Adam Powellpackage com.android.internal.view.menu;
184267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell
194267534d1c42af847ed0cefd1c88c99f66b36571Adam Powellimport com.android.internal.view.menu.MenuBuilder.MenuAdapter;
204267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell
214267534d1c42af847ed0cefd1c88c99f66b36571Adam Powellimport android.content.Context;
22e2b03a62e5014ea60e24a989544fa549b493a520Adam Powellimport android.os.Handler;
234267534d1c42af847ed0cefd1c88c99f66b36571Adam Powellimport android.util.DisplayMetrics;
248028dd32a4a04154050220dd0693583d5b750330Adam Powellimport android.view.KeyEvent;
258028dd32a4a04154050220dd0693583d5b750330Adam Powellimport android.view.MenuItem;
264267534d1c42af847ed0cefd1c88c99f66b36571Adam Powellimport android.view.View;
274267534d1c42af847ed0cefd1c88c99f66b36571Adam Powellimport android.view.View.MeasureSpec;
280458796f1401732b38660794148f4c5e5602f432Adam Powellimport android.view.ViewTreeObserver;
294267534d1c42af847ed0cefd1c88c99f66b36571Adam Powellimport android.widget.AdapterView;
304267534d1c42af847ed0cefd1c88c99f66b36571Adam Powellimport android.widget.ListPopupWindow;
316c6f575423d6718c3ff322224c1520901ce881e1Adam Powellimport android.widget.PopupWindow;
324267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell
338028dd32a4a04154050220dd0693583d5b750330Adam Powellimport java.lang.ref.WeakReference;
348028dd32a4a04154050220dd0693583d5b750330Adam Powell
354267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell/**
364267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell * @hide
374267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell */
380458796f1401732b38660794148f4c5e5602f432Adam Powellpublic class MenuPopupHelper implements AdapterView.OnItemClickListener, View.OnKeyListener,
398515ee846bd76aee86ec5ddfcc4dd1e626dd999cAdam Powell        ViewTreeObserver.OnGlobalLayoutListener, PopupWindow.OnDismissListener {
404267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell    private static final String TAG = "MenuPopupHelper";
414267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell
424267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell    private Context mContext;
434267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell    private ListPopupWindow mPopup;
448028dd32a4a04154050220dd0693583d5b750330Adam Powell    private MenuBuilder mMenu;
454267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell    private int mPopupMaxWidth;
468028dd32a4a04154050220dd0693583d5b750330Adam Powell    private WeakReference<View> mAnchorView;
478028dd32a4a04154050220dd0693583d5b750330Adam Powell    private boolean mOverflowOnly;
480458796f1401732b38660794148f4c5e5602f432Adam Powell    private ViewTreeObserver mTreeObserver;
498028dd32a4a04154050220dd0693583d5b750330Adam Powell
50e2b03a62e5014ea60e24a989544fa549b493a520Adam Powell    private final Handler mHandler = new Handler();
51e2b03a62e5014ea60e24a989544fa549b493a520Adam Powell
528028dd32a4a04154050220dd0693583d5b750330Adam Powell    public MenuPopupHelper(Context context, MenuBuilder menu) {
538028dd32a4a04154050220dd0693583d5b750330Adam Powell        this(context, menu, null, false);
548028dd32a4a04154050220dd0693583d5b750330Adam Powell    }
554267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell
568028dd32a4a04154050220dd0693583d5b750330Adam Powell    public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView) {
578028dd32a4a04154050220dd0693583d5b750330Adam Powell        this(context, menu, anchorView, false);
588028dd32a4a04154050220dd0693583d5b750330Adam Powell    }
598028dd32a4a04154050220dd0693583d5b750330Adam Powell
608028dd32a4a04154050220dd0693583d5b750330Adam Powell    public MenuPopupHelper(Context context, MenuBuilder menu,
618028dd32a4a04154050220dd0693583d5b750330Adam Powell            View anchorView, boolean overflowOnly) {
624267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell        mContext = context;
638028dd32a4a04154050220dd0693583d5b750330Adam Powell        mMenu = menu;
648028dd32a4a04154050220dd0693583d5b750330Adam Powell        mOverflowOnly = overflowOnly;
654267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell
664267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell        final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
674267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell        mPopupMaxWidth = metrics.widthPixels / 2;
688028dd32a4a04154050220dd0693583d5b750330Adam Powell
698028dd32a4a04154050220dd0693583d5b750330Adam Powell        if (anchorView != null) {
708028dd32a4a04154050220dd0693583d5b750330Adam Powell            mAnchorView = new WeakReference<View>(anchorView);
718028dd32a4a04154050220dd0693583d5b750330Adam Powell        }
724267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell    }
734267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell
74f0ad6e6eaf48ac8f4007232ad0a8511a7b5cfc0eAdam Powell    public void setAnchorView(View anchor) {
75f0ad6e6eaf48ac8f4007232ad0a8511a7b5cfc0eAdam Powell        mAnchorView = new WeakReference<View>(anchor);
76f0ad6e6eaf48ac8f4007232ad0a8511a7b5cfc0eAdam Powell    }
77f0ad6e6eaf48ac8f4007232ad0a8511a7b5cfc0eAdam Powell
784267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell    public void show() {
795e3f284baa271cb0fbf90e504d19fdd2e385382eAdam Powell        if (!tryShow()) {
805e3f284baa271cb0fbf90e504d19fdd2e385382eAdam Powell            throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
815e3f284baa271cb0fbf90e504d19fdd2e385382eAdam Powell        }
825e3f284baa271cb0fbf90e504d19fdd2e385382eAdam Powell    }
835e3f284baa271cb0fbf90e504d19fdd2e385382eAdam Powell
845e3f284baa271cb0fbf90e504d19fdd2e385382eAdam Powell    public boolean tryShow() {
850b2d306e7000f4c0c6ad4e00d494bb401d8a9fc2Adam Powell        mPopup = new ListPopupWindow(mContext, null, com.android.internal.R.attr.popupMenuStyle);
864267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell        mPopup.setOnItemClickListener(this);
878515ee846bd76aee86ec5ddfcc4dd1e626dd999cAdam Powell        mPopup.setOnDismissListener(this);
884267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell
898028dd32a4a04154050220dd0693583d5b750330Adam Powell        final MenuAdapter adapter = mOverflowOnly ?
908028dd32a4a04154050220dd0693583d5b750330Adam Powell                mMenu.getOverflowMenuAdapter(MenuBuilder.TYPE_POPUP) :
918028dd32a4a04154050220dd0693583d5b750330Adam Powell                mMenu.getMenuAdapter(MenuBuilder.TYPE_POPUP);
924267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell        mPopup.setAdapter(adapter);
934267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell        mPopup.setModal(true);
944267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell
950458796f1401732b38660794148f4c5e5602f432Adam Powell        View anchor = mAnchorView != null ? mAnchorView.get() : null;
960458796f1401732b38660794148f4c5e5602f432Adam Powell        if (anchor == null && mMenu instanceof SubMenuBuilder) {
978028dd32a4a04154050220dd0693583d5b750330Adam Powell            SubMenuBuilder subMenu = (SubMenuBuilder) mMenu;
988028dd32a4a04154050220dd0693583d5b750330Adam Powell            final MenuItemImpl itemImpl = (MenuItemImpl) subMenu.getItem();
990458796f1401732b38660794148f4c5e5602f432Adam Powell            anchor = itemImpl.getItemView(MenuBuilder.TYPE_ACTION_BUTTON, null);
1000458796f1401732b38660794148f4c5e5602f432Adam Powell            mAnchorView = new WeakReference<View>(anchor);
1010458796f1401732b38660794148f4c5e5602f432Adam Powell        }
1020458796f1401732b38660794148f4c5e5602f432Adam Powell
1030458796f1401732b38660794148f4c5e5602f432Adam Powell        if (anchor != null) {
104ca51e8788a58f2af3525b7214a675f2d0233e5daAdam Powell            if (mTreeObserver == null) {
105ca51e8788a58f2af3525b7214a675f2d0233e5daAdam Powell                mTreeObserver = anchor.getViewTreeObserver();
106ca51e8788a58f2af3525b7214a675f2d0233e5daAdam Powell                mTreeObserver.addOnGlobalLayoutListener(this);
107ca51e8788a58f2af3525b7214a675f2d0233e5daAdam Powell            }
1080458796f1401732b38660794148f4c5e5602f432Adam Powell            mPopup.setAnchorView(anchor);
1094be0d52125b88dc992a4c500edbe95bf55484e0bAdam Powell        } else {
1105e3f284baa271cb0fbf90e504d19fdd2e385382eAdam Powell            return false;
1118028dd32a4a04154050220dd0693583d5b750330Adam Powell        }
1124267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell
1134267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell        mPopup.setContentWidth(Math.min(measureContentWidth(adapter), mPopupMaxWidth));
114aa0b92ce2b51987e9c864164234fe968ab5b9311Adam Powell        mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
1154267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell        mPopup.show();
1168028dd32a4a04154050220dd0693583d5b750330Adam Powell        mPopup.getListView().setOnKeyListener(this);
1175e3f284baa271cb0fbf90e504d19fdd2e385382eAdam Powell        return true;
1184267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell    }
1194267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell
1204267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell    public void dismiss() {
1213d3da27ab394108fd51771616cca3279baae99d1Adam Powell        if (isShowing()) {
1223d3da27ab394108fd51771616cca3279baae99d1Adam Powell            mPopup.dismiss();
1233d3da27ab394108fd51771616cca3279baae99d1Adam Powell        }
1248515ee846bd76aee86ec5ddfcc4dd1e626dd999cAdam Powell    }
1258515ee846bd76aee86ec5ddfcc4dd1e626dd999cAdam Powell
1268515ee846bd76aee86ec5ddfcc4dd1e626dd999cAdam Powell    public void onDismiss() {
1278515ee846bd76aee86ec5ddfcc4dd1e626dd999cAdam Powell        mPopup = null;
128ca51e8788a58f2af3525b7214a675f2d0233e5daAdam Powell        if (mTreeObserver != null && mTreeObserver.isAlive()) {
129ca51e8788a58f2af3525b7214a675f2d0233e5daAdam Powell            mTreeObserver.removeGlobalOnLayoutListener(this);
130ed8b403cc8066bf76cdf98f8d9906ff810defc5bAdam Powell        }
131ca51e8788a58f2af3525b7214a675f2d0233e5daAdam Powell        mTreeObserver = null;
1324267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell    }
1334267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell
1348028dd32a4a04154050220dd0693583d5b750330Adam Powell    public boolean isShowing() {
1358028dd32a4a04154050220dd0693583d5b750330Adam Powell        return mPopup != null && mPopup.isShowing();
1368028dd32a4a04154050220dd0693583d5b750330Adam Powell    }
1378028dd32a4a04154050220dd0693583d5b750330Adam Powell
1384267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
139ca51e8788a58f2af3525b7214a675f2d0233e5daAdam Powell        if (!isShowing()) return;
140ca51e8788a58f2af3525b7214a675f2d0233e5daAdam Powell
1418028dd32a4a04154050220dd0693583d5b750330Adam Powell        MenuItem item = null;
1428028dd32a4a04154050220dd0693583d5b750330Adam Powell        if (mOverflowOnly) {
1438028dd32a4a04154050220dd0693583d5b750330Adam Powell            item = mMenu.getOverflowItem(position);
1448028dd32a4a04154050220dd0693583d5b750330Adam Powell        } else {
1451700ae0aef4e6a3f412b758389955abd049060dbAdam Powell            item = mMenu.getVisibleItems().get(position);
1468028dd32a4a04154050220dd0693583d5b750330Adam Powell        }
1470458796f1401732b38660794148f4c5e5602f432Adam Powell        dismiss();
148e2b03a62e5014ea60e24a989544fa549b493a520Adam Powell
149e2b03a62e5014ea60e24a989544fa549b493a520Adam Powell        final MenuItem performItem = item;
150e2b03a62e5014ea60e24a989544fa549b493a520Adam Powell        mHandler.post(new Runnable() {
151e2b03a62e5014ea60e24a989544fa549b493a520Adam Powell            public void run() {
152e2b03a62e5014ea60e24a989544fa549b493a520Adam Powell                mMenu.performItemAction(performItem, 0);
153e2b03a62e5014ea60e24a989544fa549b493a520Adam Powell            }
154e2b03a62e5014ea60e24a989544fa549b493a520Adam Powell        });
1554267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell    }
1564267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell
1578028dd32a4a04154050220dd0693583d5b750330Adam Powell    public boolean onKey(View v, int keyCode, KeyEvent event) {
1588028dd32a4a04154050220dd0693583d5b750330Adam Powell        if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) {
1598028dd32a4a04154050220dd0693583d5b750330Adam Powell            dismiss();
1608028dd32a4a04154050220dd0693583d5b750330Adam Powell            return true;
1618028dd32a4a04154050220dd0693583d5b750330Adam Powell        }
1628028dd32a4a04154050220dd0693583d5b750330Adam Powell        return false;
1638028dd32a4a04154050220dd0693583d5b750330Adam Powell    }
1648028dd32a4a04154050220dd0693583d5b750330Adam Powell
1654267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell    private int measureContentWidth(MenuAdapter adapter) {
1664267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell        // Menus don't tend to be long, so this is more sane than it looks.
1674267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell        int width = 0;
1684267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell        View itemView = null;
16950f784cf2dc2dea8061153ac3a843f60a9d88781Adam Powell        int itemType = 0;
1704267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell        final int widthMeasureSpec =
1714267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell            MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1724267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell        final int heightMeasureSpec =
1734267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell            MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1744267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell        final int count = adapter.getCount();
1754267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell        for (int i = 0; i < count; i++) {
17650f784cf2dc2dea8061153ac3a843f60a9d88781Adam Powell            final int positionType = adapter.getItemViewType(i);
17750f784cf2dc2dea8061153ac3a843f60a9d88781Adam Powell            if (positionType != itemType) {
17850f784cf2dc2dea8061153ac3a843f60a9d88781Adam Powell                itemType = positionType;
17950f784cf2dc2dea8061153ac3a843f60a9d88781Adam Powell                itemView = null;
18050f784cf2dc2dea8061153ac3a843f60a9d88781Adam Powell            }
1814267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell            itemView = adapter.getView(i, itemView, null);
1824267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell            itemView.measure(widthMeasureSpec, heightMeasureSpec);
1834267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell            width = Math.max(width, itemView.getMeasuredWidth());
1844267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell        }
1854267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell        return width;
1864267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell    }
1870458796f1401732b38660794148f4c5e5602f432Adam Powell
1880458796f1401732b38660794148f4c5e5602f432Adam Powell    @Override
1890458796f1401732b38660794148f4c5e5602f432Adam Powell    public void onGlobalLayout() {
1900458796f1401732b38660794148f4c5e5602f432Adam Powell        if (!isShowing()) {
191ca51e8788a58f2af3525b7214a675f2d0233e5daAdam Powell            if (mTreeObserver.isAlive()) {
192ca51e8788a58f2af3525b7214a675f2d0233e5daAdam Powell                mTreeObserver.removeGlobalOnLayoutListener(this);
193ca51e8788a58f2af3525b7214a675f2d0233e5daAdam Powell            }
1940458796f1401732b38660794148f4c5e5602f432Adam Powell            mTreeObserver = null;
1950458796f1401732b38660794148f4c5e5602f432Adam Powell        } else {
1960458796f1401732b38660794148f4c5e5602f432Adam Powell            final View anchor = mAnchorView != null ? mAnchorView.get() : null;
197ca51e8788a58f2af3525b7214a675f2d0233e5daAdam Powell            if (anchor == null || !anchor.isShown()) {
1980458796f1401732b38660794148f4c5e5602f432Adam Powell                dismiss();
199ca51e8788a58f2af3525b7214a675f2d0233e5daAdam Powell            } else if (isShowing()) {
200aa0b92ce2b51987e9c864164234fe968ab5b9311Adam Powell                // Recompute window size and position
201aa0b92ce2b51987e9c864164234fe968ab5b9311Adam Powell                mPopup.show();
2020458796f1401732b38660794148f4c5e5602f432Adam Powell            }
2030458796f1401732b38660794148f4c5e5602f432Adam Powell        }
2040458796f1401732b38660794148f4c5e5602f432Adam Powell    }
2054267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell}
206