Open kyze8439690 opened 2 years ago
Why CoordinatorLayout will be very small? Because I am using Activity shared element transtion. When playing sharedElementReturnTransition, ActivityTransitionCoordinator
will use the final size to measure current layout, in some scenario, final size will be very small, which make CoordinatorLayout's measure by a small size spec
Create a new ScrollingViewBehavior
and seems can be used to solve the issue above:
class ScrollingViewBehavior(context: Context, attrs: AttributeSet):
AppBarLayout.ScrollingViewBehavior(context, attrs) {
@SuppressLint("RestrictedApi")
override fun onMeasureChild(
parent: CoordinatorLayout, child: View, parentWidthMeasureSpec: Int,
widthUsed: Int, parentHeightMeasureSpec: Int, heightUsed: Int
): Boolean {
val childLpHeight = child.layoutParams.height
if (childLpHeight == ViewGroup.LayoutParams.MATCH_PARENT
|| childLpHeight == ViewGroup.LayoutParams.WRAP_CONTENT
) {
// If the menu's height is set to match_parent/wrap_content then measure it
// with the maximum visible height
val dependencies = parent.getDependencies(child)
val header: View? = dependencies.find { it is AppBarLayout }
if (header != null) {
var availableHeight = View.MeasureSpec.getSize(parentHeightMeasureSpec)
if (availableHeight > 0) {
if (ViewCompat.getFitsSystemWindows(header)) {
val parentInsets = parent.lastWindowInsets
if (parentInsets != null) {
availableHeight += (parentInsets.systemWindowInsetTop
+ parentInsets.systemWindowInsetBottom)
}
}
} else {
// If the measure spec doesn't specify a size, use the current height
availableHeight = parent.height
}
val scrollRange = if (header is AppBarLayout) {
header.totalScrollRange
} else {
header.measuredHeight
}
var height = availableHeight + scrollRange
val headerHeight = header.measuredHeight
if (shouldHeaderOverlapScrollingChild()) {
child.translationY = -headerHeight.toFloat()
} else {
height -= headerHeight
}
if (height < 0) height = availableHeight // add this line to solve measure overflow issue
val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(
height,
if (childLpHeight == ViewGroup.LayoutParams.MATCH_PARENT) View.MeasureSpec.EXACTLY else View.MeasureSpec.AT_MOST
)
// Now measure the scrolling view with the correct height
parent.onMeasureChild(
child, parentWidthMeasureSpec, widthUsed, heightMeasureSpec, heightUsed
)
return true
}
}
return false
}
}
Description: When CoordinatorLayout's height is very small, for example
86dp
, andheaderHeight
in link is larger than CoordinatorLayout's height(like90dp
), it will makeheight
in link a negative value(90dp - 86dp = -4dp).heightMeasureSpec
in link will be overflow and make second child in CoordinatorLayout measure incorrectly.If seond child view is a RecyclerView or ListView with many item in it, it will make RecyclerView onMeasure spends lots of time. Because measureSpec overflow will provide a huge height measure size, and make RecylerView layout all item in it's adapter.
Expected behavior:
height
should not be negative, orheightMeasureSpec
's size should not be overflow Source code: https://github.com/material-components/material-components-android/blob/master/lib/java/com/google/android/material/appbar/HeaderScrollingViewBehavior.java#L89Android API version: Android 11
Material Library version: 1.5.0
Device: Xiaomi Civi
To help us triage faster, please check to make sure you are using the latest version of the library.
We also happily accept pull requests.