it-roy-ru.com

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.LayoutParamsDialog и переопределите их, используя myDialog.getDialog().getWindow().get/setAttributes()
  2. Использование метода setLayout(w, h) через myDialog.getDialog().getWindow().setLayout()

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

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

Правка:

Скриншот ситуации: enter image description here

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

52
B T

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

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

Открыв макет Dialog с помощью Hierarchy Viewer , я смог изучить весь макет AlertDialog и что именно происходило: enter image description here

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

Отсюда было ясно, что путь 7 представлений (от начала blue до конца green) был тем, что не удалось правильно 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);
}

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

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

И вот рабочий результат: enter image description here

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

58
B T

После

dialog.show();

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

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

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

31
sweggersen

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

http://developer.Android.com/reference/Android/R.attr.html#windowMinWidthMajorhttp://developer.Android.com/reference/Android/R.attr.html# windowMinWidthMinor

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

11
Simon

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

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

Это работало хорошо для меня:

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);
3
Rony Tesler

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

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

С помощью этого метода вы можете использовать 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();
}
1
Khemraj

Используйте setStyle(STYLE_NORMAL, Android.R.style.Theme_Holo_Light_Dialog);

0
Mickey Tin