43

I'm trying to use ListPopupWindow to show a list of strings via an ArrayAdapter (eventually this will be a more complex custom adapter). Code is below. As shown in the screenshot, the resulting ListPopupWindow seems to act as if the content width is zero. It shows the proper number of items, the items are still clickable, and clicking successfully produce a Toast, so at least that much is working properly.

An interesting note: I could supply a width in pixels to popup.setWidth(...) instead of ListPopupWindow.WRAP_CONTENT and it will show some of the content, but this seems very inflexible.

How do I make the ListPopupWindow wrap its content?

Test activity:

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();
    }
}

screenshot:

enter image description here

Karakuri
  • 38,365
  • 12
  • 84
  • 104
  • 2
    It seems that using `WRAP_CONTENT` causes the popup to set its width equal to the width of the anchor view, which in this example is the home icon on the ActionBar. I may end up having to use an explicit width value... – Karakuri Jan 07 '13 at 18:48
  • Can you do setContentWidth to WRAP_CONTENT instead of setWidth? I can't test this right now so I am not going to put it as an answer yet – Mike Jan 24 '13 at 16:58
  • 1
    @Mike: That doesn't help unfortunately. I checked the source code, and setContentWidth() forwards the call to setWidth(). The only difference is it accounts for some padding of the background drawable. – Karakuri Jan 24 '13 at 17:09
  • I did it like this and it works. I'm using this inside adapter . View view = ((AppCompatActivity) context).findViewById(R.id.sortItems); sortItems is the menu view on the Toolbar(Contextual actionBar). listPopupWindow.setWidth(view.getWidth() * 6); listPopupWindow.show(); Would definetly works – EngineSense Apr 30 '16 at 08:09

9 Answers9

66

You could measure the width of the adapter content:

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;
}

and in your showPopup() function:

 ArrayAdapter arrayAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, STRINGS);
    popup.setAdapter(arrayAdapter);
    popup.setAnchorView(anchorView);
    popup.setContentWidth(measureContentWidth(arrayAdapter));
janzoner
  • 1,420
  • 1
  • 11
  • 19
alerant
  • 661
  • 1
  • 6
  • 7
  • Nice. I only added mMeasureParent initialization and voted up. – janzoner Feb 10 '15 at 16:29
  • 1
    Got a crash on layout with sole TextView. Had to add `if (itemView != null && itemView.getLayoutParams() == null) { itemView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); }` – YetAnotherUser Dec 30 '15 at 19:28
  • 1
    I think the solution isn't working, I'm getting the wrong width back and the content looks cut out ... strange – Romina Liuzzi Feb 01 '21 at 17:25
32

The problem is with the implementation of ListPopupWindow. I checked the source code, and using setContentWidth(ListPopupWindow.WRAP_CONTENT) or setWidth(ListPopupWindow.WRAP_CONTENT) makes the popup window use the width of its anchor view instead.

Karakuri
  • 38,365
  • 12
  • 84
  • 104
  • Did the different layout in the ArrayAdapter not do anything then? I feel like there should be some way that this can be corrected – Mike Jan 24 '13 at 18:59
  • 4
    Hmm, this seems like a common use case, that is unfortunate :( – Mike Jan 24 '13 at 19:13
  • @Karakuri Did you ever find a nice workaround for this? It looks like it is still not fixed and its frustrating to say the least. – ndsc Mar 01 '14 at 16:22
  • 1
    You've misunderstood. There is no way to make the ListPopupWindow obey the `WRAP_CONTENT` width spec. I am pointing out this fact by explaining that the source code sets the width of the ListPopupWindow to the width of the anchor view when you use `WRAP_CONTENT`. – Karakuri Apr 22 '14 at 17:17
  • I used `PopupMenu`. Works fine, although it works with XML only, and with API 11+. Please refer to this answer http://stackoverflow.com/a/23550266/1276636 – Sufian Jul 01 '14 at 09:33
7

I think the best thing to use is the PopupMenu . example:

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();
android developer
  • 114,585
  • 152
  • 739
  • 1,270
5

I would just set a dimension in your dimen.xml file at 160dp or so:

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

Then set your popup width using the getDimensionPixelSize method to convert into pixels:

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

That should keep the size density independent.

JaredBanyard
  • 118
  • 2
  • 3
3

I believe that your issue lies with your ArrayAdapter using simple_list_item_1. If you look at the source code for that element it has the property of

android:layout_width="match_parent"

If you look at the source here you can create your own new_list_item_1.xml with the same info but changing it to android:layout_width="wrap_content" and then use that in your ArrayAdapter

Mike
  • 8,137
  • 6
  • 28
  • 46
3

You can actually get the anchorView's parent (since the actual anchorView is generally a button) and base your width from there. For example:

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

That way you can get a flexible width.

Caio L.
  • 31
  • 2
  • 1
    This doesn't help. The problem is I want to respect the size of the popup window contents, which is what the WRAP_CONTENT width spec is supposed to do, even according to Google's documentation. This has no relation whatsoever to the width of the anchor view or its parent view. – Karakuri Dec 31 '13 at 04:12
  • @Karakuri actually, google talked about this issue a long time ago, in a lecture called "the world of listView". they said there that you can't use wrap_content, as you can't really expect it to check all of the items (there could be a huge number of items). same goes for both height and width. they also said that if you use it for height, they do something weird (I don't remember what, maybe taking 3 items and that's it or something similar) . – android developer Aug 10 '14 at 13:22
  • @androiddeveloper Yes, they did talk about it. But `ListPopupWindow` was introduced in API 11, well after they knew of the issue with `ListView`. I'm referring to the documentation of `ListPopupWindow.WRAP_CONTENT` (not `ViewGroup.LayoutParams.WRAP_CONTENT`), which states: "If used to specify a popup width, the popup will use the width of its content." If this value is never obeyed by ListView, their inclusion of it as a public API with that documentation is erroneous and misleading. – Karakuri Aug 10 '14 at 20:59
  • @Karakuri Well since the content is a listView, and its width isn't determined by its content like other views do, you need to set it yourself... – android developer Aug 10 '14 at 21:19
1

The following can help;

listPopupWindow.setWidth(400);
listPopupWindow.setHeight(ListPopupWindow.WRAP_CONTENT);
Onur A.
  • 3,007
  • 3
  • 22
  • 37
1

Another solution is to set a 0dp height view in your layout xml to use as an anchor.

<View
    android:id="@+id/popup_anchor"
    android:layout_width="140dp"
    android:layout_height="0dp"/>

then set the anchor view sometime before calling the show() method.

listPopupWindow.setAnchorView(popupAnchor);
listPopupWindow.show();
parkgrrr
  • 738
  • 7
  • 12
0

My approach to this.

Create a Viewin your layout which functions as an anchor, with your dedicated width and position (Height 1 dp, width as you wish) (may be dynamic i.e. with ConstraintLayouts). Make it invisible (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"
                    />

Reference to this view in your viewAnchor, (i.e with bindings):

listPopupWindow.anchorView = binding.myAnchor

Roar Grønmo
  • 2,926
  • 2
  • 24
  • 37