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
на моем устройстве, но она обрезана, а не растягивается правильно.
Я просмотрел следующие решения:
- DialogFragment.getDialog возвращает null
- Как управлять шириной и высотой диалогового окна оповещения по умолчанию в Android?
- Размер диалогового окна оповещения или пользовательского диалогового окна оповещения
Я вывел следующие два подхода к кодированию:
- Получите
WindowManager.LayoutParams
изDialog
и переопределите их с помощьюmyDialog.getDialog().getWindow().get/setAttributes()
- Используя
setLayout(w, h)
метод черезmyDialog.getDialog().getWindow().setLayout()
Я пробовал их везде, о чем только мог подумать (переопределяя onStart()
, в onShowListener
, после того, как Dialog
создан и показанLayoutParams
, и т.д.), И, как правило, могу заставить оба метода работать правильно, если им задано определенное значение, и т.д. Но всякий раз, когда WRAP_CONTENT
предоставляется, ничего не происходит.
Есть предложения?
Редактировать:
Скриншот ситуации:
Скриншот определенного значения (примечание: здесь указано 900, 850 не покрывает всю ширину представления, что имеет смысл, учитывая, что корректируется все окно. Таким образом, это обеспечивает - если требовалась другая - причину, по которой WRAP_CONTENT
важно / фиксированные значения не подходят):
Переведено автоматически
Ответ 1
У меня есть рабочее решение, которое, честно говоря, я думаю, копает слишком глубоко, чтобы получить такой простой результат. Но вот оно:
Что именно происходит:
Открыв Dialog
макет с помощью средства просмотра иерархии, я смог изучить весь макет AlertDialog
и что именно происходило:
Синим цветом выделены все части высокого уровня (Window
, рамки для Dialog
визуального стиля и т.д.), А от конца синего вниз идут компоненты для AlertDialog
(красный = заголовок, желтый = заглушка scrollview, возможно, для списков AlertDialog
, зеленый = 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
набором для обработки всех случаев, которые укладываются в размер по умолчанию, но я уверен, что для этого есть веская причина таким, какой он есть (было бы интересно услышать это).
Ответ 2
После
dialog.show();
просто используйте
dialog.getWindow().setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, yourHeight);
Очень простое решение, но оно работает для меня. Я расширяю диалоговое окно, но предполагаю, что это будет работать и для DialogFragment.
Ответ 3
AlertDialog использует эти два атрибута окна для определения наименьшего возможного размера, чтобы на планшетах они располагались разумной шириной в центре экрана.
http://developer.android.com/reference/android/R.attr.html#windowMinWidthMajor
http://developer.android.com/reference/android/R.attr.html#windowMinWidthMinor
Вы можете расширить стиль диалога по умолчанию по вашему выбору и изменить значения, чтобы применить свою собственную логику.
Ответ 4
У меня это сработало нормально:
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);