如何获取ActionBar菜单项的当前位置?

21

我想在应用程序的 ActionBar 中的 MenuItem 下方显示一小段信息标签。

为了正确定位这个标签,我需要知道 MenuItem 的x位置。

目前谷歌没有给出有用的结果。

这是否可行?

4个回答

34

我发现另一种获取操作栏按钮钩子的方法,可能比已接受的解决方案更简单。您可以使用菜单项的ID简单地调用findViewById

现在难点是何时调用此功能?在onCreateonResume中,这太早了。一种解决方法是使用窗口的ViewTreeObserver

    final ViewTreeObserver viewTreeObserver = getWindow().getDecorView().getViewTreeObserver();
    viewTreeObserver.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            View menuButton = findViewById(R.id.menu_refresh);
            // This could be called when the button is not there yet, so we must test for null
            if (menuButton != null) {
                // Found it! Do what you need with the button
                int[] location = new int[2];
                menuButton.getLocationInWindow(location);
                Log.d(TAG, "x=" + location[0] + " y=" + location[1]);

                // Now you can get rid of this listener
                viewTreeObserver.removeGlobalOnLayoutListener(this);
            }
        }
    });

这个代码应该放在 onCreate 方法里。

注意:这是在使用 ActionBarSherlock 的项目上测试过的,所以在一个“真正”的 action bar 上可能无法工作。


8
我建议在移除监听器之前添加 if(viewTreeObserver.isAlive()) 检查,因为如果它不再活动,有时会抛出异常。 - M.Sameer
太棒了!感谢您提供这个出色的答案。这应该是正确的。 - Cilenco

14
我终于找到了答案!虽然有些取巧,但并不过分。唯一的缺点是:
  1. 你需要用自己的 View 替换 MenuItem。虽然一个 ImageView 和标准的 ImageView 很相似,但它缺少长按查看工具提示等功能,可能还有其他。

  2. 我没有进行大量测试。

好的,下面是具体实现方法。将你的 ActivityonCreateOptionsMenu() 更改为以下内容:

View mAddListingButton;

@Override
public boolean onCreateOptionsMenu(Menu menu)
{
    getMenuInflater().inflate(R.menu.activity_main, menu);
    
    // Find the menu item we are interested in.
    MenuItem it = menu.findItem(R.id.item_new_listing);
    
    // Create a new view to replace the standard one.
    // It would be nice if we could just *get* the standard one
    // but unfortunately it.getActionView() returns null.
    
    // actionButtonStyle makes the 'pressed' state work.
    ImageView button = new ImageView(this, null, android.R.attr.actionButtonStyle);
    button.setImageResource(R.drawable.ic_action_add_listing);
    // The onClick attributes in the menu resource no longer work (I assume since
    // we passed null as the attribute list when creating this button.
    button.setOnClickListener(this);
    
    // Ok, now listen for when layouting is finished and the button is in position.
    button.addOnLayoutChangeListener(new OnLayoutChangeListener() {
        @Override
        public void onLayoutChange(View v, int left, int top, int right, int bottom,
                int oldLeft, int oldTop, int oldRight, int oldBottom)
        {
            // Apparently this can be called before layout is finished, so ignore it.
            // Remember also that it might never be called with sane values, for example
            // if your action button is pushed into the "more" menu thing.
            if (left == 0 && top == 0 && right == 0 && bottom == 0)
                return;
            
            // This is the only way to get the absolute position on the screen.
            // The parameters passed to this function are relative to the containing View
            // and v.getX/Y() return 0.
            int[] location = new int[2];
            v.getLocationOnScreen(location);
            
            // Ok, now we know the centre location of the button in screen coordinates!
            informButtonLocation(location[0] + (right-left)/2, location[1] + (bottom-top)/2);
        }
    });
    
    it.setActionView(button);
    mAddListingButton = it.getActionView();
    return true;
}

