วิธีเลื่อนหน้าต่างอัตโนมัติออกจากด้านหลังแป้นพิมพ์เมื่อ TextInput มีโฟกัส


90

ฉันเคยเห็นแฮ็คนี้สำหรับแอปเนทีฟเพื่อเลื่อนหน้าต่างโดยอัตโนมัติ แต่สงสัยว่าจะทำอย่างไรใน React Native ที่ดีที่สุด ... เมื่อ<TextInput>ฟิลด์ได้รับโฟกัสและอยู่ในตำแหน่งต่ำในมุมมองแป้นพิมพ์จะปกปิดฟิลด์ข้อความ

คุณสามารถดูปัญหานี้ได้ในตัวอย่างTextInputExample.jsมุมมองของ UIExplorer

ใครมีวิธีแก้ดีๆ


3
ฉันขอแนะนำให้เพิ่มสิ่งนี้เป็นปัญหาในตัวติดตาม Github และดูว่ามีอะไรเกิดขึ้นหรือไม่เนื่องจากนี่จะเป็นการร้องเรียนที่พบบ่อยมาก
Colin Ramsay

คำตอบ:


84

2017 คำตอบ

ตอนKeyboardAvoidingViewนี้น่าจะเป็นวิธีที่ดีที่สุด ตรวจสอบเอกสารที่นี่ มันง่ายมากเมื่อเทียบกับKeyboardโมดูลที่ทำให้นักพัฒนาสามารถควบคุมการแสดงภาพเคลื่อนไหวได้มากขึ้น สเปนเซอร์ Carliแสดงให้เห็นทุกวิธีที่เป็นไปได้ในบล็อกของเขากลาง

2015 คำตอบ

วิธีที่ถูกต้องในการดำเนินการนี้react-nativeไม่จำเป็นต้องใช้ไลบรารีภายนอกใช้ประโยชน์จากโค้ดเนทีฟและรวมถึงภาพเคลื่อนไหว

ขั้นแรกกำหนดฟังก์ชันที่จะจัดการonFocusเหตุการณ์สำหรับแต่ละเหตุการณ์TextInput(หรือส่วนประกอบอื่น ๆ ที่คุณต้องการเลื่อนไป):

// Scroll a component into view. Just pass the component ref string.
inputFocused (refName) {
  setTimeout(() => {
    let scrollResponder = this.refs.scrollView.getScrollResponder();
    scrollResponder.scrollResponderScrollNativeHandleToKeyboard(
      React.findNodeHandle(this.refs[refName]),
      110, //additionalOffset
      true
    );
  }, 50);
}

จากนั้นในฟังก์ชันการแสดงผลของคุณ:

render () {
  return (
    <ScrollView ref='scrollView'>
        <TextInput ref='username' 
                   onFocus={this.inputFocused.bind(this, 'username')}
    </ScrollView>
  )
}

การใช้งานนี้RCTDeviceEventEmitterสำหรับเหตุการณ์แป้นพิมพ์และการปรับขนาดมาตรการตำแหน่งขององค์ประกอบที่ใช้และคำนวณการเคลื่อนไหวเลื่อนแน่นอนจำเป็นต้องใช้ในRCTUIManager.measureLayoutscrollResponderInputMeasureAndScrollToKeyboard

คุณอาจต้องการเล่นกับadditionalOffsetพารามิเตอร์เพื่อให้เหมาะกับความต้องการของการออกแบบ UI เฉพาะของคุณ


5
นี่เป็นการค้นหาที่ดี แต่สำหรับฉันมันยังไม่เพียงพอเพราะในขณะที่ ScrollView จะตรวจสอบให้แน่ใจว่า TextInput อยู่บนหน้าจอ ScrollView ก็ยังคงแสดงเนื้อหาด้านล่างแป้นพิมพ์ที่ผู้ใช้ไม่สามารถเลื่อนไปได้ การตั้งค่าคุณสมบัติ ScrollView "keyboardDismissMode = on-drag" ช่วยให้ผู้ใช้สามารถปิดแป้นพิมพ์ได้ แต่หากมีเนื้อหาเลื่อนด้านล่างแป้นพิมพ์ไม่เพียงพอประสบการณ์การใช้งานจะสั่นเล็กน้อย หาก ScrollView ต้องเลื่อนเพียงเพราะแป้นพิมพ์ในตอนแรกและคุณปิดใช้งานการตีกลับดูเหมือนว่าจะไม่มีวิธีใดที่จะปิดแป้นพิมพ์และแสดงเนื้อหาด้านล่าง
miracle2k

