Why Does My ListView Inside a Dialog Won’t Scroll? Understanding MeasureSpec and Fixing the Bug
An Android junior developer discovers a dialog‑embedded ListView that fails to scroll, investigates the custom ListViewForScrollView implementation, learns how MeasureSpec and onMeasure work, and resolves the issue by replacing the custom view with a standard ListView.
Production Incident: A Dialog Layout Cut Off the Bottom
A junior Android developer, Xiao Zhang, was confident his code was bug‑free until a tester reproduced an online bug where a dialog’s bottom service popup was truncated.
Reproducing the Scene
The screenshot shows the dialog’s lower part being clipped, indicating the dialog’s content is not fully displayed.
Root Cause Analysis
Initially Xiao Zhang blamed the dialog’s height, but the XML layout revealed a custom ListViewForScrollView inside a RelativeLayout:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- omitted unrelated code -->
<com.xx.ListViewForScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="20dp"
android:layout_marginTop="18dip"
android:layout_marginRight="20dp"
android:layout_marginBottom="48dp"
android:divider="@null" />
</RelativeLayout>Inspecting the custom view’s source shows it merely overrides onMeasure to force an AT_MOST mode with a very large size:
public class ListViewForScrollView extends ListView {
public ListViewForScrollView(Context context) { super(context); }
public ListViewForScrollView(Context context, AttributeSet attrs) { super(context, attrs); }
public ListViewForScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); }
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
}The developer wondered why the ListView could not scroll despite inheriting from ListView. He dug deeper into Android’s measurement process.
Understanding MeasureSpec
Every view goes through onMeasure, onDraw, and onLayout. The two parameters, widthMeasureSpec and heightMeasureSpec, are 32‑bit ints where the high two bits encode the mode (UNSPECIFIED, EXACTLY, AT_MOST) and the low 30 bits encode the size. The parent view supplies these specs, but a child can influence its final size.
The three modes are:
UNSPECIFIED : No constraints from the parent.
EXACTLY : The parent dictates an exact size.
AT_MOST : The child may be any size up to the specified maximum.
The static method ViewGroup.getChildMeasureSpec combines the parent spec with the child’s layout parameters to produce an appropriate child spec.
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
case MeasureSpec.EXACTLY:
if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; }
else if (childDimension == LayoutParams.MATCH_PARENT) { resultSize = size; resultMode = MeasureSpec.EXACTLY; }
else if (childDimension == LayoutParams.WRAP_CONTENT) { resultSize = size; resultMode = MeasureSpec.AT_MOST; }
break;
case MeasureSpec.AT_MOST:
if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; }
else if (childDimension == LayoutParams.MATCH_PARENT) { resultSize = size; resultMode = MeasureSpec.AT_MOST; }
else if (childDimension == LayoutParams.WRAP_CONTENT) { resultSize = size; resultMode = MeasureSpec.AT_MOST; }
break;
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; }
else if (childDimension == LayoutParams.MATCH_PARENT) { resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; }
else if (childDimension == LayoutParams.WRAP_CONTENT) { resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; }
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}Thus a view’s final size is determined by both its parent’s constraints and its own layout parameters.
Fixing the Problem
Because ListViewForScrollView forces a massive height (≈ 536 870 911), the ListView occupies the entire dialog space, leaving no room for scrolling. Replacing the custom view with a regular ListView restores normal scrolling behavior, and the dialog displays correctly.
Key takeaways:
Understand the view drawing pipeline (measure, draw, layout).
Know how MeasureSpec influences view size.
Fully grasp the behavior of custom views before using them.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
