AlertDialog с пользовательским представлением: измените размер, чтобы обернуть содержимое представления

У меня возникла эта проблема в приложении, которое я создаю. Пожалуйста, игнорируйте все недостатки дизайна и отсутствие передовых подходов, это просто для того, чтобы показать пример того, что я не могу решить.

У меня есть DialogFragment, который возвращает базовый AlertDialog с пользовательским набором View с использованием AlertDialog.Builder.setView(). Если у этого View есть определенные требования к размеру, как мне заставить Dialog правильно изменить размер, чтобы отобразить все содержимое пользовательского View?

Это пример кода, который я использовал:

package com.test.test;

import android.os.Bundle;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Use a button for launching
        Button b = new Button(this);
        b.setText("Launch");
        b.setOnClickListener(new OnClickListener() {    
            @Override
            public void onClick(View v) {
                // Launch the dialog
                myDialog d = new myDialog();
                d.show(getFragmentManager(), null);
            }
        });

        setContentView(b);
    }

    public static class myDialog extends DialogFragment {

        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {           
            // Create the dialog
            AlertDialog.Builder db = new AlertDialog.Builder(getActivity());
            db.setTitle("Test Alert Dialog:");
            db.setView(new myView(getActivity()));

            return db.create();
        }

        protected class myView extends View {
            Paint p = null;

            public myView(Context ct) {
                super(ct);

                // Setup paint for the drawing
                p = new Paint();
                p.setColor(Color.MAGENTA);
                p.setStyle(Style.STROKE);
                p.setStrokeWidth(10);
            }

            @Override
            protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
                setMeasuredDimension(800, 300);
            }

            @Override
            protected void onDraw(Canvas canvas) {
                // Draw a rectangle showing the bounds of the view
                canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), p);
            }
        }
    }
}

Создается Button, который открывает DialogFragment по щелчку. Пользовательский View (myView) должен иметь ширину 800 и высоту 300, что правильно установлено в переопределении onMeasure(). Этот View рисует свои измеренные границы пурпурным цветом в целях отладки.

Ширина 800 шире, чем размер Dialog по умолчанию на моем устройстве, но она обрезается, а не растягивается правильно.

Я просмотрел следующие решения:

Я вывел следующие два подхода к кодированию:

  1. Получите WindowManager.LayoutParams из Dialog и переопределите их с помощью myDialog.getDialog().getWindow().get/setAttributes()
  2. Используя метод setLayout(w, h) через myDialog.getDialog().getWindow().setLayout()

Я пробовал их везде, где только мог (переопределение onStart(), в onShowListener, после создания и отображения Dialog и т. д.), и в целом могу заставить оба метода работать правильно, если LayoutParams предоставляется конкретное значение. Но всякий раз, когда указывается WRAP_CONTENT, ничего не происходит.

Какие-либо предложения?

ИЗМЕНИТЬ:

Скриншот ситуации: введите здесь описание изображения

Снимок экрана с конкретным значением (здесь вводится 900, 850 не покрывает всю ширину представления, что имеет смысл, учитывая, что настраивается все окно. Таким образом, это дает - если было необходимо другое - причину, по которой WRAP_CONTENT имеет важное значение / фиксированные значения не подходят): введите здесь описание изображения


person btalb    schedule 16.02.2013    source источник
comment
stackoverflow.com/a/39907568/7767664 — решение для диалогового окна AlertDialog AppCompat Material   -  person user924    schedule 09.12.2017


Ответы (8)


У меня есть рабочее решение, которое, если честно, я думаю, копает слишком глубоко, чтобы получить такой простой результат. Но вот оно:

Что именно происходит:

Открыв макет Dialog с помощью средства просмотра иерархии, я смог изучить весь макет AlertDialog и что именно происходит: введите здесь описание изображения

Синяя подсветка — это все части высокого уровня (Window, рамки для визуального стиля Dialog и т. д.), а от конца синего вниз находятся компоненты для AlertDialog (красный = заголовок, желтый = заглушка прокрутки, возможно, для списка AlertDialogs, зеленый = Dialog контент, т.е. настраиваемый вид, оранжевый = кнопки).

Отсюда стало ясно, что путь с 7 представлениями (от начала синего до конца зеленого) был неправильным WRAP_CONTENT. Глядя на LayoutParams.width каждого View, выяснилось, что всем дается LayoutParams.width = MATCH_PARENT и где-то (думаю, вверху) установлен размер. Поэтому, если вы следуете этому дереву, становится ясно, что ваш пользовательский View в нижней части дерева никогда не сможет повлиять на размер Dialog.

