react-nativereact-animations

Animation like slack comment box react-native


I am working on a comment box which I am expanding to the height of device on swipe up action and back to its original height on swipe down action. But I am not able to add the animation to it as the function does not work the way i wanted it to be. For the reference we can talk about the slack comment box animation.

Here is what my code looks like :

code :

Snack link : https://snack.expo.dev/@sidd0328/animate-comment-box

const deviceHeight = Dimensions.get("window").height;
  const height = useRef(new Animated.Value(1)).current;
  const videoPlaceholderSize = 80;
  const parentContainerExpandedHeight = deviceHeight * 0.85 - keyboardHeight;
  const childContainerExpandedHeight = deviceHeight * 0.82 - keyboardHeight;
  const textContainerExpandedHeight = deviceHeight * 0.75 - keyboardHeight;

 useEffect(() => {
    Animated.timing(height, {
      toValue: isExpanded ? parentContainerExpandedHeight : 0,
      duration: 400,
      useNativeDriver: false,
    }).start();
  }, [isExpanded]);

return (
    <GestureRecognizer
      onSwipeUp={() => setIsExpanded(true)}
      onSwipeDown={() => setIsExpanded(false)}
    >
      <Animated.View
        style={[
          getStyle.parentContainer,
          isExpanded && {
            height: height,
          },
        ]}
      >
        <View style={getStyle.handlebar} />
        <View
          style={[getStyle.childContainer, isExpanded && { height: childContainerExpandedHeight }]}
        >
          <View
            style={[
              getStyle.textImageWrapper,
              isExpanded && { height: textContainerExpandedHeight },
            ]}
          >
            <ScrollView showsVerticalScrollIndicator={false}>
              <>
                <TextInput
                  ref={inputRef}
                  value={inputValue}
                  style={getStyle.input}
                  placeholder={placeholder || i18n.t("community.writeComment")}
                  placeholderTextColor="gray"
                  multiline
                  textAlignVertical="top"
                  onChangeText={handleChangeText}
                  maxLength={maxLength || 500}
                />
              </>
            </ScrollView>
            {Object.keys(selectedImageFile).length ? (
              isVideoByType(selectedImageFile.type) && selectedImage.length > 0 ? (
                <View style={getStyle.videoPlaceholder}>
                  <VideoPlaceholder
                    variant="nonPressable"
                    height={videoPlaceholderSize}
                    width={videoPlaceholderSize}
                  />
                  <TouchableOpacity style={getStyle.backImageWrapper} onPress={removeImage}>
                    <VectorImage
                      source={Icons.removeCircle2}
                      width={IconSize.m}
                      height={IconSize.m}
                    />
                  </TouchableOpacity>
                </View>
              ) : selectedImage.length > 0 ? (
                <View style={getStyle.imagePlaceholder}>
                  <ImagesLayout
                    path="CommentBox"
                    images={selectedImage}
                    isRemoveImageIconVisible={true}
                    onRemoveImagePress={removeImage}
                  />
                </View>
              ) : null
            ) : null}
          </View>
          {!isEditing ? (
            <View style={getStyle.container}>
              <TouchableOpacity
                style={[getStyle.submitButton, getStyle.addImageButtonWrapper]}
                onPress={() => setImageSelectionVisible(true)}
              >
                <VectorImage source={Icons.pictureSingle2} width={IconSize.m} height={IconSize.m} />
              </TouchableOpacity>
              <TouchableOpacity
                onPress={inputValue.length ? onPressSubmit : () => {}}
                style={[
                  getStyle.submitButton,
                  {
                    backgroundColor: inputValue.length ? buttonColor.newPrimaryColor : colors.gray3,
                  },
                ]}
                activeOpacity={inputValue.length ? 0.2 : 1}
              >
                <VectorImage
                  source={Icons.send2}
                  style={getStyle.imageColor}
                  width={IconSize.m}
                  height={IconSize.m}
                />
              </TouchableOpacity>
              <ImageSelectionMethod
                isVisible={isImageSelectionVisible}
                isVideo={isVideoCaptureEnabled}
                onClose={() => setImageSelectionVisible(false)}
                onCameraPicker={showCamera}
                onImagePicker={
                  isVideoCaptureEnabled ? showPhotoAndVideoGalleryPicker : showPhotoGalleryPicker
                }
                onVideoPicker={showVideoRecorder}
              />
            </View>
          ) : (
            <View style={getStyle.buttonContainer}>
              <View style={getStyle.buttonWrapper}>
                <Button
                  variant={Variant.SECONDARY_OUTLINED_SMALL}
                  label={i18n.t("community.cancelText")}
                  onPress={cancelEditing}
                />
              </View>
              <Button
                variant={Variant.PRIMARY_SMALL}
                label={i18n.t("contactForms.createContactFormScreen.saveButtonLabel")}
                isEnabled={Boolean(inputValue.length)}
                onPress={onPressSubmit}
              />
            </View>
          )}
        </View>
        <View style={getStyle.bottomHandlebarContainer} />
      </Animated.View>
    </GestureRecognizer>
  );

