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:
WindowManager.LayoutParams
of the Dialog
and override them using myDialog.getDialog().getWindow().get/setAttributes()
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):
Step 1: Create a XML file: custom_layout. Add the below code in custom_layout. xml. This code defines the alertdialog box dimensions and add a edittext in it.
A simple dialog containing an DatePicker . This class was deprecated in API level 26.
Dialog: A dialog is a small window that prompts the user to make a decision or enter additional information. DialogFragment: A DialogFragment is a special fragment subclass that is designed for creating and hosting dialogs.
Alert Dialog shows the Alert message and gives the answer in the form of yes or no. Alert Dialog displays the message to warn you and then according to your response the next step is processed. Android Alert Dialog is built with the use of three fields: Title, Message area, Action Button.
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 AlertDialog
s, 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?
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With