2
@ miracle2k - ฉันมีฟังก์ชั่นที่รีเซ็ตตำแหน่งมุมมองการเลื่อนเมื่ออินพุตเบลอกล่าวคือเมื่อแป้นพิมพ์ปิดลง บางทีนี่อาจช่วยได้ในกรณีของคุณ?
Sherlock

2
@Sherlock ฟังก์ชั่นรีเซ็ตมุมมองการเลื่อนแบบเบลอนั้นมีลักษณะอย่างไร? วิธีแก้ปัญหาที่ยอดเยี่ยม :)
Ryan McDermott

8
ใน React Native เวอร์ชันที่ใหม่กว่าคุณจะต้องเรียก: * import ReactNative จาก 'react-native'; * ก่อนโทร * ReactNative.findNodeHandle () * มิฉะนั้นแอพจะพัง
amirfl

6
ตอนนี้import {findNodeHandle} from 'react-native' stackoverflow.com/questions/37626851/…
antoine129

26

Facebook โอเพ่นซอร์ส KeyboardAvoidingViewใน react native 0.29 เพื่อแก้ปัญหานี้ เอกสารและตัวอย่างการใช้งานที่สามารถพบได้ที่นี่


33
ระวัง KeyboardAvoidingView มันไม่ใช่เรื่องง่ายที่จะใช้ มันไม่ได้ทำงานอย่างที่คุณคาดหวังเสมอไป เอกสารแทบไม่มีอยู่จริง
Renato ย้อนกลับ

doc และพฤติกรรมเริ่มดีขึ้นแล้ว
antoine129

ปัญหาที่ฉันมีคือ KeyboardAvoidingView วัดความสูงของแป้นพิมพ์เป็น 65 ในเครื่องจำลอง iPhone 6 ของฉันดังนั้นมุมมองของฉันยังคงซ่อนอยู่หลังแป้นพิมพ์
Marc

วิธีเดียวที่ฉันสามารถจัดการได้คือใช้วิธีการด้านล่างที่จุดเริ่มต้นโดยDeviceEventEmitter.addListener('keyboardDidShow', this.keyboardDidShow.bind(this));
Marc

12

เรารวมบางส่วนของรูปแบบโค้ด react-native-keyboard-spacer และโค้ดจาก @Sherlock เพื่อสร้างคอมโพเนนต์ KeyboardHandler ที่สามารถพันรอบ ๆ View ที่มีองค์ประกอบ TextInput ใช้งานได้เหมือนมีเสน่ห์! :-)

/**
 * Handle resizing enclosed View and scrolling to input
 * Usage:
 *    <KeyboardHandler ref='kh' offset={50}>
 *      <View>
 *        ...
 *        <TextInput ref='username'
 *          onFocus={()=>this.refs.kh.inputFocused(this,'username')}/>
 *        ...
 *      </View>
 *    </KeyboardHandler>
 * 
 *  offset is optional and defaults to 34
 *  Any other specified props will be passed on to ScrollView
 */
'use strict';

var React=require('react-native');
var {
  ScrollView,
  View,
  DeviceEventEmitter,
}=React;


var myprops={ 
  offset:34,
}
var KeyboardHandler=React.createClass({
  propTypes:{
    offset: React.PropTypes.number,
  },
  getDefaultProps(){
    return myprops;
  },
  getInitialState(){
    DeviceEventEmitter.addListener('keyboardDidShow',(frames)=>{
      if (!frames.endCoordinates) return;
      this.setState({keyboardSpace: frames.endCoordinates.height});
    });
    DeviceEventEmitter.addListener('keyboardWillHide',(frames)=>{
      this.setState({keyboardSpace:0});
    });

    this.scrollviewProps={
      automaticallyAdjustContentInsets:true,
      scrollEventThrottle:200,
    };
    // pass on any props we don't own to ScrollView
    Object.keys(this.props).filter((n)=>{return n!='children'})
    .forEach((e)=>{if(!myprops[e])this.scrollviewProps[e]=this.props[e]});

    return {
      keyboardSpace:0,
    };
  },
  render(){
    return (
      <ScrollView ref='scrollView' {...this.scrollviewProps}>
        {this.props.children}
        <View style={{height:this.state.keyboardSpace}}></View>
      </ScrollView>
    );
  },
  inputFocused(_this,refName){
    setTimeout(()=>{
      let scrollResponder=this.refs.scrollView.getScrollResponder();
      scrollResponder.scrollResponderScrollNativeHandleToKeyboard(
        React.findNodeHandle(_this.refs[refName]),
        this.props.offset, //additionalOffset
        true
      );
    }, 50);
  }
}) // KeyboardHandler

