Question

I inflated a ListView as the contentView in a PopupWindow. If I don't set the width & height, I can't see the PopupWindow. If I set them like this:

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

The layout is being set like "fill_parent". Why?

The layout attributes of the ListView and ListView item are all set to "wrap_content". Any suggestion? Thanks.

Was it helpful?

Solution

Here's the SimpleListView class implementation. I know it's inefficient and I'm pretty sure it contains errors, but it shows how to measure a width of a list view.

I also recommend to read this article.

    public class SimpleListView extends AdapterView<ListAdapter> {
    private ListAdapter adapter;
    private Drawable divider;
    private int dividerHeight;
    private boolean clipDivider;

    public SimpleListView( final Context context )
    {
        this( context, null );
    }

    public SimpleListView( final Context context, final AttributeSet attrs )
    {
        this( context, attrs, android.R.attr.listViewStyle );
    }

    public SimpleListView( final Context context, final AttributeSet attrs, final int defStyle )
    {
        super( context, attrs, defStyle );

        final TypedArray array =
            context.obtainStyledAttributes( attrs, R.styleable.SimpleListView, defStyle, 0 );

        final Drawable dividerAttribute =
            array.getDrawable( R.styleable.SimpleListView_android_divider );
        if( dividerAttribute != null ) {
            setDivider( dividerAttribute );
        }

        final int dividerHeightAttribute =
            array.getDimensionPixelSize( R.styleable.SimpleListView_android_dividerHeight, 0 );
        if( dividerHeightAttribute != 0 ) {
            setDividerHeight( dividerHeightAttribute );
        }

        array.recycle();
    }

    public Drawable getDivider()
    {
        return this.divider;
    }

    public void setDivider( final Drawable newDivider )
    {
        if( newDivider != null ) {
            this.dividerHeight = newDivider.getIntrinsicHeight();
            this.clipDivider = newDivider instanceof ColorDrawable;
        } else {
            this.dividerHeight = 0;
            this.clipDivider = false;
        }
        this.divider = newDivider;
        requestLayout();
    }

    public int getDividerHeight()
    {
        return this.dividerHeight;
    }

    public void setDividerHeight( final int newHeight )
    {
        this.dividerHeight = newHeight;
        requestLayout();
    }

    @Override
    public ListAdapter getAdapter()
    {
        return this.adapter;
    }

    @Override
    public void setAdapter( final ListAdapter adapter )
    {
        this.adapter = adapter;
        removeAllViewsInLayout();
        requestLayout();
    }

    @Override
    public View getSelectedView()
    {
        throw new UnsupportedOperationException(
            "SimpleListView.getSelectedView() is not supported" );
    }

    @Override
    public void setSelection( final int position )
    {
        throw new UnsupportedOperationException(
            "SimpleListView.setSelection(int) is not supported" );
    }

    @Override
    protected void onMeasure( final int widthMeasureSpec, final int heightMeasureSpec )
    {
        super.onMeasure( widthMeasureSpec, heightMeasureSpec );

        final int widthMode = MeasureSpec.getMode( widthMeasureSpec );
        int widthSize = MeasureSpec.getSize( widthMeasureSpec );
        final int heightMode = MeasureSpec.getMode( heightMeasureSpec );
        int heightSize = MeasureSpec.getSize( heightMeasureSpec );

        int innerWidth = 0;
        int innerHeight = 0;

        final int itemCount = this.adapter == null ? 0 : this.adapter.getCount();
        if( itemCount > 0
            && ( widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY ) ) {
            for( int i = 0; i < itemCount; ++i ) {
                final View convertView = getChildAt( i );
                final View child = this.adapter.getView( i, convertView, this );
                if( convertView == null ) {
                    LayoutParams params = child.getLayoutParams();
                    if( params == null ) {
                        params = new LayoutParams( LayoutParams.WRAP_CONTENT,
                            LayoutParams.WRAP_CONTENT );
                        child.setLayoutParams( params );
                    }
                    addViewInLayout( child, i, params );
                }

                if( child.getLayoutParams() instanceof MarginLayoutParams ) {
                    measureChildWithMargins( child, widthMeasureSpec, 0, heightMeasureSpec, 0 );
                } else {
                    measureChild( child, widthMeasureSpec, heightMeasureSpec );
                }
                innerWidth = Math.max( innerWidth, child.getMeasuredWidth() );
                innerHeight += child.getMeasuredHeight();
            }

            innerHeight += ( itemCount - 1 ) * this.dividerHeight;
        }

        if( widthMode != MeasureSpec.EXACTLY ) {
            final int newWidthSize = getPaddingLeft() + getPaddingRight() + innerWidth;
            widthSize =
                widthMode == MeasureSpec.AT_MOST ? Math.min( widthSize, newWidthSize )
                    : newWidthSize;
        }

        if( heightMode != MeasureSpec.EXACTLY ) {
            final int newHeightSize = getPaddingTop() + getPaddingBottom() + innerHeight;
            heightSize =
                heightMode == MeasureSpec.AT_MOST ? Math.min( heightSize, newHeightSize )
                    : newHeightSize;
        }

        setMeasuredDimension( widthSize, heightSize );
    }

    @Override
    protected void onLayout( final boolean changed, final int left, final int top, final int right,
        final int bottom )
    {
        super.onLayout( changed, left, top, right, bottom );

        if( this.adapter == null ) {
            return;
        }

        positionItems();
        invalidate();
    }

    private void positionItems()
    {
        int top = getPaddingTop();
        final int left = getPaddingLeft();

        for( int i = 0, count = getChildCount(); i < count; ++i ) {
            final View child = getChildAt( i );

            final int width = child.getMeasuredWidth();
            final int height = child.getMeasuredHeight();

            child.layout( left, top, left + width, top + height );
            top += height + this.dividerHeight;
        }
    }

    @Override
    protected void dispatchDraw( final Canvas canvas )
    {
        final boolean drawDividers = this.dividerHeight > 0 && this.divider != null;

        if( drawDividers ) {
            final int left = getPaddingLeft();
            final int right = getWidth() - getPaddingRight();
            final Rect dividerBounds = new Rect( left, 0, right, 0 );

            for( int i = 0, count = getChildCount(); i < count - 1; ++i ) {
                final View child = getChildAt( i );

                dividerBounds.top = dividerBounds.bottom + child.getMeasuredHeight();
                dividerBounds.bottom = dividerBounds.top + this.dividerHeight;
                drawDivider( canvas, dividerBounds );
            }
        }

        super.dispatchDraw( canvas );
    }

    private void drawDivider( final Canvas canvas, final Rect bounds )
    {
        if( !this.clipDivider ) {
            this.divider.setBounds( bounds );
        } else {
            canvas.save();
            canvas.clipRect( bounds );
        }

        this.divider.draw( canvas );

        if( this.clipDivider ) {
            canvas.restore();
        }
    }
}

