当PopupWindow大小未指定时,弹出窗口超出屏幕

13

大多数示例都明确指定了弹出窗口的宽度和高度。但是我希望它们是WRAP_CONTENT - 因为内容是动态确定的,所以在构造函数中设置宽度和高度均为-2,并通过showAsDropDown(View anchor)显示。

这样做,弹出窗口总是在锚点视图下方绘制,这意味着它可能会绘制到屏幕外面。以下代码片段演示了问题。尝试点击最后一个TextView,你将看不到任何PopupWindow,因为它显示在窗口范围之外。为什么不起作用?我注意到明确指定尺寸(例如200,100)不会触发该问题。自己试试吧。

package com.zybnet.example.popupdemo;

import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.TextView;

public class PopupDemoActivity extends Activity implements OnClickListener {
    private PopupWindow popup;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // -2 means WRAP_CONTENT THIS TRIGGERS THE PROBLEM
        popup = new PopupWindow(getPopupContent(), -2, -2);
        // When you specify the dimensions everything goes fine
        //popup = new PopupWindow(getPopupContent(), 200, 100);

        LinearLayout layout = new LinearLayout(this);
        layout.setOrientation(LinearLayout.VERTICAL);

        // FILL_PARENT  and same layout weight for all children
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(-1, -1, 1);
        for (int i = 0; i < 10; i++) {
            TextView tv = new TextView(this);
            tv.setText("Click to show popup");
            tv.setOnClickListener(this);
            layout.addView(tv, params);
        }
        setContentView(layout);
    }

    @Override
    public void onClick(View view) {
        popup.dismiss();
        popup.showAsDropDown(view);
    }

    private View getPopupContent() {
        TextView popupContent = new TextView(this);
        popupContent.setText("Some text here");
        popupContent.setTextColor(Color.parseColor("#5000ae"));
        popupContent.setBackgroundColor(Color.parseColor("#ff00ff"));
        popupContent.setPadding(10, 20, 20, 10);
        return popupContent;
    }
}
3个回答

17

我开始怀疑在PopupWindow的上下文中,-2、-2实际上意味着WRAP_CONTENT,而不是将其解释为宽度=-2,高度=-2。

这是来自Android源代码的内容。

public PopupWindow(View contentView, int width, int height, boolean focusable) {
    if (contentView != null) {
        mContext = contentView.getContext();
        mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
    }
    setContentView(contentView);
    setWidth(width);
    setHeight(height);
    setFocusable(focusable);
}

其中setWidth(int width)只是一个简单的mWidth = width;,我认为你应该使用setWindowLayoutMode(int widthSpec, int heightSpec)方法。这是你应该传递WRAP_CONTENT的地方。

如果所有其他方法都失败了,请测量TextView的宽度和高度,然后像预期的那样使用setWidthsetHeight

编辑

