自定义View中ScrollView二次设置Padding后视图未更新问题咨询
Hey there! Let's break down what's happening here and how to fix it.
First, let's unpack the likely causes of your issue:
- Layout Loop Trigger: When you update the
Guidelinepositions andScrollViewpadding inside theOnLayoutChangeListener, you're triggering another layout pass. This can causeFrameLayoutAto re-measure and re-layout, firing the listener again—creating a cycle that leads to two calls. - Dynamic Content Height Changes: If
FrameLayoutAcontains content that loads asynchronously (like images or fetched data), its height will change twice: once with the initial placeholder size, then again when the actual content finishes loading. - Delayed View Update: The second time you set the padding, the
ScrollViewmight not mark itself as needing a re-layout immediately, leading to that annoying delay before the view updates.
Here are a few actionable fixes to resolve this:
Fix 1: Guard Against Unnecessary Updates & Force Layout
Add checks to only modify values when they actually change, and explicitly request a layout for the ScrollView to ensure immediate updates:
framelayoutA.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ -> val newHeight = v.height // Exit early if height hasn't changed if (newHeight == framelayoutAHeight) return@addOnLayoutChangeListener framelayoutAHeight = newHeight framelayoutAGuideline.setGuidelineBegin(framelayoutAHeight) framelayoutBGuideline.setGuidelineBegin(framelayoutAHeight) val newPadding = framelayoutAHeight + framelayoutBHeight // Only update padding if it's different from current value if (scrollView.paddingTop != newPadding) { Log.d("ParallaxScrollingView", "framelayoutAHeight = $framelayoutAHeight") Log.d("ParallaxScrollingView", "padding = $newPadding") scrollView.setPadding(0, newPadding, 0, 0) // Force the ScrollView to re-layout immediately scrollView.requestLayout() } }
Fix 2: Avoid Layout Cycles with Global Layout Listener
If the issue stems from repeated layout triggers, use a ViewTreeObserver.OnGlobalLayoutListener to run your logic after the entire view tree has finished laying out—this avoids intermediate layout passes firing your listener multiple times. Just remember to remove the listener after it runs (unless you need to handle ongoing dynamic content updates):
// Get the view tree observer from your custom view or its root parent val viewTreeObserver = yourCustomView.viewTreeObserver viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { override fun onGlobalLayout() { // Remove the listener to prevent repeated calls (skip this if you need dynamic updates) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { viewTreeObserver.removeOnGlobalLayoutListener(this) } else { viewTreeObserver.removeGlobalOnLayoutListener(this) } // Get final heights and update your UI elements framelayoutAHeight = framelayoutA.height framelayoutBHeight = framelayoutB.height framelayoutAGuideline.setGuidelineBegin(framelayoutAHeight) framelayoutBGuideline.setGuidelineBegin(framelayoutAHeight) val padding = framelayoutAHeight + framelayoutBHeight scrollView.setPadding(0, padding, 0, 0) } })
Fix 3: Handle Dynamic Content Explicitly
If FrameLayoutA's height changes due to async content (like images), consider listening directly to that content's load event instead of relying on layout changes. For example, if you're using an ImageView, set a listener for when the image loads, then update your heights and padding once that's done—this avoids multiple layout triggers from intermediate placeholder states.
内容的提问来源于stack exchange,提问作者Nicolai Hansen

