带有自定义视图的 AlertDialog:调整大小以包装视图的内容 [英] AlertDialog with custom view: Resize to wrap the view's content

查看:33
本文介绍了带有自定义视图的 AlertDialog:调整大小以包装视图的内容的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

我有 DialogFragment,它返回一个基本的 AlertDialog 和一个使用 AlertDialog.Builder.setView() 设置的自定义 View代码>.如果此 View 有特定的大小要求,我如何让 Dialog 正确调整自身大小以显示自定义 View 中的所有内容?

这是我一直在使用的示例代码:

package com.test.test;导入 android.os.Bundle;导入 android.app.Activity;导入 android.app.AlertDialog;导入 android.app.Dialog;导入 android.app.DialogFragment;导入 android.content.Context;导入 android.graphics.Canvas;导入 android.graphics.Color;导入 android.graphics.Paint;导入 android.graphics.Paint.Style;导入 android.view.Gravity;导入 android.view.LayoutInflater;导入 android.view.View;导入 android.view.WindowManager;导入 android.view.View.OnClickListener;导入 android.view.ViewGroup;导入 android.view.ViewGroup.LayoutParams;导入 android.widget.ArrayAdapter;导入 android.widget.Button;导入 android.widget.EditText;导入 android.widget.FrameLayout;导入 android.widget.LinearLayout;导入 android.widget.Spinner;导入 android.widget.TextView;公共类 MainActivity 扩展 Activity {@覆盖protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//使用按钮启动按钮 b = 新按钮(这个);b.setText("启动");b.setOnClickListener(new OnClickListener() {@覆盖public void onClick(View v) {//启动对话框myDialog d = new myDialog();d.show(getFragmentManager(), null);}});setContentView(b);}公共静态类 myDialog 扩展了 DialogFragment {@覆盖公共对话框 onCreateDialog(Bundle savedInstanceState) {//创建对话框AlertDialog.Builder db = new AlertDialog.Builder(getActivity());db.setTitle("测试警报对话框:");db.setView(new myView(getActivity()));返回 db.create();}受保护的类 myView 扩展了视图 {油漆 p = 空;公共 myView(上下文 ct){超级(ct);//为绘图设置paintp = 新油漆();p.setColor(Color.MAGENTA);p.setStyle(Style.STROKE);p.setStrokeWidth(10);}@覆盖protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(800, 300);}@覆盖受保护的无效 onDraw(画布画布){//绘制一个矩形显示视图的边界canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), p);}}}}

一个 Button 被创建,它在点击时打开 DialogFragment.自定义 View (myView) 需要具有 800 的宽度和 300 的高度,这在 onMeasure() 的覆盖中正确设置.此 View 以品红色绘制其测量边界以用于调试目的.

800 宽度比我设备上的默认 Dialog 尺寸宽,但被剪裁而不是正确拉伸.

我查看了以下解决方案:

  • ,我能够检查 AlertDialog 的整个布局以及到底发生了什么:

    蓝色 突出显示所有高级部分(WindowDialog 视觉样式的框架等)以及从末尾开始蓝色下方是 AlertDialog 的组件所在的位置(红色 = 标题,黄色 = 滚动视图存根,也许对于列表 AlertDialogs,green = Dialog 内容,即自定义视图,orange = 按钮).

    从这里很明显,7 视图路径(从 蓝色 的开始到绿色 的结尾)是未能正确WRAP_CONTENT.查看每个 ViewLayoutParams.width 发现所有都给定了 LayoutParams.width = MATCH_PARENT 和某处(我猜在顶部)大小设置.因此,如果您遵循该树,很明显您在树底部的自定义 View永远无法影响 Dialog.

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

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

    如何解决问题:

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

    我发现这只能在调用 DialogFragmentonStart 生命周期步骤之后才能完成.所以 onStart 是这样实现的:

    @Override公共无效 onStart() {//必须先调用它!否则视图调整将不会出现在显示的对话框中(很可能被覆盖)super.onStart();forceWrapContent(myCustomView);}

    然后函数适当修改View层次结构LayoutParams:

    protected void forceWrapContent(View v) {//从提供的视图开始查看当前 = v;//向上移动直到失败,修改 LayoutParams做 {//获取父级ViewParent parent = current.getParent();//检查父节点是否存在如果(父母!= null){//获取视图尝试 {当前 =(查看)父级;} catch (ClassCastException e) {//在顶视图时会发生这种情况,不能将其转换为视图休息;}//修改布局current.getLayoutParams().width = LayoutParams.WRAP_CONTENT;}} while (current.getParent() != null);//请求重新完成布局current.requestLayout();}

    这是工作结果:

    这让我很困惑,为什么整个 Dialog 不想成为 WRAP_CONTENT 并带有显式的 minWidth 设置来处理适合默认大小,但我相信它的方式有很好的理由(有兴趣听到它).

    I have been having this problem in an application I am building. Please ignore all of the design shortcomings and lack of best practice approaches, this is purely to show an example of what I cannot solve.

    I have DialogFragment which returns a basic AlertDialog with a custom View set using AlertDialog.Builder.setView(). If this View has a specific size requirement, how do I get the Dialog to correctly resize itself to display all of the content in the custom View?

    This is the example code I have been using:

    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 is created, which opens the DialogFragment on a click. The custom View (myView) is required to have a width of 800 and height of 300 which is correctly set in an override of onMeasure(). This View, draws its measured bounds in magenta for debugging purposes.

    The 800 width is wider than the default Dialog size on my device, but is clipped rather than stretching correctly.

    I have looked through the following solutions:

    I have deduced the following two coding approaches:

    1. Get the WindowManager.LayoutParams of the Dialog and override them using myDialog.getDialog().getWindow().get/setAttributes()
    2. Using the setLayout(w, h) method through myDialog.getDialog().getWindow().setLayout()

    I have tried them everywhere I can think of (overriding onStart(), in a onShowListener, after the Dialog is created and shown, etc) and can generally get both methods to work correctly if the LayoutParams are supplied a specific value. But whenever WRAP_CONTENT is supplied, nothing happens.

    Any suggestions?

    EDIT:

    Screenshot of the situation:

    Screenshot of a specific value (note 900 is entered here, 850 doesn't cover the entire width of the View, which makes sense given the entire window is being adjusted. So that provides - if another was needed - reason why WRAP_CONTENT is essential / fixed values are not appropriate):

    解决方案

    I have a working solution that to be honest, I think digs way too deep to obtain such a simple result. But here it is:

    What exactly is happening:

    By opening the Dialog layout with the Hierarchy Viewer, I was able to examine the entire layout of the AlertDialog and what exactly what was going on:

    The blue highlight is all of the high level parts (Window, frames for the Dialog visual style, etc) and from the end of the blue down is where the components for the AlertDialog are (red = title, yellow = a scrollview stub, maybe for list AlertDialogs, green = Dialog content i.e. custom view, orange = buttons).

    From here it was clear that the 7-view path (from the start of the blue to the end of the green) was what was failing to correctly WRAP_CONTENT. Looking at the LayoutParams.width of each View revealed that all are given LayoutParams.width = MATCH_PARENT and somewhere (I guess at the top) a size is set. So if you follow that tree, it is clear that your custom View at the bottom of the tree, will never be able to affect the size of the Dialog.

    So what were the existing solutions doing?

    • Both of the coding approaches mentioned in my question were simply getting the top View and modifying its LayoutParams. Obviously, with all View objects in the tree matching the parent, if the top level is set a static size, the whole Dialog will change size. But if the top level is set to WRAP_CONTENT, all the rest of the View objects in the tree are still looking up the tree to "MATCH their PARENT", as opposed to looking down the tree to "WRAP their CONTENT".

    How to solve the problem:

    Bluntly, change the LayoutParams.width of all View objects in the affecting path to be WRAP_CONTENT.

    I found that this could only be done AFTER onStart lifecycle step of the DialogFragment is called. So the onStart is implemented like:

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

    Then the function to appropriately modify the View hierarchy 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();
    }
    

    And here is the working result:

    It confuses me why the entire Dialog would not want to be WRAP_CONTENT with an explicit minWidth set to handle all cases that fit inside the default size, but I'm sure there is a good reason for it the way it is (would be interested to hear it).

    这篇关于带有自定义视图的 AlertDialog:调整大小以包装视图的内容的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