Taha
Taha

Reputation: 651

How to adjust font size to fit view in React Native for Android?

How I can make the font size of the text auto change inside a view in react native?

I have text with different lengths, some of which doesn't fit the view so decreased the font size. I have checked the docs and found this but it's for iOS only and I need it for Android.

And does it work with other components like button and touchableopacity?

Upvotes: 53

Views: 91183

Answers (10)

boycott-nestle
boycott-nestle

Reputation: 95

You could probably just save the text into a string and divide a number that works for you (e.g. 2000) through the string's number of characters (length) + a second number to not get incredible large texts:

fontSize = 2000 / (string.length() + 50)

So for a length of 150 characters you would have a fontSize of 10, for 50 characters it would be a fontSize of 20. You have to try out the right proportions.

Upvotes: 0

Akash Golui
Akash Golui

Reputation: 531

Without and headache you can use a small library which can maintain the font scale for each device.

npm i --save react-native-responsive-fontsize

### import RF from "react-native-responsive-fontsize"

### fontSize: RF(2)

But don't forget to add allowFontScaling={false} props to text. Because ios has auto fon scaling ability so font size could be different from your expectations sometimes.

Upvotes: 0

I faced an even more challenging task recently. The task was to fit multiple words with different lengths (eg: Both Plane and Truck has the same character count, but Truck is longer that Plane when written in the same font size.) in Views (one word for each view) of the same size. I used the React Native Animated library to achieve this. Input words -> Plane, Truck, Bus, Car, Cab

  1. Rendered all the given words using the Text component with a fixed, preferably larger font size that exceeds the required width for the longest word.
  2. Found the length of the longest word using onLayout function of Text Component.
  3. Calculated the scale factor (would be less than 1 due to the assumption in step 1.) along the X-axis needed to fit the longest word within the View.
  4. Then rendered each word inside an Animated.Text component wrapped with an Animated.View component which has the fixed length calculated in Step 2.
  5. Transformed the ScaleX property of all animated components above from 1 down to scale factor.
  6. Transformed the ScaleY property of all Animated.Text components from 1 to scale factor. (duration of all animations are set to 0 milliseconds)
  7. Use react state management and/or CSS styles to hide all Text components, which were positioned absolutely.

I implemented the above idea since continuously decreasing font size until the word fits the View using onLayout was visible in the screen to the user, no matter which trick I used to hide it during the running time of the algorithm. Here is a basic implementation, that I have used to develop more robust codes.

import React, {useCallback, useEffect, useRef, useState} from 'react';
import {Animated, View} from 'react-native';

const ScaleText = props => {
  const {word, wordLength, fontWeight} = props;
  const [calculatedLength, setCalculatedLength] = useState(0);
  const [animationEnded, setAnimationEnded] = useState(false);
  const scaleViewAnimation = useRef(new Animated.Value(0)).current;

  const scalingAnimation = useCallback(() => {
    Animated.timing(scaleViewAnimation, {
      toValue: wordLength / calculatedLength,
      duration: 0,
    }).start(() => {
      setAnimationEnded(true);
    });
  }, [calculatedLength, scaleViewAnimation, wordLength]);

  useEffect(() => {
    if (calculatedLength && !animationEnded) {
      scalingAnimation();
    }
  }, [animationEnded, calculatedLength, scaleViewAnimation, scalingAnimation]);

  return (
    <View style={{width: wordLength, justifyContent: 'center', flex: 1}}>
      <Animated.Text
        onLayout={event => {
          let {width} = event.nativeEvent.layout;
          setCalculatedLength(width);
        }}
        numberOfLines={1}
        adjustsFontSizeToFit={true}
        style={[
          {...props},
          {
            fontWeight: fontWeight || 'normal',
            fontSize: Math.floor((30 * 4 * wordLength) / (110 * word.length)),
            transform: [{scaleX: scaleViewAnimation}],
            textAlign: 'center',
            position: 'absolute',
          },
        ]}>
        {word}
      </Animated.Text>
    </View>
  );
};



export default function App() {
  return (
    <View style={{width: '100%', height: '100%', flexDirection: 'row'}}>
      {['Chamoda', 'Me', 'Nanayakkara'].map((e, i) => (
        <View style={{flex: 1, aspectRatio: 1}} key={i}>
          <ScaleText fontWeight={'bold'} word={e} wordLength={80} />
        </View>
      ))}
    </View>
  );
}

Upvotes: 1

Yurii H
Yurii H

Reputation: 337

A bit improved Jeffy Lee code worked for me

const AdjustLabel = ({
  fontSize, text, style, numberOfLines
}) => {
  const [currentFont, setCurrentFont] = useState(fontSize);

  return (
    <Text
      numberOfLines={ numberOfLines }
      adjustsFontSizeToFit
      style={ [style, { fontSize: currentFont }] }
      onTextLayout={ (e) => {
        const { lines } = e.nativeEvent;
        if (lines.length > numberOfLines) {
          setCurrentFont(currentFont - 1);
        }
      } }
    >
      { text }
    </Text>
  );
};

Upvotes: 30

Thadeus Ajayi
Thadeus Ajayi

Reputation: 1283

Combine these two props, works on Android and iOS:

adjustsFontSizeToFit={true}
numberOfLines={1}

adjustsFontSizeToFit ensures the font doesn't exceed container, numberOfLines makes sure the text stays in one line.

