ListPopupWindow不遵守WRAP_CONTENT宽度规范

43

我试图使用ListPopupWindow通过ArrayAdapter(最终将使用更复杂的定制适配器)显示字符串列表。以下是代码。如截图所示,结果的ListPopupWindow似乎表现得好像内容宽度为零。它显示了正确数量的项目,这些项目仍然可点击,并且点击成功产生Toast,因此至少这部分工作正常。

有趣的是:我可以向popup.setWidth(...)提供像素宽度,而不是ListPopupWindow.WRAP_CONTENT,它会显示一些内容,但这似乎非常死板。

我该如何使ListPopupWindow自动适应其内容?

测试活动:

public class MainActivity extends Activity {

    private static final String[] STRINGS = {"Option1","Option2","Option3","Option4"};
    private View anchorView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getActionBar().setHomeButtonEnabled(true);
        setContentView(R.layout.activity_main);
        anchorView = findViewById(android.R.id.home);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                showPopup();
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

    private void showPopup() {
        ListPopupWindow popup = new ListPopupWindow(this);
        popup.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, STRINGS));
        popup.setAnchorView(anchorView);
        popup.setWidth(ListPopupWindow.WRAP_CONTENT);
        popup.setOnItemClickListener(new OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Toast.makeText(MainActivity.this, "Clicked item " + position, Toast.LENGTH_SHORT).show();
            }
        });
        popup.show();
    }
}

截图:

在这里输入图片描述


2
似乎使用WRAP_CONTENT会导致弹出窗口的宽度等于锚视图的宽度,而在此示例中,锚视图是ActionBar上的主页图标。我可能最终需要使用显式宽度值... - Karakuri
你能否将setContentWidth设置为WRAP_CONTENT而不是setWidth?我现在无法测试,所以我暂时不会将其作为答案。 - Mike
1
@Mike:不幸的是,那并没有帮助。我检查了源代码,并且setContentWidth()将调用转发给setWidth()。唯一的区别是它考虑了背景可绘制物的一些填充。 - Karakuri
我是这样做的,它可以工作。我正在适配器中使用它。View view = ((AppCompatActivity) context).findViewById(R.id.sortItems); sortItems 是 Toolbar(上下文操作栏)上的菜单视图。listPopupWindow.setWidth(view.getWidth() * 6); listPopupWindow.show(); 肯定会起作用。 - EngineSense
9个回答

66

您可以测量适配器内容的宽度:

private int measureContentWidth(ListAdapter listAdapter) {
    ViewGroup mMeasureParent = null;
    int maxWidth = 0;
    View itemView = null;
    int itemType = 0;

    final ListAdapter adapter = listAdapter;
    final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
    final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
    final int count = adapter.getCount();
    for (int i = 0; i < count; i++) {
        final int positionType = adapter.getItemViewType(i);
        if (positionType != itemType) {
            itemType = positionType;
            itemView = null;
        }

        if (mMeasureParent == null) {
            mMeasureParent = new FrameLayout(mContext);
        }

        itemView = adapter.getView(i, itemView, mMeasureParent);
        itemView.measure(widthMeasureSpec, heightMeasureSpec);

        final int itemWidth = itemView.getMeasuredWidth();

        if (itemWidth > maxWidth) {
            maxWidth = itemWidth;
        }
    }

    return maxWidth;
}

并且在你的showPopup()函数中:

 ArrayAdapter arrayAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, STRINGS);
    popup.setAdapter(arrayAdapter);
    popup.setAnchorView(anchorView);
    popup.setContentWidth(measureContentWidth(arrayAdapter));

很好。我只是添加了mMeasureParent的初始化并点赞了。 - janzoner
1
在只有一个TextView的布局上出现了崩溃。不得不添加以下代码:if (itemView != null && itemView.getLayoutParams() == null) { itemView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); } - YetAnotherUser
1
我认为这个解决方案不起作用,我得到了错误的宽度并且内容看起来被切割了...很奇怪。 - Romina Liuzzi

32
问题出在ListPopupWindow的实现上。我查看了源代码,使用 setContentWidth(ListPopupWindow.WRAP_CONTENT) 或者 setWidth(ListPopupWindow.WRAP_CONTENT) 会导致弹出窗口使用其锚点视图的宽度。