module.exports=KeyboardHandler;

มีอะไรง่าย / ชัดเจนที่จะป้องกันไม่ให้คีย์บอร์ดแสดงในโปรแกรมจำลอง iOS
seigel

1
คุณลอง Command + K (Hardware-> Keyboard-> Toggle Software Keboard) หรือไม่?
John kendall

ลองใช้เวอร์ชันแก้ไขที่นี่: gist.github.com/dbasedow/f5713763802e27fbde3fc57a600adcd3ฉันเชื่อว่าสิ่งนี้ดีกว่าเพราะไม่ต้องพึ่งพาการหมดเวลาใด ๆ ซึ่งฉันคิดว่าเป็น imo ที่เปราะบาง
CoderDave

10

แรกที่คุณจำเป็นต้องติดตั้งตอบสนองพื้นเมือง-KeyboardEvents

  1. ใน XCode ในตัวนำทางโปรเจ็กต์ให้คลิกขวาที่ Libraries ➜ Add Files to [your project's name] ไปที่ node_modules ➜ react-native-keyboardevents และเพิ่มไฟล์. xcodeproj
  2. ใน XCode ในตัวนำทางโครงการให้เลือกโครงการของคุณ เพิ่ม lib * .a จากโปรเจ็กต์ keyboardevents ไปยัง Build Phases ของโปรเจ็กต์ของคุณ➜เชื่อมโยงไบนารีกับไลบรารีคลิกไฟล์. xcodeproj ที่คุณเพิ่มไว้ก่อนหน้าในตัวนำทางโปรเจ็กต์และไปที่แท็บ Build Settings ตรวจสอบให้แน่ใจว่าได้เปิดใช้งาน "ทั้งหมด" แล้ว (แทนที่จะเป็น "พื้นฐาน") มองหาเส้นทางการค้นหาส่วนหัวและตรวจสอบให้แน่ใจว่ามีทั้ง $ (SRCROOT) /../ react-native / React และ $ (SRCROOT) /../../ React - ทำเครื่องหมายทั้งสองเป็นแบบวนซ้ำ
  3. ดำเนินโครงการของคุณ (Cmd + R)

จากนั้นกลับมาที่จาวาสคริปต์:

คุณต้องนำเข้าเหตุการณ์แป้นพิมพ์ตอบสนองพื้นเมือง

var KeyboardEvents = require('react-native-keyboardevents');
var KeyboardEventEmitter = KeyboardEvents.Emitter;

จากนั้นในมุมมองของคุณให้เพิ่มสถานะบางอย่างสำหรับพื้นที่คีย์บอร์ดและอัปเดตจากการฟังเหตุการณ์บนคีย์บอร์ด

  getInitialState: function() {
    KeyboardEventEmitter.on(KeyboardEvents.KeyboardDidShowEvent, (frames) => {
      this.setState({keyboardSpace: frames.end.height});
    });
    KeyboardEventEmitter.on(KeyboardEvents.KeyboardWillHideEvent, (frames) => {
      this.setState({keyboardSpace: 0});
    });

    return {
      keyboardSpace: 0,
    };
  },

สุดท้ายเพิ่มตัวเว้นวรรคในฟังก์ชันการเรนเดอร์ของคุณด้านล่างทุกอย่างดังนั้นเมื่อมันเพิ่มขนาดมันจะกระแทกสิ่งของของคุณ

<View style={{height: this.state.keyboardSpace}}></View>

นอกจากนี้ยังสามารถใช้ API ของแอนิเมชั่นได้ แต่เพื่อความเรียบง่ายเราเพียงแค่ปรับหลังจากภาพเคลื่อนไหว


1
การดูตัวอย่างโค้ด / ข้อมูลเพิ่มเติมเกี่ยวกับวิธีการทำแอนิเมชั่นจะดีมาก การกระโดดนั้นค่อนข้างไม่ดีและใช้เฉพาะเมธอด "จะแสดง" และ "แสดง" เท่านั้นฉันไม่สามารถเดาได้ว่าจะคาดเดาได้อย่างไรว่าภาพเคลื่อนไหวบนแป้นพิมพ์จะยาวแค่ไหนหรือสูงแค่ไหนจาก "จะแสดง"
Stephen

2
react-native@0.11.0-rc ตอนนี้ส่งเหตุการณ์แป้นพิมพ์ (เช่น "keyboardWillShow") ผ่าน DeviceEventEmitter ดังนั้นคุณจึงสามารถลงทะเบียนผู้ฟังสำหรับเหตุการณ์เหล่านี้ได้ อย่างไรก็ตามเมื่อจัดการกับ ListView ฉันพบว่าการเรียก scrollTo () บน scrollview ของ ListView ทำงานได้ดีขึ้น: this.listView.getScrollResponder().scrollTo(rowID * rowHeight); สิ่งนี้จะเรียกใช้ TextInput ของแถวเมื่อได้รับเหตุการณ์ onFocus
Jed Lau

4
คำตอบนี้ใช้ไม่ได้อีกต่อไปเนื่องจาก RCTDeviceEventEmitter ทำงาน
Sherlock


6

ลองสิ่งนี้:

import React, {
  DeviceEventEmitter,
  Dimensions
} from 'react-native';

...

getInitialState: function() {
  return {
    visibleHeight: Dimensions.get('window').height
  }
},

...

componentDidMount: function() {
  let self = this;

  DeviceEventEmitter.addListener('keyboardWillShow', function(e: Event) {
    self.keyboardWillShow(e);
  });

  DeviceEventEmitter.addListener('keyboardWillHide', function(e: Event) {
      self.keyboardWillHide(e);
  });
}

...

keyboardWillShow (e) {
  let newSize = Dimensions.get('window').height - e.endCoordinates.height;
  this.setState({visibleHeight: newSize});
},

keyboardWillHide (e) {
  this.setState({visibleHeight: Dimensions.get('window').height});
},

...

render: function() {
  return (<View style={{height: this.state.visibleHeight}}>your view code here...</View>);
}

...

มันได้ผลสำหรับฉัน โดยทั่วไปมุมมองจะหดตัวลงเมื่อแป้นพิมพ์ปรากฏขึ้นและจะกลับมาอีกครั้งเมื่อซ่อนอยู่


นอกจากนี้โซลูชันนี้ยังทำงานได้ดี (RN 0.21.0) stackoverflow.com/a/35874233/3346628
pomo

use this.keyboardWillHide.bind (this) แทน self
animekun

ใช้ Keyboard แทน DeviceEventEmitter
Madura Pradeep

4

แค่อยากจะพูดถึงตอนนี้มีKeyboardAvoidingViewใน RN เพียงแค่นำเข้าและใช้เป็นโมดูลอื่น ๆ ใน RN

นี่คือลิงค์ไปยังการกระทำบน RN:

https://github.com/facebook/react-native/commit/8b78846a9501ef9c5ce9d1e18ee104bfae76af2e

มีให้ตั้งแต่ 0.29.0

พวกเขาได้รวมตัวอย่างไว้ใน UIExplorer ด้วย


4

อาจจะช้าไป แต่ทางออกที่ดีที่สุดคือใช้IQKeyboardManagerไลบรารีดั้งเดิม

เพียงลากและวางไดเรกทอรี IQKeyboardManager จากโครงการสาธิตไปยังโครงการ iOS ของคุณ แค่นั้นแหละ. นอกจากนี้คุณสามารถตั้งค่า valus บางอย่างตามที่เปิดใช้งาน isToolbar หรือช่องว่างระหว่างการป้อนข้อความและแป้นพิมพ์ในไฟล์ AppDelegate.m รายละเอียดเพิ่มเติมเกี่ยวกับการปรับแต่งอยู่ในลิงก์หน้า GitHub ที่ฉันได้เพิ่มเข้าไป


1
นี่เป็นตัวเลือกที่ยอดเยี่ยม นอกจากนี้โปรดดูที่github.com/douglasjunior/react-native-keyboard-managerสำหรับเวอร์ชันที่รวมไว้สำหรับ ReactNative ซึ่งง่ายต่อการติดตั้ง
loevborg

3

ฉันใช้ TextInput.onFocus และ ScrollView.scrollTo

...
<ScrollView ref="scrollView">
...
<TextInput onFocus={this.scrolldown}>
...
scrolldown: function(){
  this.refs.scrollView.scrollTo(width*2/3);
},

2

@ สตีเฟน

หากคุณไม่รังเกียจที่จะไม่ให้ความสูงเคลื่อนไหวในอัตราเดียวกับที่แป้นพิมพ์ปรากฏขึ้นคุณสามารถใช้ LayoutAnimation ได้อย่างน้อยความสูงจะไม่เข้าที่ เช่น

นำเข้า LayoutAnimation จาก react-native และเพิ่มวิธีการต่อไปนี้ในส่วนประกอบของคุณ

getInitialState: function() {
    return {keyboardSpace: 0};
  },
   updateKeyboardSpace: function(frames) {
    LayoutAnimation.configureNext(animations.layout.spring);
    this.setState({keyboardSpace: frames.end.height});
  },

  resetKeyboardSpace: function() {
    LayoutAnimation.configureNext(animations.layout.spring);
    this.setState({keyboardSpace: 0});
  },

  componentDidMount: function() {
    KeyboardEventEmitter.on(KeyboardEvents.KeyboardDidShowEvent, this.updateKeyboardSpace);
    KeyboardEventEmitter.on(KeyboardEvents.KeyboardWillHideEvent, this.resetKeyboardSpace);
  },

  componentWillUnmount: function() {
    KeyboardEventEmitter.off(KeyboardEvents.KeyboardDidShowEvent, this.updateKeyboardSpace);
    KeyboardEventEmitter.off(KeyboardEvents.KeyboardWillHideEvent, this.resetKeyboardSpace);
  },

ภาพเคลื่อนไหวตัวอย่างบางส่วน ได้แก่ (ฉันใช้สปริงด้านบน):

var animations = {
  layout: {
    spring: {
      duration: 400,
      create: {
        duration: 300,
        type: LayoutAnimation.Types.easeInEaseOut,
        property: LayoutAnimation.Properties.opacity,
      },
      update: {
        type: LayoutAnimation.Types.spring,
        springDamping: 400,
      },
    },
    easeInEaseOut: {
      duration: 400,
      create: {
        type: LayoutAnimation.Types.easeInEaseOut,
        property: LayoutAnimation.Properties.scaleXY,
      },
      update: {
        type: LayoutAnimation.Types.easeInEaseOut,
      },
    },
  },
};

อัพเดท:

ดูคำตอบของ @ sherlock ด้านล่างเนื่องจาก react-native 0.11 การปรับขนาดแป้นพิมพ์สามารถแก้ไขได้โดยใช้ฟังก์ชันในตัว


2

คุณสามารถรวมวิธีการบางอย่างเข้ากับสิ่งที่ง่ายกว่าเล็กน้อยได้

แนบตัวฟัง onFocus กับอินพุตของคุณ

<TextInput ref="password" secureTextEntry={true} 
           onFocus={this.scrolldown.bind(this,'password')}
/>

วิธีการเลื่อนลงของเรามีลักษณะดังนี้:

scrolldown(ref) {
    const self = this;
    this.refs[ref].measure((ox, oy, width, height, px, py) => {
        self.refs.scrollView.scrollTo({y: oy - 200});
    });
}

สิ่งนี้จะบอกมุมมองการเลื่อนของเรา (อย่าลืมเพิ่มการอ้างอิง) เพื่อเลื่อนลงไปที่ตำแหน่งของอินพุตที่โฟกัสของเรา - 200 (เป็นขนาดโดยประมาณของแป้นพิมพ์)

componentWillMount() {
    this.keyboardDidHideListener = Keyboard.addListener(
      'keyboardWillHide', 
      this.keyboardDidHide.bind(this)
    )
}

componentWillUnmount() {
    this.keyboardDidHideListener.remove()
}

keyboardDidHide(e) {
    this.refs.scrollView.scrollTo({y: 0});
}

ที่นี่เรารีเซ็ตมุมมองการเลื่อนของเรากลับไปด้านบน

ป้อนคำอธิบายภาพที่นี่


2
@ คุณช่วยระบุวิธีการ render () ของคุณได้ไหม
valerybodak

0

ฉันใช้วิธีที่ง่ายกว่านี้ แต่มันยังไม่เคลื่อนไหว ฉันมีสถานะส่วนประกอบที่เรียกว่า "bumpedUp" ซึ่งฉันตั้งค่าเริ่มต้นเป็น 0 แต่ตั้งค่าเป็น 1 เมื่อ textInput ได้รับโฟกัสดังนี้:

ในข้อความของฉัน

onFocus={() => this.setState({bumpedUp: 1})}
onEndEditing={() => this.setState({bumpedUp: 0})}

ฉันยังมีสไตล์ที่ทำให้คอนเทนเนอร์ห่อของทุกอย่างบนหน้าจอนั้นมีขอบด้านล่างและขอบด้านบนติดลบเช่นนี้:

mythingscontainer: {
  flex: 1,
  justifyContent: "center",
  alignItems: "center",
  flexDirection: "column",
},
bumpedcontainer: {
  marginBottom: 210,
  marginTop: -210,
},

จากนั้นบนภาชนะห่อฉันตั้งค่ารูปแบบดังนี้:

<View style={[styles.mythingscontainer, this.state.bumpedUp && styles.bumpedcontainer]}>

ดังนั้นเมื่อตั้งค่าสถานะ "bumpedUp" เป็น 1 สไตล์คอนเทนเนอร์ที่ถูกกระแทกจะเข้ามาและย้ายเนื้อหาขึ้น

ค่อนข้างแฮ็คและระยะขอบเป็นฮาร์ดโค้ด แต่ใช้งานได้ :)