我刚刚尝试了一下,并添加了这行代码以使其工作

    // -2 means WRAP_CONTENT THIS TRIGGERS THE PROBLEM
    popup = new PopupWindow(getPopupContent(), 200, 100);
    popup.setWindowLayoutMode(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    // When you specify the dimensions everything goes fine
    //popup = new PopupWindow(getPopupContent(), 200, 100);

    LinearLayout layout = new LinearLayout(this);
    layout.setOrientation(LinearLayout.VERTICAL);

我在这里保留了原始评论,以便您自己看到所有内容应放置的位置。

编辑2: 好的,我拿了你发布的代码并一字不改地在我的设备上尝试了一下,你是对的,它没有起作用,因为当Android确定要布局放置在哪些视图中时,它依赖于您提供的宽度和高度。这意味着,由于您将0和0作为宽度和高度,Android会来说:“哦,看,这个框只有0 x 0,所以我可以将其显示为内容下面的弹出窗口。”但这不是想要的!问题是,你知道这个框会比那个大,所以让我们开始输入一些不同的数字,看看结果。

popup = new PopupWindow(getPopupContent(), 1, 1);

现在,当我点击底部的框时,请注意它会跳到上面。(见截图)这是因为Android知道宽度和高度都是1(如我在构造函数中设置的),并且列表项下方可用的屏幕空间为0。如果那里没有足够的空间来显示它,那么它必须出现在上面!

enter image description here

但是,等一下!如果像我的当前示例一样,字符串每次都添加更多内容怎么办?嗯,这就是事情变得有趣的地方。你会看到在我接下来的截图中,即使应该向上显示弹出窗口,因为现在它有6行长度,但实际上是显示在底部的!哦,该死!那是因为它是根据我在构造函数中使用的1 1进行测量的。那么,有什么解决方案呢?

enter image description here

解决方案一:我倾向于猜测TextView的平均最大高度,然后简单地投入一个普遍较大的数字。
popup = new PopupWindow(getPopupContent(), 300, 300); //just guessing it won't get bigger than that

方案二:这种方法更加适当,但是你需要牺牲一点速度。使用Paint类来测量文本内容的大小,并在显示弹出窗口之前将其传递到setWidth()setHeight()中。我已经构建了一个几乎完整的解决方案,但我没有考虑填充和其他东西(请参见注释)。

private int maxWidth;
private int maxHeight;
private Paint p = new Paint();
private Rect bounds = new Rect();
private View getPopupContent() {
    maxWidth = 0;
    maxHeight = 0;
    TextView popupContent = new TextView(this);
    popupContent.setText(popupText += "\n" + DEMO);
    popupContent.setTextColor(Color.parseColor("#5000ae"));
    popupContent.setBackgroundColor(Color.parseColor("#ff00ff"));
    popupContent.setPadding(10, 20, 20, 10);
    //the measure can only work line by line so I split it up by line
    String[] temp = popupText.split("\n");
    for (String s : temp){
        //measure each line of string and get its width and height
        p.getTextBounds(s, 0, s.length(), bounds);

        //keep a running total of the longest width
        maxWidth = (bounds.width() > maxWidth) ? bounds.width() : maxWidth;
        //add up each of the heights
        maxHeight += bounds.height();
    }

    //also take in account the padding too... if you REALLY want it to be completely robust
    //probably adding another 20 or 30 to the maxHeight should be good
    return popupContent;
}

然后在 onClick() 中,我添加了这两行代码

    popup.setHeight(maxHeight);
    popup.setWidth(maxWidth);

我也阅读了源代码,看起来更像是Android的一个bug,因为大小是正确的,但位置却不对。你可以自己测试一下,因为我提供了一个演示。现在我正在尝试使用PopupWindow.showAtLocation()。 - Raffaele
如果下一次调用弹出窗口时内容视图发生变化会发生什么?这就是为什么我需要WRAP_CONTENT的原因。 - Raffaele
请再次阅读我的回答。我确实使用了 WRAP_CONTENT,并且它能够正常的包裹内容。我在我的安卓设备上运行这段代码,它能够正常工作,请你尝试一下以验证。 - Bob
我刚刚重新编辑了它,更具体地解决了你隐藏的问题。 - Bob
感谢您的热情 :) 即使我很久以前就通过使用showAtLocation()和手动计算解决了这个问题。如果我理解您的答案,测量是一个问题,通常我会在TextView上使用MeasureSpec.UNSPECIFIED的measure()方法,并使用getMeasuredWidth()来检索它。 - Raffaele
显示剩余2条评论

12

我通过在弹出内容视图中调用measure()方法来解决这个问题:

View content = getPopupContent();

content.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
int measuredHeight = content.getMeasuredHeight();

popup = new PopupWindow(content, ViewGroup.LayoutParams.WRAP_CONTENT, measuredHeight);

0
final PopupWindow popupWindow = new PopupWindow();
// inflate your layout or dynamically add view
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.alert_reply_chat,null);

popupWindow.setFocusable(true);
popupWindow.setWidth(WindowManager.LayoutParams.WRAP_CONTENT);
popupWindow.setHeight(180);
popupWindow.setBackgroundDrawable(null);

popupWindow.setClippingEnabled(false);

popupWindow.setTouchable(true);
popupWindow.setContentView(view);

return popupWindow;

//以上是我检查过的可用代码


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