PopupWindow.showAtLocation的确切行为是什么?

3

我有一部安卓4.0手机,称为A,还有一台安卓4.4平板电脑,称为B,它们都有一个软件导航栏。 我使用以下方法:

showAtLocation(myView, Gravity.NO_GRAVITY, x, y);

在特定位置显示窗口。实际结果是,在A上看起来不错,但在B上有一个y偏移量。我发现偏移量似乎与B的导航栏高度相同。因此,我使用以下代码获取高度并进行减法:

private int getNavigationBarHeight(Resources res, Context context) {
        final int apiLevel = Build.VERSION.SDK_INT;
        if((apiLevel >= Build.VERSION_CODES.HONEYCOMB && apiLevel <= Build.VERSION_CODES.HONEYCOMB_MR2)
                ||
                (apiLevel >= Build.VERSION_CODES.ICE_CREAM_SANDWICH && !ViewConfiguration.get(context).hasPermanentMenuKey())
                ) {
            int resourceId = res.getIdentifier("navigation_bar_height", "dimen", "android");
            if (resourceId > 0) {
                return res.getDimensionPixelSize(resourceId);
            }
        }
        return 0;
    }

新的结果是: 在B中窗口现在正常了,但在A中显示时有一个y偏移。

问题是,我该如何使我的窗口在两个设备上都正常显示。
1个回答

1
今天我遇到了和你一样的问题。在我的模拟器上,由于PopupWindow的Gravity.NO_GRAVITY,PopupWindow在窗口范围内正确绘制。但是在我的Nexus 7平板电脑上,PopupWindow显示在设备底部显示的状态栏下方。
当我在屏幕上点击ImageButton时,我的PopupWindow会显示出来,此时它的y-position与ImageButton相同(而且宽度为match_parent)。ImageButton的位置可能在窗口范围内,也可能完全位于平板电脑底部的状态栏下方。
这是我想出来的方法:
我们有:
- 我们在PopupWindow的showAtLocation方法中指定的[x,y]位置(在这里我们只需要y-position,我将其命名为oldY
我们计算:
- Popup高度 - 状态栏高度 - 可能的最大高度以保持在窗口范围内(screenHeight - statusBarHeight - popupHeight
然后我们检查:
  • 我们检查oldY是否大于maxY
  • 如果是这样,newY将是maxY,然后我们重新绘制 PopupWindow。 如果不是这种情况,这意味着我们什么都不做,只使用oldY作为正确的Y位置。

注意1:我编写了此代码,但在调试期间发现我的模拟器和Nexus平板电脑上的状态栏高度均为0,因此仅使用screenHeight - popupHeight就足够了。 仍然,在我的配置文件中包含了计算底部状态栏高度的代码,并使用布尔值来启用/禁用此功能,以防将来在其他平板电脑上安装应用程序。

以下是代码,我只是添加了上面的描述,以便清楚地说明我用哪种方法解决了这个问题:

// Get the [x, y]-location of the ImageButton
int[] loc = new int[2];
myImageButton.getLocationOnScreen(loc);

// Inflate the popup.xml
LinearLayout viewGroup = (LinearLayout)findViewById(R.id.popup_layout);
LayoutInflater layoutInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View layout = layoutInflater.inflate(R.layout.popup, viewGroup);

// Create the PopupWindow
myPopupWindow = new PopupWindow(ChecklistActivity.this);
myPopupWindow.setContentView(layout);
myPopupWindow.setWindowLayoutMode(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);

... // Some more stuff with the PopupWindow's content

// Clear the default translucent background and use a white background instead
myPopupWindow.setBackgroundDrawable(new ColorDrawable(android.graphics.Color.WHITE));

// Displaying the Pop-up at the specified location
myPopupWindow.showAtLocation(layout, Gravity.NO_GRAVITY, 0, loc[1]);

// Because the PopupWindow is displayed below the Status Bar on some Device's,
// we recalculate it's height:
// Wait until the PopupWindow is done loading by using an OnGlobalLayoutListener:
final int[] finalLoc = loc;
if(layout.getViewTreeObserver().isAlive()){
    layout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        // This will be called once the layout is finished, prior to displaying it
        // So we can change the y-position of the PopupWindow just before that
        @Override
        public void onGlobalLayout() {
            // Get the PopupWindow's height
            int popupHeight = layout.getHeight();
            // Get the Status Bar's height
            int statusBarHeight = 0;
            // Enable/Disable this in the Config-file
            // This isn't needed for the Emulator, nor the Nexus 7 tablet
            // Since the calculated Status Bar Height is 0 with both of them
            // and the PopupWindow is displayed at its correct position
            if(D.WITH_STATUS_BAR_CHECK){
                // Check whether the Status bar is at the top or bottom
                Rect r = new Rect();
                Window w = ChecklistActivity.this.getWindow();
                w.getDecorView().getWindowVisibleDisplayFrame(r);
                int barHeightCheck = r.top;
                // If the barHeightCheck is 0, it means our Status Bar is
                // displayed at the bottom and we need to get it's height
                // (If the Status Bar is displayed at the top, we use 0 as Status Bar Height)
                if(barHeightCheck == 0){
                    int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
                    if (resourceId > 0)
                        statusBarHeight = getResources().getDimensionPixelSize(resourceId);
                }
            }
            // Get the Screen's height:
            DisplayMetrics dm = new DisplayMetrics();
            getWindowManager().getDefaultDisplay().getMetrics(dm);
            int screenHeight = dm.heightPixels;
            // Get the old Y-position
            int oldY = finalLoc[1];
            // Get the max Y-position to be within Window boundaries
            int maxY = screenHeight - statusBarHeight - popupHeight;
            // Check if the old Y-position is outside the Window boundary
            if(oldY > maxY){
                // If it is, use the max Y-position as new Y-position,
                // and re-draw the PopupWindow
                myPopupWindow.dismiss();
                myPopupWindow.showAtLocation(layout, Gravity.NO_GRAVITY, 0, maxY);
            }

            // Since we don't want onGlobalLayout to continue forever, we remove the Listener here again
            layout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
        }
    });
}

