Android 仿微信長按列表彈出PopupMenu菜單欄

PopupMenu使用步驟

//這里的anchor是PopupMenu需要依附的view
PopupMenu popupMenu = new PopupMenu(context, anchor);
//填充PopupMenu內(nèi)容
popupMenu.getMenuInflater().inflate(R.menu.conversation_item_popupmenu, popupMenu.getMenu());
//PopupMenu點(diǎn)擊事件
popupMenu.setOnMenuItemClickListener(onMenuItemClickListener);
//PopupMenu消失事件
popupMenu.setOnDismissListener(onDismissListener);
//彈出PopupMenu荞驴,默認(rèn)gravity為Gravity.START
popupMenu.show();

彈出效果如下圖所示


Gravity.START

可以使用setGravity()方法來指定彈出窗口與anchor視圖的對齊方式溺健,例如修改對齊方式為Gravity.END


Gravity.END

使用起來還是比較簡單的像棘,但是好像大部分項(xiàng)目的需求是PopupMenu在用戶點(diǎn)擊的位置彈出安券,然而PopupMenu并沒有提供在指定坐標(biāo)彈出的方法,所以只能咱們自己來實(shí)現(xiàn)咯!

想讓PopupMenu在指定彈出位置,首先咱們得先了解show()方法是如何讓PopupMenu彈出來的庶溶,所以只能去閱讀源碼了(Read The Fucking Source Code~)。

    public void show() {
        mPopup.show();
    }

PopupMenu的show()方法很簡單懂鸵,直接把任務(wù)轉(zhuǎn)給MenuPopupHelper來處理偏螺,處理流程:show() -> tryShow() -> showPopup(0, 0, false, false);

    public void show() {
        if (!tryShow()) {
            throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
        }
    }
  //安全判斷,檢測是否有anchor view
    public boolean tryShow() {
        if (isShowing()) {
            return true;
        }
        if (mAnchorView == null) {
            return false;
        }
        //這就是我們要找的方法匆光,默認(rèn)坐標(biāo)是(0,0)
        showPopup(0, 0, false, false);
        return true;
    }

    //xOffset 相對于anchor視圖的x坐標(biāo)
    //yOffset 相對于anchor視圖的y坐標(biāo)
    private void showPopup(int xOffset, int yOffset, boolean useOffsets, boolean showTitle) {
        final MenuPopup popup = getPopup();
        popup.setShowTitle(showTitle);

        if (useOffsets) {
            // If the resolved drop-down gravity is RIGHT, the popup's right
            // edge will be aligned with the anchor view. Adjust by the anchor
            // width such that the top-right corner is at the X offset.
            final int hgrav = GravityCompat.getAbsoluteGravity(mDropDownGravity,
                    ViewCompat.getLayoutDirection(mAnchorView)) & Gravity.HORIZONTAL_GRAVITY_MASK;
            if (hgrav == Gravity.RIGHT) {
                xOffset -= mAnchorView.getWidth();
            }

            popup.setHorizontalOffset(xOffset);
            popup.setVerticalOffset(yOffset);

            // Set the transition epicenter to be roughly finger (or mouse
            // cursor) sized and centered around the offset position. This
            // will give the appearance that the window is emerging from
            // the touch point.
            final float density = mContext.getResources().getDisplayMetrics().density;
            final int halfSize = (int) (TOUCH_EPICENTER_SIZE_DP * density / 2);
            final Rect epicenter = new Rect(xOffset - halfSize, yOffset - halfSize,
                    xOffset + halfSize, yOffset + halfSize);
            popup.setEpicenterBounds(epicenter);
        }

        popup.show();
    }

我們可以看到showPopup方法內(nèi)有兩個(gè)參數(shù)int xOffset套像、int yOffset,根據(jù)注釋可以知道這就是相對于anchor視圖的坐標(biāo)值终息。所以如果要指定PopupMenu的彈出位置凉夯,MenuPopupHelper應(yīng)該這樣處理彈出邏輯:show(int x, int y) -> tryShow(int x, int y) -> showPopup(x, y, true, true)货葬。

但是由于PopupMenu無法調(diào)用到MenuPopupHelper的show(int x, int y) 方法,因此我們只能使用反射機(jī)制繞過PopupMenu劲够,直接調(diào)用MenuPopupHelper的show(int x, int y)方法。

到此為止休傍,已經(jīng)有了大致的解決思路征绎,接下來看看具體實(shí)現(xiàn)。

