Repository navigation
Fix nested Text decorations disappearing from TextInput on Android - #59201
Open
jkasprzyk17 wants to merge 1 commit into
Open
jkasprzyk17 wants to merge 1 commit into
jkasprzyk17 wants to merge 1 commit into
Conversation
ReactEditText draws the underline or line-through of a nested Text as CanvasEffectSpans around super.onDraw, using getLayout(). Changing the placeholder makes TextView drop its layout and wait for the next measure. Fabric measures a TextInput only when its frame changes, so the layout stays null. onDraw then fell back to super.onDraw, which rebuilds the layout and draws the text, but skipped the decorations. When the input has CanvasEffectSpans and its layout is missing at draw time, measure the view again with its current frame before drawing. TextView#onDraw would build the same layout anyway.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary:
On Android, the underline or line-through of a
<Text>nested in a<TextInput>disappears when the input'splaceholderchanges. The text is still drawn. The decoration comes back only when the input is drawn again for another reason, because by thenTextViewhas rebuilt its layout. It has the same cause as #58999, which reported it for<Text>. #58847 fixes<Text>the same way, and this PR does it for<TextInput>.A decoration set on the
TextInputitself isn't affected:ReactTextInputManagerapplies it asPaint.UNDERLINE_TEXT_FLAGorPaint.STRIKE_THRU_TEXT_FLAG, whichLayout.drawpaints. A nested<Text>gets aReactUnderlineSpanorReactStrikethroughSpaninstead. Those areCanvasEffectSpans, whichReactEditText.onDrawpaints before and aftersuper.onDrawusinggetLayout(). WhengetLayout()returns null,onDrawfalls back to a plainsuper.onDrawand skips them.The layout is null at draw time after a placeholder change:
ReactEditText.setPlaceholdersetshint.TextView#setHintcallscheckForRelayout(), which can't rebuild the layout in place here. It callsnullLayouts()andrequestLayout()instead.updateLayoutand doesn't measure the view.requestLayout()stops at the parentReactViewGroup.TextView#onDrawrebuilds it inassumeLayout(), which is afterReactEditTexthas skipped the spans.With this change,
ReactEditText.onDrawmeasures the view again with its current frame when it hasCanvasEffectSpans and the layout is missing, before it draws the spans. These are the same specs Fabric measures the view with inupdateLayout, andReactEditTextalready re-measures at the current bounds the same way whenmultilineis turned off.TextView#onDrawwould build the same layout anyway, so this adds no layout work. Inputs withoutCanvasEffectSpans keep the current draw path.I confirmed the cause with temporary logging in
ReactEditText.onDraw, which is not part of this change. After a placeholder change,getLayout()was null inonDrawfor the inputs whose nested decorations disappeared. Inserting a sibling after the inputs (the trigger in #58999) and changingfontSizeon a fixed-size input both left the layout in place, so they don't reproduce it for<TextInput>.Changelog:
[ANDROID] [FIXED] - Fix the underline and line-through of a
Textnested in aTextInputdisappearing when the placeholder changesTest Plan:
A new Robolectric test,
ReactEditTextTest.drawsCanvasEffectSpansAfterPlaceholderChangeThatKeepsTheFrame, lays out aReactEditTextwhose text has aCanvasEffectSpan, changes its placeholder without measuring it again, and draws it. It checks that the span gotonPreDrawandonDraw. The test fails without this change and passes with it.I also reproduced it in RNTester: Debug build, new architecture, Pixel 8 emulator with Android 15 (API 35).
RNTesterPlayground.js
Steps: tap PLACEHOLDER. Before this change, the nested underline in
aand the nested line-through inbdisappear.c, whose underline comes from its own style, keeps it, and so doesd, which has no placeholder. With this change, both nested decorations stay after each placeholder change. I also focusedaand typed: the text and caret behave as before, and the decorations stay after another placeholder change.Not run: iOS and Fantom tests (this is an Android-only Kotlin change), and JS lint or Flow (no JS changes).
Not changed: in the screenshots the nested decorations sit slightly too high in a
TextInput, becauseonDrawtranslates the spans by the padding only, without the vertical gravity offset. That happens with or without this change. #56767 handles the same offset for<Text>, and I left<TextInput>out of this PR.