ArrayAdapter 中的不同布局没有起作用吗?我觉得应该有一些方法可以纠正这个问题。 - Mike
4
嗯,这似乎是一个常见的使用情况,这很不幸 :( - Mike
@Karakuri 你有没有找到一个好的解决方法?看起来它仍然没有被修复,这让人非常沮丧。 - ndsc
1
你误解了。没有办法让ListPopupWindow遵守WRAP_CONTENT的宽度规范。我是通过解释源代码在使用WRAP_CONTENT时将ListPopupWindow的宽度设置为锚视图的宽度来指出这一事实。 - Karakuri
我使用了 PopupMenu。它可以正常工作,但只能与 XML 一起使用,并且需要 API 11+。请参考此答案 https://dev59.com/cnLYa4cB1Zd3GeqPVUK3#23550266。 - Sufian

7
我认为最好使用PopupMenu。例如:
final PopupMenu popupMenu = new PopupMenu(mActivity, v);
popupMenu.getMenu().add("test");
popupMenu.setOnMenuItemClickListener(new OnMenuItemClickListener() {

    @Override
    public boolean onMenuItemClick(final MenuItem item) {
        return true;
    }
});
popupMenu.show();

1
是的,但只有在您不需要菜单项的自定义内容时才可以。 - Denys Kniazhev-Support Ukraine
你什么意思?所有菜单项都是定制的 - 你总是选择要设置哪个字符串... - android developer
@android_developer 除非你想要它成为自定义视图。 - Denys Kniazhev-Support Ukraine
@DenisKniazhev 哦,你的意思是我误解了。你的意思是你希望一个完全定制的弹出窗口?但是在这里,他们想要一个项目列表,所以应该足够了。 - android developer

5

我建议在你的dimen.xml文件中设置一个大约为160dp的尺寸:

<dimen name="overflow_width">160dp</dimen>

然后使用getDimensionPixelSize方法将弹出窗口的宽度设置为像素:

int width = mContext.getResources().getDimensionPixelSize(R.dimen.overflow_width); 
mListPopupWindow.setWidth(width);

这将使大小与密度无关。


3
我相信你的问题在于使用了简单列表项1(simple_list_item_1)的ArrayAdapter。如果你查看该元素的源代码,它具有以下属性:
android:layout_width="match_parent"

如果你查看这里的源代码,你可以创建一个新的new_list_item_1.xml,并将其与simple_list_item_1.xml相同的内容变为android:layout_width="wrap_content",然后在你的ArrayAdapter中使用它。

1
它对我没用。你确定你没有漏掉什么吗? - android developer

3

您实际上可以获取anchorView的父级(因为实际的anchorView通常是一个按钮),并以此为基础确定宽度。例如:

popup.setWidth(((View)anchor.getParent()).getWidth()/2);

那样你就可以获得灵活的宽度。

1
这并没有帮助。问题是我想尊重弹出窗口内容的大小,这正是 WRAP_CONTENT 宽度规范应该做到的,甚至根据 Google 的文档也是如此。这与锚视图或其父视图的宽度毫无关系。 - Karakuri
@Karakuri 实际上,谷歌很久以前就在一场名为“listView世界”的讲座中讨论过这个问题。他们说你不能使用wrap_content,因为你不能真的指望它检查所有的项(可能有大量的项)。高度和宽度也是如此。他们还说,如果你将其用于高度,他们会做一些奇怪的事情(我不记得是什么了,可能是只取3个项目之类的) 。 - android developer
@androiddeveloper 是的,他们确实谈到了这个问题。但是 ListPopupWindow 是在 API 11 中引入的,远在他们知道 ListView 问题之后。我指的是 ListPopupWindow.WRAP_CONTENT(而不是 ViewGroup.LayoutParams.WRAP_CONTENT)的文档,其中写道:“如果用于指定弹出窗口的宽度,则弹出窗口将使用其内容的宽度。” 如果 ListView 从未遵守此值,则将其包含为公共 API 并附带该文档是错误和误导的。 - Karakuri
@Karakuri 嗯,因为内容是 listView,并且它的宽度不像其他视图一样由其内容确定,所以你需要自行设置... - android developer

1
另一种解决方案是在您的布局xml中设置一个0dp高度的视图作为锚点。
<View
    android:id="@+id/popup_anchor"
    android:layout_width="140dp"
    android:layout_height="0dp"/>

在调用show()方法之前,设置锚点视图。
listPopupWindow.setAnchorView(popupAnchor);
listPopupWindow.show();

1
以下内容可以帮助你;
listPopupWindow.setWidth(400);
listPopupWindow.setHeight(ListPopupWindow.WRAP_CONTENT);

0

我的方法是这样的。

在您的布局中创建一个作为锚点的View,具有您指定的宽度和位置(高度1 dp,宽度任意)(可以是动态的,例如使用ConstraintLayouts)。将其设置为不可见(android:visibility=View.INIVISIBLE)。

                <View
                    android:id="@+id/my_anchor"
                    android:layout_height="1dp"
                    android:layout_width="0dp"
                    app:layout_constraintTop_toBottomOf="@id/list_item_order_list_app_compat_image_button_gallery"
                    app:layout_constraintStart_toEndOf="@id/list_item_order_list_material_button_map"
                    app:layout_constraintEnd_toEndOf="parent"
                    android:visibility="invisible"
                    android:layout_marginStart="8dp"
                    android:layout_marginEnd="8dp"
                    />

在你的视图中引用这个视图(即使用绑定):

listPopupWindow.anchorView = binding.myAnchor


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