fix: TextInput line height issue in Android
Summary:
There are two issues related to line height in Android:
- the height of the input changes when typing, since it seems the line height is not taken into consideration in the measurements
- the cursor height of an empty input is smaller than when the input has text
See extensive discussion of these problems in Expensify issue.
Videos of the problematic behaviour
1st problem:
| Old Arch | New Arch |
|---|---|
2nd problem:
https://github.com/facebook/react-native/assets/25725586/d7c67548-d409-4c77-91a4-df5c02a412ca
Reasoning for the fix
1st Problem
- Old Arch
One interesting thing that updateCachedSpannable does in New Arch is 'fake' text content if there is none, so that there is something to measure. This is not done for the Old Arch, which in itself is the root of the problem. The fix was modifying the ReactTextInputShadowNode.java->measure method to fake some text on the text input if lineHeight is set so that it can be used to measure correctly.
- New Arch
Although for New Arch it's taking into consideration the faking of the text, the lineHeight is not being passed down to the TextAttributes, so it was necessary to add a setter setLineHeight to both ReactTextInputManager.java and ReactEditText.java, which set the line height value in it's textAttributes instance. With this, the line height is correctly applied to the spans when updating the spannables.
2nd Problem
Since the cursor size seems to be tied to the textSize of the EditText when empty, the solution taken was setting the textSize to be equal to the line height (taking into consideration that it's not 1-1 and the cursor will be smaller, so there's a coeficient that is used based on the font metrics).
With this change, the font size of the spannables must not be stripped, and the font size and line height must be applied to the placeholder.
Videos after the fix
https://github.com/facebook/react-native/assets/25725586/780b50fd-6f2b-4140-a1f6-d8a8aca4082a
Changelog:
[ANDROID] [FIXED] - Cursor and text input adapt to line height when empty
Test Plan:
Run rn-tester in Android and check the Text Input -> Line Height example and other TextInput examples to see that no behaviour changed.
| Platform | Engine | Arch | Size (bytes) | Diff |
|---|---|---|---|---|
| android | hermes | arm64-v8a | 8,884,315 | +961 |
| android | hermes | armeabi-v7a | 7,935,113 | +960 |
| android | hermes | x86 | 9,281,391 | +968 |
| android | hermes | x86_64 | 9,184,499 | +955 |
| android | jsc | arm64-v8a | 9,473,132 | +69 |
| android | jsc | armeabi-v7a | 8,416,494 | +76 |
| android | jsc | x86 | 9,455,884 | +70 |
| android | jsc | x86_64 | 9,772,092 | +69 |
Base commit: e37e53086af5c0569830efa607fc42c2b9593be1 Branch: main
Hi! Any update regarding this? Is there something that should be changed about this solution?
@cortinico can you please take a look into this? Thank you!
Sorry. I forgot to post this test case. I was able to reproduce the same cursor flickering issue on iOS (react-native with head 1706066533f).
function TextInputExample() {
const [value, setValue] = React.useState();
const [lineHeight, setLineHeight] = React.useState(50);
const increaseLineHeight = () => {
setLineHeight(prevLineHeight => prevLineHeight + 10);
};
const decraseLineHeight = () => {
setLineHeight(prevLineHeight => prevLineHeight - 10);
};
console.log('lineHeight', lineHeight);
return (
<>
{/*
<Text style={{lineHeight: 500, fontSize: 15, backgroundColor: 'red'}}>
font size 15
<Text style={{fontSize: 50, lineHeight: 500}}>font size 50</Text>
</Text>
*/}
<TextInput
multiline
style={{
backgroundColor: 'red',
height: 50,
}}
onChangeText={text => setValue(text)}>
<Text style={{lineHeight}}>{value}</Text>
</TextInput>
<Button
title="increase line height"
onPress={() => increaseLineHeight()}
/>
<Button
title="decrease line height"
onPress={() => decraseLineHeight()}
/>
</>
);
}
https://user-images.githubusercontent.com/24992535/253580394-0d4a8c94-71ed-4523-9146-2e0872531491.mp4
I was able to reproduce the issue today on head f322dc7a84eb72370910f6933d0a4fa7780f49bc.
https://github.com/facebook/react-native/assets/24992535/43de41e2-1fb4-4506-82c2-852ef066538c
This PR is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.
This PR is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.