OTHER TIPS

This is how to do it:

// Don't use this. It causes ListView's to have strange widths, similar to "fill_parent"
//popupWindow.setWindowLayoutMode(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT);

// Instead, use this:
final int NUM_OF_VISIBLE_LIST_ROWS = 4;
listView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
popupWindow.setWidth(listView.getMeasuredWidth());
popupWindow.setHeight((listView.getMeasuredHeight() + 10) * NUM_OF_VISIBLE_LIST_ROWS);

Note that setWidth() and setHeight() use raw pixel values, so you need to adjust for different screen sizes and different densities.

My solution is override onMeasure of ListView like this:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int maxWidth = meathureWidthByChilds() + getPaddingLeft() + getPaddingRight();
    super.onMeasure(MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.EXACTLY), heightMeasureSpec);     
}

public int meathureWidthByChilds() {
    int maxWidth = 0;
    View view = null;
    for (int i = 0; i < getAdapter().getCount(); i++) {
        view = getAdapter().getView(i, view, this);
        view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
        if (view.getMeasuredWidth() > maxWidth){
            maxWidth = view.getMeasuredWidth();
        }
    }
    return maxWidth;
}

I faced the same problem. The only solution I found is to set the PopupWindow width to the exact width of the ListView:

listView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
final PopupWindow popup = new PopupWindow(listView, listView.getMeasuredWidth(),
    ViewGroup.LayoutParams.WRAP_CONTENT, true);
popup.showAsDropDown(anchor);

Unfortunately, it doesn't solve the problem entirely. The ListView measures itself so that it can wrap only its first child. If for example the second child is wider than the first one, the second child will be clipped.

I'm not sure, but the only way to change the way the ListView measures itself is to subclass it and override the onMeasure() method. I'm trying to do it now and I'll write a comment here if I succeed in it.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top