自定义视图的AlertDialog:调整大小以适应视图内容

60
我一直在构建的应用中遇到了这个问题。请忽略所有设计上的缺陷和缺乏最佳实践方法,这纯粹是为了展示我无法解决的问题的例子。
我有一个使用AlertDialog.Builder.setView()设置自定义View的DialogFragment,返回一个基本的AlertDialog。如果此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 为了调试目的绘制了它的测量范围(magenta)。
我的设备上默认的 Dialog 大小比 800 的宽度要窄,但是它被裁剪而不是正确地拉伸。
我已经查看了以下解决方案: 我推导出了以下两种编码方法:
  1. 通过myDialog.getDialog().getWindow().get/setAttributes()获取并覆盖DialogWindowManager.LayoutParams
  2. 使用myDialog.getDialog().getWindow().setLayout()方法中的setLayout(w, h)

我已经在所有我能想到的地方尝试过它们(在onStart()中重写,在onShowListener中,创建和显示Dialog后等等),如果为LayoutParams提供了一个特定的值,通常可以成功地使用这两种方法。但是每当提供WRAP_CONTENT时,什么也不会发生。

有什么建议吗?

编辑:

情况的屏幕截图: enter image description here

这是一个特定值的屏幕截图(注意此处输入了900,850不能覆盖整个视图的宽度,考虑到整个窗口正在调整,这是有道理的。因此,如果需要另一个原因,WRAP_CONTENT是必不可少的/固定值不合适):enter image description here


1
Material AppCompat AlertDialog的解决方案:https://dev59.com/Govda4cB1Zd3GeqPUQ46#39907568 - user924
8个回答

65

我有一个可行的解决方案,说实话,我认为这个方案过于复杂,去获取如此简单的结果。但是这就是它:

到底发生了什么:

通过使用Hierarchy Viewer打开Dialog布局,我能够检查AlertDialog的整个布局以及确切的情况: enter image description here

蓝色高亮部分是所有高层次部分(WindowDialog视觉样式的框架等),从蓝色结束到下面是AlertDialog组件(红色 = 标题,黄色 = 用于列表AlertDialog的滚动视图存根,绿色 = Dialog内容即自定义视图,橙色 = 按钮)。

从这里可以清楚地看到,7个视图路径(从蓝色开始到绿色结束)无法正确地WRAP_CONTENT。查看每个ViewLayoutParams.width,发现所有这些视图都被赋予LayoutParams.width = MATCH_PARENT,并且某个地方(我猜是顶部)设置了一个大小。因此,如果按照该树进行操作,很明显,位于树底部的自定义View永远无法影响Dialog的��小。

那么现有的解决方案是做什么的?

  • 在我的问题中提到的两种编码方法都只是获取顶层View并修改其LayoutParams。显然,如果所有View对象在树中匹配父级,如果顶层设置为静态大小,则整个Dialog的大小将会改变。但是,如果将顶层设置为WRAP_CONTENT,则树中其余的所有View对象仍然向上查找以"匹配其父级",而不是向下查找以"包装内容"。

如何解决问题:

简单地说,将所有影响路径中的View对象的LayoutParams.width更改为WRAP_CONTENT

我发现这只能在调用DialogFragment的生命周期步骤onStart之后完成。因此,实现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来处理适合默认大小内的所有情况,但我相信现在它这样做有一个很好的原因(很想听听它是什么)。


经过数个小时的尝试寻找更好的解决方案,这是唯一有效的方法。 - sulai
1
好的解决方案,节省了我很多时间!将对话框变小花费了我几个小时的时间。隐式更改布局参数为“match_parent”非常烦人。 - Johnny Doe
3
我的CustomView应该是什么?我传入了getCurrentFocus()。编辑:我传入了对话框使用的View,明白了!太好了!编辑2:右边好像有一点填充。 - Zen
1
干得好!我成功地通过将这种方法与设置窗口布局参数相结合来使我的支持库材料风格对话框的宽度自动换行。我还必须设置一个最小宽度并调整一些 @dimen/ 值,以强制支持库本身对对话框进行约束。 - user1643723
1
对于普通的AlertDialog,我也进行了一些更改,它对我也起作用了。我花了几天时间寻找解决方案。谢谢。 - El_o.di
显示剩余6条评论

36

之后

dialog.show();

只需使用

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

非常简单的解决方案,但对我有效。我正在扩展一个对话框,但是假定这也适用于DialogFragment。


2
我有一个继承自Dialog的类,这段代码在show()的重写中,在super.show()之后工作正常。我还将它们两个都改为WRAP_CONTENT; 这也很好。 - Unknownweirdo

17

这是唯一适用于我的解决方案。你可以通过AlertDialog.Builder(context, R.style.AppTheme_Dialog)来应用样式。 - Joshua

6

这对我来说运行良好:

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

1
完美运行。谢谢。 - Sabeer

4

我在B T的答案中发现了一个问题,即对话框靠左对齐(而不是居中显示)。为了解决这个问题,我修改了父布局的gravity属性。请参见更新后的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();
}

3

对话框应该自适应内容

你可以创建一个父布局,使用match_parent属性和透明背景,并设置gravity属性为center。然后将主要布局放在其下方,这样它就会看起来像是居中定位的对话框。

通过这种方法,你可以在对话框中使用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();
}

0
使用 setStyle(STYLE_NORMAL,android.R.style.Theme_Holo_Light_Dialog);

-1

只需使用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);
    }
}

不幸的是,其中很多是专有代码,实际上并没有解释哪一部分是旨在解决问题的。 - Abandoned Cart

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接