Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PopupWindow out of screen when size is unspecified

Tags:

android

Most examples out there specify exactly the width and height of the popup window. I want them to be WRAP_CONTENT - since the content is dinamically determined, so in the constructor I set -2 for both width and height and show it via showAsDropDown(View anchor)

Doing this, the popup is always drawn below the anchor view, which means it can be drawn offscreen. The following snippet demonstrates the problem. Try clicking on the last TextView and you won't see any PopupWindow, since it's shown outside of the windows bounds. Why doesn't it work? I remark that specifying dimension explicitly (for example 200, 100) doesn't trigger the problem. Try it yourself

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;
    }
}
like image 512
Raffaele Avatar asked Oct 08 '11 10:10

Raffaele


1 Answers

I'm starting to doubt that in the context of a PopupWindow that -2, -2 actually means WRAP_CONTENT rather I think that its just interpreting it as width = -2 height = -2

This is from the Android Source

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

where setWidth(int width) is just a simple mWidth = width; I think what you are looking for instead is the setWindowLayoutMode (int widthSpec, int heightSpec) method. That is where you should pass in the WRAP_CONTENT

If all else fails, measure the width and height of the TextView and then use setWidth and setHeight as expected.

Edit:

Just tried it out for myself and added this line of code to make it work

    // -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);

I left the original comments in there so you can see for yourself where everything should go.

Edit 2: Okay, so I took your code you posted and verbatim tried it on my device, and you were right, it didn't work because when Android is determining which views to layout to put it in, it relies on the width and height you provided. What that means is since you are using 0 and 0 as your width and height, Android will come along and say, "Oh look, the box is only a 0 by 0 box so I can display it as a popup below the content." This isn't what is desired though! The thing is, you know that the box is going to be bigger than that, so lets start putting in some different numbers and seeing the results that come from it.

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

Now when I go and click the bottom box, notice that it jumps above. (See screenshot) That's because Android KNOWS that the width and height are 1 (as I set in the constructor) and that the available screen space below the list item is 0. Well, if there's not enough room to display it there, then it must above then!

enter image description here

But wait! What if like in my current example the string adds on more content each time? Well, this is where things get interesting. You see, in my next screenshot that the popup, even though it should be displayed up since it is 6 lines long now, is rather displayed at the bottom! Oh crap, right! That's because it is measuring against the 1 1 I used in the constructor. Well what are some solutions to this then?

enter image description here

Solution One: My preferred way would be to take a guess at what the average max height of the TextView would be and then simply toss in one generically big number.

popup = new PopupWindow(getPopupContent(), 300, 300); //just guessing it won't get bigger than that

Solution Two: This is more proper, but you are sacrificing a little speed to do it. Using the Paint class to measure what the text content size is going to be and passing that into the the setWidth() and setHeight() before you display the popup. I went ahead and built an almost complete solution, but I didn't measure in padding and stuff (see the comment)

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

And then in the onClick() I added these two lines

    popup.setHeight(maxHeight);
    popup.setWidth(maxWidth);
like image 152
Bob Avatar answered Oct 29 '22 13:10

Bob