TextInput 用作子组件时需要双击键盘

问题描述 投票:0回答:1

问题:

加载应用程序后,当用户选择(点击)输入时,键盘第一次不会显示。只有在通过点击屏幕上的其他任何位置取消选择输入后,然后再次点击相同的输入才会显示键盘。并且这种双击仅在初始加载后需要一次。点击打开和关闭输入后,单击会继续工作。

enter image description here

我认为是这个原因

我注意到当我将输入移动到子组件中时,这个问题开始发生。然而,如果我写出重复的代码,它似乎工作得很好。我认为

setState
导致子级最初重新渲染,然后一旦再次调用
setState
就不会再渲染。

代码

enter image description here

应用程序.json

{
  "expo": {
    "name": "react-native-expo-template",
    "slug": "react-native-expo-template",
    "version": "1.0.0",
    "scheme": "msauth",
    "orientation": "portrait",
    "icon": "./assets/icon.png",
    "userInterfaceStyle": "light",
    "backgroundColor": "#001689",
    "splash": {
      "image": "./assets/splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#001689"
    },
    "assetBundlePatterns": ["**/*"],
    "ios": {
      "supportsTablet": true,
      "bundleIdentifier": "com.jamespageced.reactnativeexpotemplate",
      "buildNumber": "1.0.0"
    },
    "android": {
      "adaptiveIcon": {
        "foregroundImage": "./assets/icon.png",
        "backgroundColor": "#001689"
      },
      "package": "com.jamespageced.reactnativeexpotemplate",
      "versionCode": 1
    },
    "web": {
      "favicon": "./assets/favicon.png"
    },
    "plugins": [
      [
        "expo-image-picker",
        {
          "photosPermission": "Allow $(PRODUCT_NAME) to access your photos",
          "cameraPermissions": "Allow $(PRODUCT_NAME) to access your camera"
        }
      ]
    ]
  }
}

babel.config.js

module.exports = function (api) {
  api.cache(true);
  return {
    presets: ['module:metro-react-native-babel-preset', 'babel-preset-expo'],
    plugins: [
      [
        require.resolve('babel-plugin-module-resolver'),
        {
          extensions: ['.js', '.jsx', '.ts', '.tsx', '.android.js', '.android.tsx', '.ios.js', '.ios.tsx'],
          alias: {
            '@': './',
            '@app': './app'
          }
        }
      ]
    ]
  };
};

package.json

{
  "name": "react-native-expo-template",
  "version": "1.0.0",
  "main": "node_modules/expo/AppEntry.js",
  "scripts": {
    "ts:check": "tsc",
    "dev-android": "cross-env NODE_ENV=development npx expo run:android --port 8082",
    "dev-ios": "cross-env NODE_ENV=development npx expo run:ios --port 8082",
    "android": "expo run:android",
    "ios": "expo run:ios"
  },
  "dependencies": {
    "@react-navigation/native": "^6.1.9",
    "@react-navigation/stack": "^6.3.20",
    "expo": "~49.0.15",
    "expo-image-picker": "~14.3.2",
    "expo-splash-screen": "~0.20.5",
    "expo-status-bar": "~1.6.0",
    "react": "18.2.0",
    "react-native": "0.72.6",
    "react-native-gesture-handler": "~2.12.0",
    "react-native-safe-area-context": "4.6.3",
    "react-native-screens": "~3.22.0",
    "react-query": "^3.39.3"
  },
  "devDependencies": {
    "@babel/core": "^7.20.0",
    "@types/react": "~18.2.14",
    "cross-env": "^7.0.3",
    "typescript": "^5.1.3"
  },
  "private": true
}

应用程序.tsx

import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { QueryClient, QueryClientProvider } from 'react-query';
import { Home } from '@app/screens';

const queryClient = new QueryClient();

export default function App() {
  return (
    <NavigationContainer>
      <QueryClientProvider client={queryClient}>
        <Home />
      </QueryClientProvider>
    </NavigationContainer>
  );
}

应用程序/屏幕/index.ts

export { default as Home } from './Home';

应用程序/屏幕/Home.tsx

import React, { useEffect, useState } from 'react';
import { Keyboard, Pressable, StyleSheet, TouchableWithoutFeedback, View } from 'react-native';
import { StatusBar } from 'expo-status-bar';
import * as ImagePicker from 'expo-image-picker';
import type { PermissionStatus } from 'expo-modules-core';
import { useQuery } from 'react-query';
import { useIsFocused } from '@react-navigation/native';
import { MaterialIcons } from '@expo/vector-icons';
import { Header, InputWithCamera } from '@app/screens/homeChildren';

