最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to create custom animated floating label TextInput in React Native? - Stack Overflow

programmeradmin1浏览0评论

I created a TextInput in React Native,

But I want the label go outside the box with animation when isFocused or filled with some value like:

If value is null or empty the label must be inside the input, otherwise the label must move outside the input with animation.

My Code:

export default function InputBox(props) {
  return (
    <View style={styles.container}>
      <Text style={styles.label}>{props.label}</Text>
      <TextInput
        style={styles.input}
        autoCapitalize="none"
        defaultValue={props.defaultValue}
        onChangeText={props.onChangeText}
        keyboardType={props.keyboardType}
        editable={props.editable}
      />
    </View>
  );
}

Style:

const styles = StyleSheet.create({
  container: {
    marginBottom: 20,
    backgroundColor: COLORS.WHITE,
    paddingTop: 5,
    paddingHorizontal: 10,
    borderWidth: 1,
    borderColor: COLORS.GREY_BORDER,
    borderRadius: 2,
  },
  icon: {
    width: 40,
    justifyContent: 'center',
    alignItems: 'center',
  },
  input: {
    fontFamily: FONT_FAMILY.primaryMedium,
    fontSize: 13,
    height: 35,
    color: COLORS.BLACK,
  },
  label: {
    fontFamily: FONT_FAMILY.primary,
    color: COLORS.GREY,
    fontSize: 10,
  },
});

I created a TextInput in React Native,

But I want the label go outside the box with animation when isFocused or filled with some value like:

If value is null or empty the label must be inside the input, otherwise the label must move outside the input with animation.

My Code:

export default function InputBox(props) {
  return (
    <View style={styles.container}>
      <Text style={styles.label}>{props.label}</Text>
      <TextInput
        style={styles.input}
        autoCapitalize="none"
        defaultValue={props.defaultValue}
        onChangeText={props.onChangeText}
        keyboardType={props.keyboardType}
        editable={props.editable}
      />
    </View>
  );
}

Style:

const styles = StyleSheet.create({
  container: {
    marginBottom: 20,
    backgroundColor: COLORS.WHITE,
    paddingTop: 5,
    paddingHorizontal: 10,
    borderWidth: 1,
    borderColor: COLORS.GREY_BORDER,
    borderRadius: 2,
  },
  icon: {
    width: 40,
    justifyContent: 'center',
    alignItems: 'center',
  },
  input: {
    fontFamily: FONT_FAMILY.primaryMedium,
    fontSize: 13,
    height: 35,
    color: COLORS.BLACK,
  },
  label: {
    fontFamily: FONT_FAMILY.primary,
    color: COLORS.GREY,
    fontSize: 10,
  },
});
Share Improve this question edited Jul 25, 2022 at 16:02 Chance Snow 2,7382 gold badges19 silver badges20 bronze badges asked Aug 11, 2021 at 6:36 Juhi KukrejaJuhi Kukreja 1871 gold badge5 silver badges14 bronze badges 1
  • codesandbox.io/s/gifted-mcnulty-80z7z – gu mingfeng Commented Aug 11, 2021 at 8:40
Add a ment  | 

3 Answers 3

Reset to default 3

If you want to make your own animation you can wrap that label Text into a Animated.View ponent and give animation.

Try this:

import React, { useEffect, useState, useRef } from "react";
import {
  View,
  StyleSheet,
  TextInput,
  Text,
  Animated,
  Pressable,
} from "react-native";

const TestScreen = () => {
  const [value, setValue] = useState("");
  const moveText = useRef(new Animated.Value(0)).current;

  useEffect(() => {
    if (value !== "") {
        moveTextTop();
    } else if (value === "") {
        moveTextBottom();
    }
  }, [value])

  const onChangeText = (text: string) => {
    setValue(text);
  };

  const onFocusHandler = () => {
    if (value !== "") {
      moveTextTop();
    }
  };

  const onBlurHandler = () => {
    if (value === "") {
      moveTextBottom();
    }
  };

  const moveTextTop = () => {
    Animated.timing(moveText, {
      toValue: 1,
      duration: 200,
      useNativeDriver: true,
    }).start();
  };

  const moveTextBottom = () => {
    Animated.timing(moveText, {
      toValue: 0,
      duration: 200,
      useNativeDriver: true,
    }).start();
  };

  const yVal = moveText.interpolate({
    inputRange: [0, 1],
    outputRange: [4, -20],
  });

  const animStyle = {
    transform: [
      {
        translateY: yVal,
      },
    ],
  };

  return (
    <View style={styles.container}>
      <Animated.View style={[styles.animatedStyle, animStyle]}>
        <Text style={styles.label}>Enter Your Name</Text>
      </Animated.View>
      <TextInput
        autoCapitalize={"none"}
        style={styles.input}
        value={value}
        onChangeText={(text: string) => onChangeText(text)}
        editable={true}
        onFocus={onFocusHandler}
        onBlur={onBlurHandler}
        blurOnSubmit
      />
    </View>
  );
};
export default TestScreen;

const styles = StyleSheet.create({
  container: {
    marginBottom: 20,
    marginTop: 20,
    backgroundColor: "#fff",
    paddingTop: 5,
    paddingHorizontal: 10,
    borderWidth: 1,
    borderColor: "#bdbdbd",
    borderRadius: 2,
    width: "90%",
    alignSelf: "center",
  },
  icon: {
    width: 40,
    justifyContent: "center",
    alignItems: "center",
  },
  input: {
    fontSize: 13,
    height: 35,
    color: "#000",
  },
  label: {
    color: "grey",
    fontSize: 10,
  },
  animatedStyle: {
    top: 5,
    left: 15,
    position: 'absolute',
    borderRadius: 90,
    zIndex: 10000,
  },
});