Так что же делали существующие решения?

  • Оба подхода к кодированию, упомянутые в моем вопросе, заключались в простом получении верхнего View и изменении его LayoutParams. Очевидно, что со всеми View объектами в дереве, соответствующими родителю, если для верхнего уровня задан статический размер, все Dialog изменит размер. Но если верхний уровень установлен на WRAP_CONTENT, все остальные View объекты в дереве по-прежнему смотрят вверх по дереву, чтобы "СООТВЕТСТВОВАТЬ своему РОДИТЕЛЮ", а не смотрят вниз по дереву. для "ОБЕРТЫВАНИЯ СОДЕРЖИМОГО".

Как решить проблему:

Грубо говоря, измените LayoutParams.width всех View объектов на затрагивающем пути на WRAP_CONTENT.

Я обнаружил, что это можно сделать только ПОСЛЕ onStart шага жизненного цикла DialogFragment. Итак, onStart реализовано так:

@Override
public void onStart() { 
    // This MUST be called first! Otherwise the view tweaking will not be present in the displayed Dialog (most likely overriden)
    super.onStart();

    forceWrapContent(myCustomView);
}

Затем функция для соответствующего изменения View иерархии LayoutParams:

protected void forceWrapContent(View v) {
    // Start with the provided view
    View current = v;

    // Travel up the tree until fail, modifying the LayoutParams
    do {
        // Get the parent
        ViewParent parent = current.getParent();    

        // Check if the parent exists
        if (parent != null) {
            // Get the view
            try {
                current = (View) parent;
            } catch (ClassCastException e) {
                // This will happen when at the top view, it cannot be cast to a View
                break;
            }

            // Modify the layout
            current.getLayoutParams().width = LayoutParams.WRAP_CONTENT;
        }
    } while (current.getParent() != null);

    // Request a layout to be re-done
    current.requestLayout();
}

И вот рабочий результат: введите здесь описание изображения

Меня смущает, почему весь Dialog не хотел бы быть WRAP_CONTENT с явным набором minWidth для обработки всех случаев, которые вписываются в размер по умолчанию, но я уверен, что для этого есть веская причина (было бы интересно слышать).

person btalb    schedule 17.02.2013
comment
После часов попыток найти лучшее решение это единственное, что работает. - person sulai; 10.12.2013
comment
Хорошее решение, сэкономило мне кучу времени! Уменьшение размера диалога отняло у меня несколько часов времени. Неявное изменение параметров макета на match_parent раздражает. - person Johnny Doe; 04.03.2014
comment
Что такое myCustomView? Я передал в getCurrentFocus() Редактировать: Передал в представлении, которое использует диалог, понял! Отлично! Edit2: кажется, у меня есть немного отступов справа - person Zen; 01.04.2014
comment
Это решение сработало для меня. Спасибо. Вместо использования DialogFragment было бы ответом использовать Activity? используя: <activity android:theme="@android:style/Theme.Holo.Dialog" > Проверьте это (конец) developer.android.com/guide/ тем/ui/dialogs.html#CustomLayout - person Mert Gülsoy; 23.01.2015
comment
Прекрасная работа! Мне удалось сделать ширину диалогов в стиле материалов моей библиотеки поддержки оберткой, комбинируя этот подход с настройкой параметров макета окна. Мне также пришлось установить minWidth и повозиться с парой значений @dimen/, наложенных на диалог самой библиотекой поддержки. - person user1643723; 26.06.2015
comment
У меня работало и для обычного AlertDialog с некоторыми изменениями. Я провел дни в поисках решения. Спасибо. - person sparkly_frog; 12.08.2016
comment
У меня не сработало, когда я использовал android.support.v7.app.AlertDialog. У кого-нибудь есть идеи? - person Henrik; 28.01.2017
comment
@user1643723 user1643723 было бы здорово добавить пример для диалогов поддержки здесь - person user924; 09.12.2017
comment
@ user924 все эти значения ресурсов являются внутренними API, поэтому они могут просто измениться на другие имена в следующей версии библиотеки поддержки. Откройте исходный код библиотеки поддержки, найдите шаблон диалога и посмотрите сами, это довольно просто (должно быть ~3 файла макета, вложенные друг в друга, которые вместе составляют макет диалога). - person user1643723; 10.12.2017
comment
@user1643723 user1643723 для меня windowMinWidthMajor и windowMinWidthMinor решили проблему (в пользовательской теме диалога, которая расширяет некоторые диалоговые окна предупреждений appcompat, светлые или темные) - person user924; 10.12.2017
comment
Я думаю, что это улучшит читаемость вашего кода: if (parent instanceof View), так что вы можете преобразовать в View и избежать предложений try и catch. - person Simple; 18.06.2020