const styles = StyleSheet.create({
  formContainer: {
    width: '100%',
    padding: 20,
    gap: 20,
    borderBottomWidth: 1,
    borderColor: '#999'
  },
  cameraButton: {
    alignItems: 'center',
    justifyContent: 'center',
    height: 48,
    padding: 10,
    backgroundColor: '#ffffff'
  },
  cameraButtonDisabled: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: '#cccccc',
    borderColor: 'black',
    borderWidth: 1,
    marginLeft: 10
  }
});

export default function Home() {
  const isFocused = useIsFocused();
  const [cameraPermission, setCameraPermission] = useState<PermissionStatus>(ImagePicker.PermissionStatus.DENIED);
  const [inputOneText, setInputOneText] = useState('');
  const [inputTwoText, setInputTwoText] = useState('');
  const [isInputOneActive, setIsInputOneActive] = useState(false);
  const [isInputTwoActive, setIsInputTwoActive] = useState(false);

  const { isFetching: isFetchingScreenData } = useQuery(
    ['transactionStartScreenLoadData', isFocused],
    async () => {
      if (!isFocused) return undefined;
      const { status } = await ImagePicker.getCameraPermissionsAsync();
      return status;
    },
    {
      onError: (error: any) => {
        if (!isFocused) return undefined;
        return undefined;
      },
      onSuccess: (resp: PermissionStatus) => {
        if (resp === undefined) return;
        setCameraPermission(resp);
        return undefined;
      }
    }
  );

  // functions
  const handleSelectInputs = (selectedInputActiveState?: string | null) => {
    if (selectedInputActiveState === null) {
      Keyboard.dismiss();
    }
    if (selectedInputActiveState === 'isInputOneActive') {
      setIsInputOneActive(true);
    } else if (selectedInputActiveState === 'isInputTwoActive') {
      setIsInputTwoActive(true);
    }
    if (selectedInputActiveState !== 'isInputOneActive') setIsInputOneActive(false);
    if (selectedInputActiveState !== 'isInputTwoActive') setIsInputTwoActive(false);
  };
  const isCameraDisabled = () => {
    return cameraPermission === ImagePicker.PermissionStatus.DENIED;
  };

  // setup
  useEffect(() => {
    if (!isFocused) {
      handleSelectInputs(null);
    }
  }, [isFocused]);
  return (
    <TouchableWithoutFeedback onPress={() => handleSelectInputs(null)}>
      <View style={{ backgroundColor: '#ffffff', ...StyleSheet.absoluteFillObject }}>
        <Header title="Main Title" subTitle="Sub Title" />
        <View style={styles.formContainer}>
          <InputWithCamera
            placeholderText="Input One"
            isInputActive={isInputOneActive}
            inputText={inputOneText}
            setInputToActive={() => handleSelectInputs('isInputOneActive')}
            setInputText={setInputOneText}
          >
            <Pressable
              disabled={isCameraDisabled()}
              style={isCameraDisabled() ? styles.cameraButtonDisabled : styles.cameraButton}
              onPress={() => handleSelectInputs(null)}
            >
              <MaterialIcons name="camera-alt" size={24} color={isCameraDisabled() ? '#333333' : '#2370B3'} />
            </Pressable>
          </InputWithCamera>
          <InputWithCamera
            placeholderText="Input Two"
            isInputActive={isInputTwoActive}
            inputText={inputTwoText}
            setInputToActive={() => handleSelectInputs('isInputTwoActive')}
            setInputText={setInputTwoText}
          >
            <Pressable
              disabled={isCameraDisabled()}
              style={isCameraDisabled() ? styles.cameraButtonDisabled : styles.cameraButton}
              onPress={() => handleSelectInputs(null)}
            >
              <MaterialIcons name="camera-alt" size={24} color={isCameraDisabled() ? '#333333' : '#2370B3'} />
            </Pressable>
          </InputWithCamera>
          <View style={{ marginBottom: 100 }} />
        </View>
        <StatusBar style="auto" />
      </View>
    </TouchableWithoutFeedback>
  );
}

应用程序/屏幕/homeChildren/index.ts

export { default as Header } from './Header';
export { default as InputWithCamera } from './InputWithCamera';

应用程序/屏幕/homeChildren/InputWithCamera.tsx

import React, { Dispatch, ReactNode, SetStateAction } from 'react';
import {
  NativeSyntheticEvent,
  StyleSheet,
  Text,
  TextInput,
  TextInputFocusEventData,
  TouchableOpacity,
  View
} from 'react-native';

interface Props {
  placeholderText: string;
  isInputActive: boolean;
  inputText: string;
  maxInputHeight: number;
  setInputToActive: (e: NativeSyntheticEvent<TextInputFocusEventData>) => void;
  setInputText: Dispatch<SetStateAction<string>>;
  children: ReactNode;
}