The Answer is good but you might not need useEffect in this case, this can be refactored as this

import React, {useState, useRef} from 'react';
import {View, StyleSheet, TextInput, Text, Animated} from 'react-native';

const TestScreen = () => {
  const [value, setValue] = useState('');
  const moveText = useRef(new Animated.Value(0)).current;

  const onFocusHandler = () => {
    if (!value) {
      moveTextTop();
    }
  };
  const onBlur = () => {
    if (!value) {
      moveTextBottom();
    }
  };
  const onChangeText = (textValue: string) => {
    setValue(textValue);
    if (textValue !== '') {
      moveTextTop();
    } else if (value === '') {
      moveTextBottom();
    }
  };

  const moveTextTop = () => {
    Animated.timing(moveText, {
      toValue: 1,
      duration: 200,
      useNativeDriver: true,
    }).start();
  };

  const moveTextBottom = () => {
    Animated.timing(moveText, {
      toValue: 0,
      duration: 200,
      useNativeDriver: true,
    }).start();
  };

  const yVal = moveText.interpolate({
    inputRange: [0, 1],
    outputRange: [4, -20],
  });

  const animStyle = {
    transform: [{translateY: yVal}],
  };

  return (
    <View style={styles.container}>
      <Animated.View style={[styles.animatedStyle, animStyle]}>
        <Text style={styles.label}>Enter Your Name</Text>
      </Animated.View>
      <TextInput
        value={value}
        onBlur={onBlur}
        style={styles.input}
        onFocus={onFocusHandler}
        onChangeText={onChangeText}
      />
    </View>
  );
};
export default TestScreen;

const styles = StyleSheet.create({
  container: {
    marginBottom: 20,
    marginTop: 20,
    backgroundColor: '#fff',
    paddingTop: 5,
    paddingHorizontal: 10,
    borderWidth: 1,
    borderColor: '#bdbdbd',
    borderRadius: 2,
    width: '90%',
    alignSelf: 'center',
  },
  icon: {
    width: 40,
    justifyContent: 'center',
    alignItems: 'center',
  },
  input: {
    fontSize: 13,
    height: 35,
    color: '#000',
  },
  label: {
    color: 'grey',
    fontSize: 10,
  },
  animatedStyle: {
    top: 5,
    left: 15,
    position: 'absolute',
    borderRadius: 90,
    zIndex: 10000,
  },
});

import React, {useRef, useState} from 'react';
import {Animated, ScrollView, StyleSheet, TextInput, View} from 'react-native';

export default function AnimatedTextInput() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [address, setAddress] = useState('');
  return (
    <ScrollView contentContainerStyle={styles.container}>
      <AnimatedInput value={name} onChange={setName} placeholder="Name" />
      <AnimatedInput value={email} onChange={setEmail} placeholder="Email" />
      <AnimatedInput
        value={address}
        onChange={setAddress}
        placeholder="Address"
        multiline
      />
    </ScrollView>
  );
}

function AnimatedInput({value, onChange, placeholder, multiline}) {
  const [inputHeight, setHeight] = useState(null);
  const [placeholderWidth, setWidth] = useState(null);
  const animation = useRef(new Animated.Value(0)).current;
  const translateY = animation.interpolate({
    inputRange: [0, 1],
    outputRange: [0, -inputHeight / 2],
  });
  const translateX = animation.interpolate({
    inputRange: [0, 1],
    outputRange: [0, -placeholderWidth / 4],
  });
  const scale = animation.interpolate({
    inputRange: [0, 1],
    outputRange: [1, 0.5],
  });
  const onFocus = () => animate(1);
  const onBlur = () => !value && animate(0);
  const animate = val => {
    Animated.spring(animation, {
      toValue: val,
      bounciness: 0,
      useNativeDriver: true,
    }).start();
  };
  return (
    <View
      style={styles.inputContainer}
      onLayout={e => !inputHeight && setHeight(e.nativeEvent.layout.height)}>
      <View style={{height: inputHeight, ...styles.placeholderContainer}}>
        <Animated.Text
          style={[
            styles.placeholder,
            {transform: [{translateY}, {translateX}, {scale}]},
          ]}
          onTextLayout={e =>
            !placeholderWidth && setWidth(e.nativeEvent.lines[0]?.width || 0)
          }>
          {placeholder}
        </Animated.Text>
      </View>
      <TextInput
        style={[
          styles.input,
          multiline && {height: 100, textAlignVertical: 'top'},
        ]}
        onFocus={onFocus}
        onBlur={onBlur}
        onChangeText={onChange}
        multiline={multiline}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    padding: 20,
  },
  inputContainer: {
    borderWidth: 1,
    borderRadius: 5,
    borderColor: '#999',
    marginBottom: 25,
  },
  input: {
    paddingHorizontal: 10,
    fontSize: 18,
  },
  placeholderContainer: {
    position: 'absolute',
    backgroundColor: 'red',
    justifyContent: 'center',
  },
  placeholder: {
    fontSize: 22,
    position: 'absolute',
    marginHorizontal: 5,
    paddingHorizontal: 5,
    backgroundColor: '#fff',
    color: '#999',
  },
});
发布评论

评论列表(0)

  1. 暂无评论