【问题标题】:AlertDialog with custom view: Resize to wrap the view's content带有自定义视图的 AlertDialog:调整大小以包装视图的内容
【发布时间】:2013-02-01 03:50:38
【问题描述】:

我在正在构建的应用程序中遇到了这个问题。请忽略所有设计缺陷和缺乏最佳实践方法,这纯粹是为了展示我无法解决的问题。

我有DialogFragment,它返回一个基本的AlertDialog,并使用AlertDialog.Builder.setView() 设置了一个自定义View。如果此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. 获取DialogWindowManager.LayoutParams 并使用myDialog.getDialog().getWindow().get/setAttributes() 覆盖它们
  2. 通过myDialog.getDialog().getWindow().setLayout() 使用setLayout(w, h) 方法

我在我能想到的任何地方都尝试过它们(覆盖onStart(),在onShowListener 中,在创建和显示Dialog 之后等)并且如果LayoutParams 通常可以使这两种方法正常工作提供了一个特定值。但只要提供WRAP_CONTENT,什么都不会发生。

有什么建议吗?

编辑:

情况截图:

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

【问题讨论】:

标签: java android android-layout android-alertdialog android-dialogfragment


【解决方案1】:

我有一个可行的解决方案,老实说,我认为挖掘得太深而无法获得如此简单的结果。但这里是:

到底发生了什么:

通过使用Hierarchy Viewer 打开Dialog 布局,我能够检查AlertDialog 的整个布局以及到底发生了什么:

蓝色突出显示所有高级部分(WindowDialog 视觉样式的框架等)以及从 蓝色 的末尾开始down 是 AlertDialog 的组件所在的位置(red = 标题,yellow = 滚动视图存根,可能用于列表 AlertDialogs,green = Dialog 内容即自定义视图,橙色 = 按钮)。

从这里很明显,7 视图路径(从 蓝色 的开始到 绿色 的结束)是没有正确的@987654331 @。查看每个ViewLayoutParams.width 显示所有都给出了LayoutParams.width = MATCH_PARENT 并且某处(我猜在顶部)设置了大小。因此,如果您跟随该树,很明显您在树底部的自定义 View,将永远能够影响 Dialog 的大小。

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

  • 我的问题中提到的两种编码方法都是简单地获取顶部View 并修改其LayoutParams。显然,由于树中的所有View 对象都与父对象匹配,如果顶层设置为静态大小,则整个Dialog 将改变大小。但如果顶层设置为WRAP_CONTENT,则树中所有其余的View 对象仍查找树以“匹配其父级”,而不是向下看以“整理他们的内容”。

如何解决问题:

说白了就是把影响路径中所有View对象的LayoutParams.width改成WRAP_CONTENT

我发现这只能在调用DialogFragmentonStart 生命周期步骤之后完成。所以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 来处理所有符合默认大小的情况,但我确信这是有充分理由的它的样子(很想听)。

【讨论】:

  • 经过 小时 尝试找到更好的解决方案,这是唯一有效的解决方案。
  • 不错的解决方案,它为我节省了很多时间!缩小对话占用了我几个小时的时间。将布局参数隐式更改为 match_parent 很烦人。
  • myCustomView 应该是什么?我传入了getCurrentFocus() 编辑:传入了对话框使用的View,明白了!这很棒! Edit2:我的右边似乎有一点填充
  • 干得好!通过将此方法与设置窗口布局参数相结合,我设法使我的支持库材质样式对话框的宽度换行。我还必须设置一个 minWidth 并修改几个 @dimen/ 值,由支持库本身在对话框中强制执行。
  • 对正常的 AlertDialog 也适用,对我来说也有一些变化。我花了几天时间寻找解决方案。谢谢。
【解决方案2】:

之后

dialog.show();

随便用

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

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

【讨论】:

  • 我有一个扩展 Dialog 的类,并且这段代码在 super.show(); 之后覆盖了 show()。我还把它们都改成了 WRAP_CONTENT;那也很好。
【解决方案3】:

AlertDialog 使用这两个窗口属性来定义它们可以达到的最小尺寸,以便在平板电脑上它们在屏幕中心以合理的宽度浮动。

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

您可以扩展您选择的默认对话框样式并更改值以应用您自己的逻辑。

【讨论】:

  • 这是唯一适合我的解决方案。您可以通过AlertDialog.Builder(context, R.style.AppTheme_Dialog) 应用样式。
【解决方案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);

【讨论】:

  • 完美运行。谢谢
【解决方案5】:

我发现B T's answer 存在一个问题。对话框已左对齐(不在屏幕中心)。为了解决这个问题,我添加了更改父布局的重力。请参阅更新的 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();
}

【讨论】:

    【解决方案6】:

    对话框应该是包装内容

    您可以使用ma​​tch_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();
    }
    

    【讨论】:

      【解决方案7】:

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

      【讨论】:

        【解决方案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);
            }
        }
        

        【讨论】:

        • 不幸的是,其中大部分是专有代码,并没有真正解释它的哪一部分实际上是为了解决问题。
        猜你喜欢
        • 1970-01-01
        • 2012-05-26
        • 1970-01-01
        • 1970-01-01
        • 2011-11-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多