注意2:我已经在这一行中将tag_popup本身设置为width = match_parent; height = wrap_content

myPopupWindow.setWindowLayoutMode(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);

将此弹出窗口的主要布局设置为width = match_parent; height = match_parent

 <?xml version="1.0" encoding="utf-8"?>
 <!DOCTYPE xml>
 <!-- The DOCTYPE above is added to get rid of the following warning:
     "No grammar constraints (DTD or XML schema) detected for the document." -->

 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/popup_layout"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:background="@layout/tag_shape"
     android:padding="@dimen/default_margin">

     ... <!-- Popup's Content (EditTexts, Spinner, TextViews, Button, etc.) -->

 </RelativeLayout>

注意3:我的应用程序被强制保持为竖屏模式。我还没有在横屏模式下测试过这个问题,但我认为应该进行一些修改(不确定)。编辑:在我的两个设备上测试过了,在横屏模式下也可以工作。我不知道启用底部栏高度的情况下是否也适用于横屏模式。

希望这能帮助您和其他遇到类似问题的人。希望他们将来会修复PopupWindow的Gravity问题,让它永远不会出现在状态栏下面,除非程序员自己想要并更改PopupWindow的设置。


1
你的回答确实有帮助,但我最终在我的应用程序中找到了问题。问题在于我的PopupWindow中的内容视图具有边距(即平板电脑上的Y偏移量)到窗口顶部边缘。我使用View.getLocationOnScreen在绘制之前获取偏移量,然后一切都正常了。 - suitianshi
@suitianshi 哦,好的。很高兴我们都找到了自己问题的解决方案。 - Kevin Cruijssen

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