numberOfLines without adjustsFontSizeToFit would make an ellipsis, adjustsFontSizeToFit without numberOfLines would reduce the height of size of the text to fit into the containing view, in the case where the containing view has a fixed height or width.

Upvotes: 61

ppahka
ppahka

Reputation: 11

Text component's adjustsFontSizeToFit prop has been supported for Android since React Native 0.62. Check the docs.

Upvotes: 1

Ronak Online
Ronak Online

Reputation: 108

Quick answer use Text property adjustsFontSizeToFit

Upvotes: 0

Jeffy Lee
Jeffy Lee

Reputation: 87

Try this method.

I used the method onLayout to achieve this.

First, I wrapped my Text view inside a fixed size View, then I implement onLayout for both view, so that I can get the height of the fixed size View and the height of the Text view.

Next, if the Text view's required height is more than the height of our View, we decrease the font size of our Text view until the height can fit into our View.

Herein the test code, I made 2 Views, one in pink and one in yellow background, the font size of Text inside the pink View will be adjust to fit the View and the yellow Text will remain the same font size.

import React, { useState, useEffect } from 'react';
import { StyleSheet, Text, View } from 'react-native';

export default function App() {
  const [size, setSize] = useState(20)
  const [viewHeight, setViewHeight] = useState(0)
  const [textHeight, setTextHeight] = useState(0)

  useEffect(() => {
    if (textHeight > viewHeight) {
      setSize(size - 1) // <<< You may adjust value 1 to a smaller value so the text can be shrink more precisely
    }
  }, [textHeight])

  return (
    <View style={styles.container}>
      <View
        style={{
          margin: 20,
          backgroundColor: 'pink',
          width: 200,
          height: 200
        }}
        onLayout={(event) => {
          var { x, y, width, height } = event.nativeEvent.layout;
          setViewHeight(height)
        }}
      >
        <Text
          style={{
            fontSize: size,
          }}
          onLayout={(event) => {
            var { x, y, width, height } = event.nativeEvent.layout;
            setTextHeight(height)
          }}
        >
          Gemma is a middle grade novel that follows a curious explorer and her ring-tailed lemur, Milo, 
          as they hunt for the “most greatest treasure in the world”. Solving riddles, battling a bell-wearing 
          jaguar, and traveling the Eight Seas, Gemma’s adventures take her from a young girl to a brave captain, 
          whose only limits are the stars.
        </Text>
      </View>

      <View
        style={{
          margin: 20,
          backgroundColor: 'yellow',
          width: 200,
          height: 200
        }}
      >
        <Text
          style={{
            fontSize: 20
          }}
        >
          Gemma is a middle grade novel that follows a curious explorer and her ring-tailed lemur, Milo, 
          as they hunt for the “most greatest treasure in the world”. Solving riddles, battling a bell-wearing 
          jaguar, and traveling the Eight Seas, Gemma’s adventures take her from a young girl to a brave captain, 
          whose only limits are the stars.
        </Text>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

You may notice the pink Text's font is having a shrinking animation when the font size is decreasing, I suggest that you can initial the Text opacity with 0 and after the font adjustment finish set it to 1 so user wont see the ugly animation.

Upvotes: 1

dhahn
dhahn

Reputation: 787

I had a similar issue with the number of text resizing the views (user inputs string into an input field currently limited to 30 characters) in my React Native app. I noticed there are a few properties already implemented for iOS: adjustsFontSizeToFit minimumFontScale={.4} however the Android does not have such methods available at this time.

Note: Solution which works for me but might not be the "best" possible!

Currently, I'm using a nested ternary operator:

fontSize: this.props.driverName.length > 12 && 
this.props.driverName.length < 20 ? heightPercentageToDP('2%') : 
this.props.driverName.length > 20 && this.props.driverName.length < 40 
? heightPercentageToDP('1.75%') : heightPercentageToDP('2.25%')

In layman's terms, the above ternary checks the range of characters used and based off the range modifies the fontSize into a '%' inside the view. The method: heightPercentageToDP is a function which I am using to convert the size into a '%' based on the mobile screen's DP (this way it is responsive on a tablet or a mobile phone).

heightPercentageToDP Code Snippet:

    import { Dimensions, PixelRatio } from "react-native";
    const widthPercentageToDP = widthPercent => {
    const screenWidth = Dimensions.get("window").width;
    // Convert string input to decimal number
    const elemWidth = parseFloat(widthPercent);
    return PixelRatio.roundToNearestPixel((screenWidth * elemWidth) / 100);
    };
    const heightPercentageToDP = heightPercent => {
    const screenHeight = Dimensions.get("window").height;
    // Convert string input to decimal number
    const elemHeight = parseFloat(heightPercent);
    return PixelRatio.roundToNearestPixel((screenHeight * elemHeight) / 100);
    };
    export { widthPercentageToDP, heightPercentageToDP };

Credit and source for heightPercentageToDP goes to: https://medium.com/react-native-training/build-responsive-react-native-views-for-any-device-and-support-orientation-change-1c8beba5bc23.

Upvotes: 0

Allloush
Allloush

Reputation: 1168

Try this approach. Consider setting the font size according to screen width as following

width: Dimensions.get('window').width

So in your style try to make a percentage calculation to make the font size fit the screen

style={
  text:{
     fontSize:0.05*width
  }
}

this will gonna work for all screens (both Android and IOS)

Upvotes: 4

Related Questions