После

dialog.show();

просто используйте

dialog.getWindow().setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, yourHeight);

Очень простое решение, но оно работает для меня. Однако я расширяю диалог, но предполагаю, что это будет работать и для DialogFragment.

person sweggersen    schedule 18.07.2013
comment
У меня был класс, который расширял Dialog, и этот код работал в переопределении show() сразу после super.show();. Я также изменил их оба на WRAP_CONTENT; это тоже было хорошо. - person Unknownweirdo; 10.03.2015

AlertDialog использует эти два атрибута окна, чтобы определить наименьший размер, который они могут иметь, чтобы на планшетах они плавали с разумной шириной в центре экрана.

http://developer.android.com/reference/android/R.attr.html#windowMinWidthMajor http://developer.android.com/reference/android/R.attr.html#windowMinWidthMinor

Вы можете расширить стиль диалогового окна по умолчанию по своему выбору и изменить значения, чтобы применить свою собственную логику.

person Simon    schedule 07.04.2015
comment
Это единственное решение, которое работает для меня. Вы можете применить стиль через AlertDialog.Builder(context, R.style.AppTheme_Dialog). - person Joshua; 03.08.2017

Это сработало для меня нормально:

WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
lp.copyFrom(dialog.getWindow().getAttributes());
lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
dialog.show();
dialog.getWindow().setAttributes(lp);
person Rony Tesler    schedule 23.03.2016
comment
Работает идеально. Спасибо - person Sabeer Mohammed; 01.05.2019

Я нашел одну проблему с ответом B T. Диалог выровнен по левому краю (не по центру экрана). Чтобы исправить это, я добавил изменение гравитации родительских макетов. См. обновленный метод forceWrapContent().

protected void forceWrapContent(View v) {
    // Start with the provided view
    View current = v;

    // Travel up the tree until fail, modifying the LayoutParams
    do {
        // Get the parent
        ViewParent parent = current.getParent();

        // Check if the parent exists
        if (parent != null) {
            // Get the view
            try {
                current = (View) parent;
                ViewGroup.LayoutParams layoutParams = current.getLayoutParams();
                if (layoutParams instanceof FrameLayout.LayoutParams) {
                    ((FrameLayout.LayoutParams) layoutParams).
                            gravity = Gravity.CENTER_HORIZONTAL;
                } else if (layoutParams instanceof WindowManager.LayoutParams) {
                    ((WindowManager.LayoutParams) layoutParams).
                            gravity = Gravity.CENTER_HORIZONTAL;
                }
            } catch (ClassCastException e) {
                // This will happen when at the top view, it cannot be cast to a View
                break;
            }

            // Modify the layout
            current.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT;
        }
    } while (current.getParent() != null);

    // Request a layout to be re-done
    current.requestLayout();
}
person Yuriy    schedule 01.09.2015

Диалоговое окно должно быть обернуто

Вы можете создать родительский макет с помощью match_parent с прозрачным фоном и с центром тяжести. и поместите свой основной макет под него. поэтому он будет выглядеть как диалоговое окно, расположенное в центре.

С помощью этого метода вы можете использовать Scrollview, RecyclerView и любой тип макета в диалоговом окне.

public void showCustomDialog(Context context) {
    Dialog dialog = new Dialog(context);
    dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
    LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    view = inflater.inflate(R.layout.dialog_layout, null, false); 
    findByIds(view);  /*find your views by ids*/
    ((Activity) context).getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
    dialog.setContentView(view);
    final Window window = dialog.getWindow();
    window.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT);
    window.setBackgroundDrawableResource(R.color.colorTransparent);
    window.setGravity(Gravity.CENTER);
    dialog.show();
}
person Khemraj Sharma    schedule 27.10.2017

Используйте 1_

person Mickey Tin    schedule 21.05.2015

просто используйте AppCompatDialog

import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.AppCompatDialog;
import android.view.Window;
import android.widget.ProgressBar;

public class ProgressDialogCompat extends AppCompatDialog {

    public ProgressDialogCompat(Context context) {
        super(context);
        supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Context context = getContext();
        int padding = context.getResources().getDimensionPixelSize(R.dimen.x_medium);
        ProgressBar progressBar = new ProgressBar(context);
        progressBar.setPadding(padding, padding, padding, padding);
        setContentView(progressBar);
        setCancelable(false);
        super.onCreate(savedInstanceState);
    }
}
person zlxrx    schedule 18.03.2016
comment
К сожалению, большая часть этого кода является проприетарным и на самом деле не объясняет, какая его часть на самом деле предназначена для решения проблемы. - person Abandoned Cart; 06.05.2019