react-native icon indicating copy to clipboard operation
react-native copied to clipboard

fix: TextInput line height issue in Android

Open BeeMargarida opened this issue 2 years ago • 4 comments

Summary:

There are two issues related to line height in Android:

  1. the height of the input changes when typing, since it seems the line height is not taken into consideration in the measurements
  2. 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

Screenshot 2023-06-21 at 14 57 56

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.

BeeMargarida avatar Jun 21 '23 14:06 BeeMargarida

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

analysis-bot avatar Jun 21 '23 15:06 analysis-bot

Hi! Any update regarding this? Is there something that should be changed about this solution?

BeeMargarida avatar Aug 08 '23 09:08 BeeMargarida

@cortinico can you please take a look into this? Thank you!

efstathiosntonas avatar Dec 06 '23 13:12 efstathiosntonas

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

fabOnReact avatar Feb 16 '24 07:02 fabOnReact

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.

react-native-bot avatar Aug 15 '24 05:08 react-native-bot

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.

react-native-bot avatar Aug 15 '24 05:08 react-native-bot