/**
 * 彈出菜單欄
 *
 * @param context
 * @param anchor                  觸發(fā)事件的View
 * @param menuRes                 菜單欄內(nèi)容
 * @param point                   點(diǎn)擊事件相對整個(gè)屏幕的坐標(biāo)
 * @param onMenuItemClickListener 菜單欄點(diǎn)擊事件
 * @param onDismissListener       菜單欄消失
 */
public static void showPopupMenu(Context context, View anchor, @MenuRes int menuRes, Point point,
                                 PopupMenu.OnMenuItemClickListener onMenuItemClickListener,
                                 PopupMenu.OnDismissListener onDismissListener) {
    //這里的anchor代表PopupMenu需要依附的view
    PopupMenu popupMenu = new PopupMenu(context, anchor);
    //填充PopupMenu內(nèi)容
    popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu());
    //PopupMenu點(diǎn)擊事件
    popupMenu.setOnMenuItemClickListener(onMenuItemClickListener);
    //PopupMenu消失事件
    popupMenu.setOnDismissListener(onDismissListener);
    //通過反射機(jī)制修改彈出位置磨取,在點(diǎn)擊的位置彈出PopupMenu
    try {
        //獲取PopupMenu類的成員變量MenuPopupHelper mPopup
        Field mPopup = popupMenu.getClass().getDeclaredField("mPopup");
        mPopup.setAccessible(true);
        Object o = mPopup.get(popupMenu);
        //MenuPopupHelper -> show(int x, int y)方法
        if (o instanceof MenuPopupHelper) {
            MenuPopupHelper menuPopupHelper = (MenuPopupHelper) o;
            int[] position = new int[2];
            //獲取anchor左上角在屏幕上的相對坐標(biāo)
            anchor.getLocationInWindow(position);
            //計(jì)算xOffset人柿、yOffset,相對anchor左下角位置為彈出位置
            int xOffset = (point.x - position[0]);
            int yOffset;
            //菜單高度
            int popupMenuHeight = DensityUtil.dp2px(context, 48 * popupMenu.getMenu().size());
            //如果菜單高度大于底部剩余空間忙厌,菜單就會向上彈出凫岖;否則向下彈出
            if (ScreenUtil.getScreenHeight(context) - point.y >= popupMenuHeight) {
                yOffset = (point.y - (position[1] + anchor.getHeight()));
            } else {
                yOffset = (point.y - (position[1]));
            }
            menuPopupHelper.show(xOffset, yOffset);
        }
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } finally {
        //出錯(cuò)時(shí)調(diào)用普通show方法。未出錯(cuò)時(shí)此方法也不會影響正常顯示
        popupMenu.show();
    }
}

最終彈出效果如下圖所示


指定位置彈出
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末逢净,一起剝皮案震驚了整個(gè)濱河市哥放,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌爹土,老刑警劉巖甥雕,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異胀茵,居然都是意外死亡社露,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門琼娘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來峭弟,“玉大人,你說我怎么就攤上這事脱拼÷魅常” “怎么了?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵挪拟,是天一觀的道長挨务。 經(jīng)常有香客問我,道長玉组,這世上最難降的妖魔是什么谎柄? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮惯雳,結(jié)果婚禮上朝巫,老公的妹妹穿的比我還像新娘。我一直安慰自己石景,他們只是感情好劈猿,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布拙吉。 她就那樣靜靜地躺著,像睡著了一般揪荣。 火紅的嫁衣襯著肌膚如雪筷黔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天仗颈,我揣著相機(jī)與錄音佛舱,去河邊找鬼。 笑死挨决,一個(gè)胖子當(dāng)著我的面吹牛请祖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播脖祈,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼肆捕,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了盖高?” 一聲冷哼從身側(cè)響起慎陵,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎或舞,沒想到半個(gè)月后荆姆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡映凳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年胆筒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诈豌。...
    茶點(diǎn)故事閱讀 38,724評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡仆救,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出矫渔,到底是詐尸還是另有隱情彤蔽,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布庙洼,位于F島的核電站顿痪,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏油够。R本人自食惡果不足惜蚁袭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望石咬。 院中可真熱鬧揩悄,春花似錦、人聲如沸鬼悠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蹬挺,卻和暖如春维贺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背汗侵。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工幸缕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人晰韵。 一個(gè)月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像熟妓,于是被迫代替她去往敵國和親雪猪。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容