然后你需要一个函数,用来更新帮助指南视图中按钮的位置并重新绘制它:
private void informButtonLocation(int cx, int cy)
{
    MyHelpView v = (MyHelpView)findViewById(R.id.instructions);
    v.setText("Click this button.");
            v.setTarget(cx, cy);
}

最后,制作一个花哨的自定义视图,向(接近)按钮绘制箭头。在onDraw方法中,您可以使用相同的getLocationOnScreen函数将屏幕坐标转换回Canvas坐标。代码示例如下:

@Override
public void onDraw(Canvas canvas)
{
    int[] location = new int[2];
    getLocationOnScreen(location);
    
    canvas.drawColor(Color.WHITE);

    mPaint.setColor(Color.BLACK);
    mPaint.setTextSize(40);
    mPaint.setAntiAlias(true);
    mPaint.setTextAlign(Align.CENTER);
    canvas.drawText(mText, getWidth()/2, getHeight()/2, mPaint);

    // Crappy line that isn't positioned right at all and has no arrowhead.
    if (mTargetX != -1 && mTargetY != -1)
        canvas.drawLine(getWidth()/2, getHeight()/2,
                mTargetX - location[0], mTargetY - location[1], mPaint);
}

(但显然,您必须将目标位置剪切到画布大小)。如果您采用与Google相同的方法,在整个屏幕上叠加视图,则不必担心这一点,但这更具侵入性。令人惊讶的是,Google的方法绝对不使用任何私有API。请查看Launcher2代码中的Cling.java。

顺便说一句,如果您查看Google为启动器实现类似功能的源代码(他们称说明屏幕为“Cling”,原因不详),则可以看到它甚至更加令人费解。基本上,他们使用由使用哪个布局确定的绝对屏幕位置。

希望这能帮助人们!


这真的很好用!虽然对我来说已经没有用处了(我已经使用了自己的解决方案,见下文),而且现在改变应用程序也不是一个选项。但我希望它能帮助未来的其他人!非常好的答案! - Pieter888
addOnLayoutChangeListener仅支持Honeycomb及以上版本,不支持旧的API。 - android developer
我现在有坐标了 - 但如何在片段上面绘制? - Skynet

12

将菜单项视为普通视图进行处理,就像这样:

    View myActionView = findViewById(R.id.my_menu_item_id);
    if (myActionView != null) {
        int[] location = new int[2];
        myActionView.getLocationOnScreen(location);

        int x = location[0];
        int y = location[1];
        Log.d(TAG, "menu item location --> " + x + "," + y);
    }

注意:我已经测试了使用ToolBar配置的Activity,而不是直接使用ActionBar...但我想它应该工作得相似。


注:我已经测试过使用ToolBar配置的Activity,而不是直接使用ActionBar...但我认为它应该类似工作。

你甚至可以在onOptionsItemSelected中调用它,它也能正常工作。 - Simon

0

此答案已过时,请查看上面的被采纳的答案

我发现(目前)无法确定菜单项的x/y值。请注意,菜单项可能会在用户按下菜单按钮之前隐藏在屏幕上。在Android 3.0及以上版本中,您可以选择在应用程序的ActionBar中实现菜单。您可以在操作栏中添加许多菜单项,当操作栏上没有足够的空间放置一个项目时,它将简单地创建一个“溢出”按钮,其中包含所有不能放置在操作栏上的菜单项,因为空间不足或程序员明确设置了菜单项出现在溢出菜单中。

所有这些行为可能就是为什么菜单项没有它们的x/y位置可以检索(或设置)的原因。

我最终是通过使用Paint.measureText()函数来确定每个菜单项在操作栏中的宽度,并将其用于计算标签应该位于屏幕右侧的偏移量来解决这个问题。

这是一个解决方法,但如果有人遇到和我一样的问题,请知道这个方法可以正常工作。只需记得在Paint对象上设置适当的字体大小,否则当您测量文本长度时,它很可能会使用与菜单项字体大小不同的字体大小计算文本宽度。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接