animation video for above implementation : https://www.dropbox.com/s/ivfaqd397f3jutz/Screen%20Recording%202023-06-28%20at%205.53.38%20PM.mov?dl=0

What I want :

https://www.dropbox.com/s/ylktmw3j1z0ec5d/IMG_5044.mov?dl=0

Any suggestions or help would be greatly appreciated!!!


Solution

  • I checked the snack link shared by you and added the animation in it, here is the snack with expected behavior.

    Explanation

    I have updated some of the styling related to TextInput and its containers and removed the check you added for adding the animated height in style.

              styles.parentContainer,
              isExpanded && {       // removed this
                height: height,
              },
            ]}
    

    I have also removed the useEffect which was increasing/descresing the height of container,instead I added 2 separate functions to do the same thing and called this functions in swipeUp/swipeDown callbacks.

    Also, To get the correct increased height, substract 96 from parentContainerExpandedHeight. Here 96 is the total minimum height of the container of TextInput (total minimum height = vertical padding/margin + height).

      const parentContainerExpandedHeight = deviceHeight - 96;
    

    Code

    Swipe gesture callback function

      const onSwipeUp = () => {
        console.log('swipe up');
        isExpanded.current = true;
        Animated.timing(height, {
          toValue: parentContainerExpandedHeight,
          duration: 400,
          useNativeDriver: false,
        }).start();
      };
    
      const onSwipeDown = () => {
        console.log('swipe down');
        isExpanded.current = false;
        Animated.timing(height, {
          toValue: 0,
          duration: 400,
          useNativeDriver: false,
        }).start();
      };
    
    

    Gesture handler with TextInput

          <GestureRecognizer
            onSwipeUp={state => onSwipeUp()}
            onSwipeDown={state => onSwipeDown()}>
            <View style={[styles.parentContainer]}>
              <View style={styles.handlebar} />
              <Animated.View style={[styles.childContainer, {minHeight: height}]}>
                <View style={[styles.anotherChildContainer]}>
                  <View style={[styles.textImageWrapper]}>
                    <ScrollView showsVerticalScrollIndicator={false}>
                      <>
                        <TextInput
                          value={text}
                          onChangeText={onChangeText}
                          placeholder={'placeholder'}
                          placeholderTextColor="gray"
                          multiline
                          textAlignVertical="top"
                          style={{
                            textAlignVertical: 'top',
                            marginBottom: 10,
                          }}
                          maxLength={500}
                        />
                      </>
                    </ScrollView>
                  </View>
                  <View style={styles.container}>
                    <TouchableOpacity
                      style={styles.submitButton}></TouchableOpacity>
                    <TouchableOpacity
                      onPress={() => {}}
                      style={styles.submitButton}></TouchableOpacity>
                  </View>
                </View>
              </Animated.View>
            </View>
          </GestureRecognizer>
    

    Output

    enter image description here