const styles = StyleSheet.create({
  container: { flex: 1 },
  showInputSecondBoarder: {
    padding: 2,
    borderWidth: 1,
    borderColor: '#03A9F4'
  },
  hideInputSecondBoarder: {
    height: 0,
    padding: 2
  },
  inputContainerOuter: {
    flexDirection: 'row',
    alignItems: 'center',
    borderWidth: 1,
    borderColor: '#03A9F4'
  },
  activePlaceholderContainer: {
    position: 'absolute',
    top: -12,
    left: 16,
    zIndex: 10,
    paddingHorizontal: 5,
    backgroundColor: '#ffffff'
  },
  inactivePlaceholderContainer: {
    position: 'absolute',
    top: 14,
    left: 16,
    zIndex: 10,
    paddingHorizontal: 5,
    backgroundColor: '#ffffff'
  },
  inputContainerInner: {
    flex: 1,
    justifyContent: 'center'
  },
  input: { paddingHorizontal: 10, backgroundColor: '#ffffff' }
});

export default function InputWithCamera({
  placeholderText,
  isInputActive,
  inputText,
  maxInputHeight,
  setInputToActive,
  setInputText,
  children
}: Props): ReactNode {
  return (
    <View style={{ ...styles.container, marginBottom: maxInputHeight - 7 }}>
      <View
        style={
          isInputActive ? { ...styles.showInputSecondBoarder, height: maxInputHeight } : styles.hideInputSecondBoarder
        }
      >
        <View style={{ ...styles.inputContainerOuter, height: maxInputHeight - 7 }}>
          <View
            style={isInputActive || inputText ? styles.activePlaceholderContainer : styles.inactivePlaceholderContainer}
            pointerEvents="none"
          >
            <Text>
              {placeholderText} <Text style={{ color: 'red' }}>*</Text>
            </Text>
          </View>
          <TouchableOpacity style={styles.inputContainerInner} activeOpacity={1}>
            <TextInput
              onChangeText={(text: string) => setInputText(text)}
              onFocus={setInputToActive}
              style={{ ...styles.input, height: maxInputHeight - 9 }}
              placeholder=""
              value={inputText}
              autoCorrect={false}
              spellCheck={false}
            />
          </TouchableOpacity>
          {children}
        </View>
      </View>
    </View>
  );
}

InputWithCamera.defaultProps = {
  maxInputHeight: 57
};

应用程序/屏幕/homeChildren/Header.tsx

import React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import { MaterialIcons } from '@expo/vector-icons';

interface Props {
  title: string;
  subTitle: string;
}

const styles = StyleSheet.create({
  container: {
    width: '100%',
    flexDirection: 'row',
    alignItems: 'center',
    paddingTop: 40,
    paddingHorizontal: 20,
    paddingBottom: 20,
    backgroundColor: '#f4fcff'
  },
  iconContainer: { backgroundColor: '#d6e7f8', padding: 10, borderRadius: 50, marginRight: 10 },
  hideIconContainer: {
    justifyContent: 'center',
    alignItems: 'center',
    height: 30,
    width: 30,
    marginLeft: 'auto',
    backgroundColor: '#2370B3',
    borderRadius: 50
  },
  titleContainer: { flex: 1, flexDirection: 'column' },
  titleText: { flexShrink: 1, color: '#000000', opacity: 0.67 }
});

export default function Header({ title, subTitle }: Props): JSX.Element {
  return (
    <View style={styles.container}>
      <View style={styles.iconContainer}>
        <MaterialIcons name="fact-check" size={24} color="#4D688B" />
      </View>
      <View style={styles.titleContainer}>
        <Text numberOfLines={2} style={{ ...styles.titleText, fontSize: 18 }}>
          {title}
        </Text>
        <Text numberOfLines={1} style={{ ...styles.titleText, fontSize: 16 }}>
          {subTitle}
        </Text>
      </View>
    </View>
  );
}
react-native expo
1个回答
0
投票

我解决了这个问题。这是因为第二个边框视图造成了某种干扰,所以我将其添加为绝对位置并确保它不会环绕输入。

风格改变...

const styles = StyleSheet.create({
  container: { flex: 1, position: 'relative' },
  inputSecondBoarder: {
    position: 'absolute',
    top: -4,
    right: -4,
    bottom: -4,
    left: -4,
    zIndex: 1,
    borderWidth: 1,
    borderColor: '#03A9F4'
  },
  inputContainerOuter: {
    zIndex: 2,
    flexDirection: 'row',
    alignItems: 'center',
    borderWidth: 1,
    borderColor: '#03A9F4'
  },
...

JSX 变更

    <View style={{ ...styles.container, marginBottom: maxInputHeight - 7 }}>
      <View style={isInputActive ? styles.inputSecondBoarder : {}} />
      <View style={{ ...styles.inputContainerOuter, height: maxInputHeight - 7 }}>
...
...
...
        </TouchableOpacity>
        {children}
      </View>
    </View>
  );
}

注意:不要忘记删除相关的结尾

</View>

© www.soinside.com 2019 - 2024. All rights reserved.