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);
}
}
}
}
Создается
A Button
, который открывается нажатием DialogFragment
. Пользовательский View
(myView
) должен иметь ширину 800 и высоту 300, которая правильно установлена в переопределении onMeasure()
. Этот View
рисует измеренные границы в пурпуре для целей отладки.
Ширина 800 шире, чем размер по умолчанию Dialog
на моем устройстве, но обрезается, а не растягивается правильно.
Я просмотрел следующие решения:
Я вывел следующие два подхода к кодированию:
- Получите
WindowManager.LayoutParams
Dialog
и переопределите их, используя myDialog.getDialog().getWindow().get/setAttributes()
- Используя метод
setLayout(w, h)
через myDialog.getDialog().getWindow().setLayout()
Я пробовал их везде, о которых я могу думать (переопределяя onStart()
, в onShowListener
, после создания и отображения Dialog
и т.д.) и, как правило, можно корректно работать с обоими методами, если LayoutParams
задано конкретное значение. Но всякий раз, когда WRAP_CONTENT
предоставляется, ничего не происходит.
Любые предложения?
EDIT:
Снимок экрана:
![enter image description here]()
Снимок экрана с конкретным значением (здесь записано примечание 900, 850 не охватывает всю ширину представления, что имеет смысл, учитывая, что все окно корректируется. Таким образом, это обеспечивает - если нужно другое - причина, почему WRAP_CONTENT
является существенным/фиксированные значения не подходят):
![enter image description here]()
Ответы
Ответ 1
У меня есть рабочее решение, которое, честно говоря, я думаю, что он слишком глубокий, чтобы получить такой простой результат. Но вот он:
Что именно происходит:
Открыв макет Dialog
с помощью Hierarchy Viewer, я смог изучить весь макет AlertDialog
и что именно что происходило:
![enter image description here]()
Значок синий - это все части высокого уровня (Window
, фреймы для визуального стиля Dialog
и т.д.) и с конца синего down, где компоненты для AlertDialog
( красный= название, желтый= задержка прокрутки, возможно, для списка AlertDialog
s, зеленый= Dialog
, т.е. пользовательский вид, оранжевый =).
Отсюда ясно, что путь 7-view (начиная с начала синего до конца зеленый) был неправильным WRAP_CONTENT
. Глядя на LayoutParams.width
каждого View
, выяснилось, что все даны LayoutParams.width = MATCH_PARENT
и где-то (я думаю, наверху) задан размер. Поэтому, если вы будете следовать этому дереву, ясно, что ваш пользовательский View
в нижней части дерева будет никогда не влиять на размер Dialog
.
Итак, каковы были существующие решения?
- Оба подходов к кодированию, упомянутые в моем вопросе, просто получали верхний
View
и изменяли его LayoutParams
. Очевидно, что при всех View
объектах в дереве, соответствующем родительскому элементу, если на верхнем уровне установлен статический размер, весь Dialog
изменит размер. Но если верхний уровень установлен на WRAP_CONTENT
, все остальные объекты View
в дереве все еще смотрят дерево на "MATCH the PARENT", а не глядя вниз на дерево, чтобы "WRAP их CONTENT",.
Как решить проблему:
Неправильно, измените 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();
}
И вот рабочий результат:
![enter image description here]()
Это смущает меня, почему весь 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
Я нашел один вопрос с ответом 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();
}
Ответ 5
Это сработало для меня:
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);
Ответ 6
Использование
setStyle(STYLE_NORMAL, android.R.style.Theme_Holo_Light_Dialog);
Ответ 7
Его поздний ответ, но я думаю, что должен поделиться этим с новыми посетителями, чтобы они знали, какой диалог они должны предпочесть.
Если вы хотите создать собственное диалоговое окно с любым из ваших представлений. Затем вы можете выбрать самый простой способ сделать это.
В вопросе, который вам нужен диалог, нужно обернуть контент,, вы можете создать родительский макет с match_parent с прозрачным фоном и центром тяжести.
и поставьте в нее свой основной макет. поэтому он будет выглядеть как диалоговое окно с расположением по центру.
БОНУС. С помощью этого метода вы можете использовать scrollview, recycler view и любой тип макета в диалоговом окне.
В этом примере R.layout.dialog_update_name
- образец макета. которые вы хотите раздуть в диалоговом окне.
public void showCustomDialog(final Context context) {
this.context = context;
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_update_name, null, false);
findByIds(view); /*HERE YOU CAN FIND YOU IDS AND SET TEXTS OR BUTTONS*/
((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();
}
Ответ 8
просто используйте 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);
}
}