AlertDialog with custom view: Resize to wrap the view's content
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?
- Both of the coding approaches mentioned in my question were simply getting the top
View
and modifying itsLayoutParams
. Obviously, with allView
objects in the tree matching the parent, if the top level is set a static size, the wholeDialog
will change size. But if the top level is set toWRAP_CONTENT
, all the rest of theView
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).
After
dialog.show();
just use
dialog.getWindow().setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, yourHeight);
Very simple solution, but it works for me. I'm extending a dialog though but assumes that this will work for DialogFragment also.
AlertDialog's use these two window attributes to define the smallest size they can be so that on Tablets they float with a reasonable width in the center of the screen.
http://developer.android.com/reference/android/R.attr.html#windowMinWidthMajor http://developer.android.com/reference/android/R.attr.html#windowMinWidthMinor
You can extend a default Dialog style of your choice and change the values to apply your own logic.
This worked ok for me:
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);