PopupWindow вне экрана, когда размер не указан

Большинство примеров там точно определяют ширину и высоту всплывающего окна. Я хочу, чтобы они были WRAP_CONTENT - поскольку контент динамически определен, поэтому в конструкторе я устанавливаю -2 как по ширине, так и по высоте и показываю его через showAsDropDown (View anchor)

Выполняя это, всплывающее окно всегда отображается под анкером, что означает, что его можно вывести из экрана. Следующий фрагмент демонстрирует проблему. Попробуйте нажать на последний TextView, и вы не увидите PopupWindow, так как он отображается за пределами границ Windows. Почему это не работает? Я замечаю, что явно указывать размерность (например, 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;
    }
}

Ответ 1

Я начинаю сомневаться, что в контексте PopupWindow, что -2, -2 на самом деле означает WRAP_CONTENT, скорее я считаю, что его просто интерпретирует его как width = -2 height = -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, а затем используйте setWidth и setHeight, как ожидалось.

Edit

Просто попробовал это для себя и добавил эту строку кода, чтобы заставить ее работать

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

Ответ 2

Я решил эту проблему, вызвав метод 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);