0

ฉันใช้คำตอบ brysgo เพื่อยกส่วนล่างของมุมมองแบบเลื่อนของฉัน จากนั้นฉันใช้ onScroll เพื่ออัปเดตตำแหน่งปัจจุบันของ scrollview จากนั้นฉันพบReact Nativeนี้: การหาตำแหน่งขององค์ประกอบเพื่อรับตำแหน่งของ textinput จากนั้นฉันจะคำนวณง่ายๆเพื่อดูว่าอินพุตอยู่ในมุมมองปัจจุบันหรือไม่ จากนั้นฉันใช้ scrollTo เพื่อย้ายจำนวนเงินขั้นต่ำบวกระยะขอบ มันเนียนสวย นี่คือรหัสสำหรับส่วนการเลื่อน:

            focusOn: function(target) {
                return () => {
                    var handle = React.findNodeHandle(this.refs[target]);
                    UIManager.measureLayoutRelativeToParent( handle, 
                        (e) => {console.error(e)}, 
                        (x,y,w,h) => {
                            var offs = this.scrollPosition + 250;
                            var subHeaderHeight = (Sizes.width > 320) ? Sizes.height * 0.067 : Sizes.height * 0.077;
                            var headerHeight = Sizes.height / 9;
                            var largeSpace = (Sizes.height - (subHeaderHeight + headerHeight));
                            var shortSpace = largeSpace - this.keyboardOffset;
                            if(y+h >= this.scrollPosition + shortSpace) {
                                this.refs.sv.scrollTo(y+h - shortSpace + 20);
                            }
                            if(y < this.scrollPosition) this.refs.sv.scrollTo(this.scrollPosition - (this.scrollPosition-y) - 20 );
                        }
                     );
                };
            },

0

ฉันยังพบคำถามนี้ สุดท้ายฉันแก้ไขโดยกำหนดความสูงของแต่ละฉากเช่น:

<Navigator
    ...
    sceneStyle={{height: **}}
/>

และฉันยังใช้โมดูลของบุคคลที่สามhttps://github.com/jaysoo/react-native-extra-dimensions-androidเพื่อให้ได้ความสูงที่แท้จริง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.