Làm cách nào để tự động trượt cửa sổ ra khỏi bàn phím khi TextInput có tiêu điểm?


90

Tôi đã thấy bản hack này cho các ứng dụng gốc để tự động cuộn cửa sổ, nhưng đang băn khoăn không biết cách tốt nhất để thực hiện điều đó trong React Native ... Khi một <TextInput>trường được lấy nét và nằm ở vị trí thấp trong chế độ xem, bàn phím sẽ che trường văn bản.

Bạn có thể thấy vấn đề này trong ví dụ của UIExplorer's TextInputExample.jsview.

Có ai có một giải pháp tốt?


3
Tôi khuyên bạn nên thêm điều này như một vấn đề trên trình theo dõi Github và xem liệu có bất cứ điều gì xảy ra với nó không, vì đây sẽ là một khiếu nại rất phổ biến.
Colin Ramsay

Câu trả lời:


83

Câu trả lời năm 2017

KeyboardAvoidingViewlẽ là cách tốt nhất để đi bây giờ. Kiểm tra các tài liệu ở đây . Nó thực sự đơn giản so với Keyboardmô-đun cho phép Nhà phát triển kiểm soát nhiều hơn để thực hiện các hoạt ảnh. Spencer Carli đã trình bày tất cả các cách có thể trên blog phương tiện của mình .

Câu trả lời năm 2015

Cách chính xác để làm điều này là react-nativekhông yêu cầu thư viện bên ngoài, tận dụng mã gốc và bao gồm các hình ảnh động.

Trước tiên, hãy xác định một hàm sẽ xử lý onFocussự kiện cho từng TextInput(hoặc bất kỳ thành phần nào khác mà bạn muốn cuộn đến):

// 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);
}

Sau đó, trong chức năng kết xuất của bạn:

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

Điều này sử dụng RCTDeviceEventEmittercho các sự kiện bàn phím và định cỡ, đo vị trí của thành phần đang sử dụng RCTUIManager.measureLayoutvà tính toán chuyển động cuộn chính xác cần thiết scrollResponderInputMeasureAndScrollToKeyboard.

Bạn có thể muốn thử với additionalOffsettham số để phù hợp với nhu cầu của thiết kế giao diện người dùng cụ thể của bạn.


5
Đây là một phát hiện hay, nhưng đối với tôi thì vẫn chưa đủ, bởi vì trong khi ScrollView sẽ đảm bảo TextInput hiển thị trên màn hình, ScrollView vẫn hiển thị nội dung bên dưới bàn phím mà người dùng không thể cuộn đến. Đặt thuộc tính ScrollView "keyboardDismissMode = on-drag" cho phép người dùng loại bỏ bàn phím, nhưng nếu không có đủ nội dung cuộn bên dưới bàn phím, trải nghiệm sẽ hơi chói tai. Nếu scrollview chỉ phải di chuyển vì bàn phím ở nơi đầu tiên, và bạn vô hiệu hóa nảy thì có vẻ là không có cách nào để bỏ qua bàn phím & hiển thị các nội dung dưới đây
miracle2k

2
@racle2k - Tôi có một chức năng đặt lại vị trí chế độ xem cuộn khi đầu vào bị mờ, tức là khi bàn phím đóng. Có lẽ điều này có thể giúp ích trong trường hợp của bạn?
Sherlock

2
@Sherlock Chức năng đặt lại chế độ xem cuộn mờ đó trông như thế nào? Giải pháp tuyệt vời bằng cách này :)
Ryan McDermott

8
Trong các phiên bản React Native mới hơn, bạn sẽ cần gọi: * import ReactNative từ 'react-native'; * trước khi gọi * ReactNative.findNodeHandle () * Nếu không ứng dụng sẽ gặp sự cố
amirfl

6
Bây giờ import {findNodeHandle} from 'react-native' stackoverflow.com/questions/37626851/...
antoine129

26

Facebook mở nguồn gốc KeyboardAvoidingView trong react native 0.29 để giải quyết vấn đề này. Tài liệu và ví dụ sử dụng có thể được tìm thấy ở đây .


32
Hãy coi chừng KeyboardAvoidingView, nó không dễ sử dụng. Nó không phải lúc nào cũng hoạt động như bạn mong đợi. Tài liệu thực tế là không tồn tại.
Renato Trở lại

doc và hành vi đang nhận được tốt hơn bây giờ
antoine129

Vấn đề tôi gặp phải là KeyboardAvoidingView đo chiều cao bàn phím là 65 trên trình mô phỏng iPhone 6 của tôi và do đó chế độ xem của tôi vẫn bị ẩn sau bàn phím.
Marc

Chỉ cách tôi có thể quản lý nó đã được thông qua một cách tiếp cận bottompadding kích hoạt bởiDeviceEventEmitter.addListener('keyboardDidShow', this.keyboardDidShow.bind(this));
Marc

12

Chúng tôi đã kết hợp một số dạng mã react-native-keyboard-spacer và mã từ @Sherlock để tạo thành phần KeyboardHandler có thể được bao quanh bất kỳ Chế độ xem nào có phần tử TextInput. Hoạt động như một sự quyến rũ! :-)

/**
 * 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;

Bất cứ điều gì dễ dàng / hiển nhiên có thể ngăn bàn phím hiển thị trong trình mô phỏng iOS?
seigel

1
Bạn đã thử Command + K (Hardware-> Keyboard-> Toggle Software Keboard) chưa?
John kendall

Hãy thử phiên bản đã sửa đổi của điều này tại đây: gist.github.com/dbasedow/f5713763802e27fbde3fc57a600adcd3 Tôi tin rằng điều này tốt hơn vì nó không dựa vào bất kỳ thời gian chờ nào mà tôi nghĩ là imo mong manh.
CoderDave

10

Trước tiên, bạn cần cài đặt react-native-keyboardevents .

  1. Trong XCode, trong trình điều hướng dự án, nhấp chuột phải vào Thư viện ➜ Thêm tệp vào [tên dự án của bạn] Đi tới node_modules ➜ react-native-keyboardevents và thêm tệp .xcodeproj
  2. Trong XCode, trong trình điều hướng dự án, hãy chọn dự án của bạn. Thêm lib * .a từ dự án keyboardevents vào các giai đoạn xây dựng của dự án ➜ Liên kết nhị phân với thư viện Nhấp vào tệp .xcodeproj mà bạn đã thêm trước đó trong trình điều hướng dự án và chuyển đến tab Cài đặt xây dựng. Đảm bảo rằng 'Tất cả' được bật (thay vì 'Cơ bản'). Tìm Đường dẫn Tìm kiếm Tiêu đề và đảm bảo rằng nó chứa cả $ (SRCROOT) /../ react-native / React và $ (SRCROOT) /../../ React - đánh dấu cả hai là đệ quy.
  3. Chạy dự án của bạn (Cmd + R)

Sau đó trở lại vùng đất javascript:

Bạn cần nhập các phản ứng-gốc-bàn phím.

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

Sau đó, trong chế độ xem của bạn, hãy thêm một số trạng thái cho không gian bàn phím và cập nhật từ việc nghe các sự kiện bàn phím.

  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,
    };
  },

Cuối cùng, thêm một miếng đệm vào chức năng kết xuất của bạn bên dưới mọi thứ để khi nó tăng kích thước, nó sẽ đẩy nội dung của bạn lên.

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

Cũng có thể sử dụng api hoạt ảnh, nhưng để đơn giản hơn, chúng tôi chỉ điều chỉnh sau hoạt ảnh.


1
Sẽ thật tuyệt khi xem một ví dụ mã / một số thông tin khác về cách thực hiện hoạt ảnh. Bước nhảy khá khó khăn và chỉ làm việc với các phương thức "sẽ hiển thị" và "đã hiển thị", tôi không thể tìm ra cách đoán hoạt ảnh bàn phím sẽ dài bao lâu hoặc nó cao bao nhiêu từ "sẽ hiển thị".
Stephen

2
react-native@0.11.0-rc hiện gửi các sự kiện bàn phím (ví dụ: "keyboardWillShow") thông qua DeviceEventEmitter, vì vậy bạn có thể đăng ký người nghe cho các sự kiện này. Tuy nhiên, khi xử lý ListView, tôi thấy rằng việc gọi scrollTo () trên chế độ xem cuộn của ListView hoạt động tốt hơn: this.listView.getScrollResponder().scrollTo(rowID * rowHeight); Điều này được gọi trên TextInput của một hàng khi nó nhận được sự kiện onFocus.
Jed Lau

4
Câu trả lời này không còn hợp lệ vì RCTDeviceEventEmitter thực hiện công việc.
Sherlock


6

Thử cái này:

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>);
}

...

Nó đã làm việc cho tôi. Về cơ bản, chế độ xem sẽ thu hẹp lại khi bàn phím được hiển thị và phát triển trở lại khi bị ẩn.


Ngoài ra, giải pháp này hoạt động tốt (RN 0,21.0) stackoverflow.com/a/35874233/3346628
pomo

sử dụng this.keyboardWillHide.bind (this) thay vì self
animekun

Sử dụng bàn phím thay vì DeviceEventEmitter
Madura Pradeep


4

Có thể là muộn, nhưng giải pháp tốt nhất là sử dụng thư viện gốc, IQKeyboardManager

Chỉ cần kéo và thả thư mục IQKeyboardManager từ dự án demo sang dự án iOS của bạn. Đó là nó. Ngoài ra, bạn có thể thiết lập một số valus, như isToolbar được bật hoặc khoảng cách giữa nhập văn bản và bàn phím trong tệp AppDelegate.m. Thông tin chi tiết về tùy chỉnh có trong liên kết trang GitHub mà tôi đã thêm.


1
Đây là một lựa chọn tuyệt vời. Ngoài ra, hãy xem github.com/douglasjunior/react-native-keyboard-manager để biết phiên bản dành cho ReactNative - rất dễ cài đặt.
loevborg

3

Tôi đã sử dụng TextInput.onFocus và ScrollView.scrollTo.

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

2

@Stephen

Nếu bạn không bận tâm về việc chiều cao hoạt ảnh với tỷ lệ chính xác như bàn phím xuất hiện, bạn chỉ có thể sử dụng LayoutAnimation, để ít nhất chiều cao không nhảy vào vị trí. ví dụ

nhập LayoutAnimation từ react-native và thêm các phương thức sau vào thành phần của bạn.

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);
  },

Một số hoạt ảnh ví dụ là (tôi đang sử dụng hình ảnh mùa xuân ở trên):

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,
      },
    },
  },
};

CẬP NHẬT:

Xem câu trả lời của @ sherlock bên dưới, kể từ react-native 0.11, việc thay đổi kích thước bàn phím có thể được giải quyết bằng cách sử dụng chức năng tích hợp sẵn.


2

Bạn có thể kết hợp một số phương pháp thành một cái gì đó đơn giản hơn một chút.

Đính kèm trình nghe onFocus trên đầu vào của bạn

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

Phương thức cuộn xuống của chúng tôi trông giống như sau:

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

Điều này cho biết chế độ xem cuộn của chúng tôi (hãy nhớ thêm một số tham chiếu) để cuộn xuống vị trí của đầu vào được tập trung của chúng tôi - 200 (nó gần bằng kích thước của bàn phím)

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

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

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

Ở đây chúng tôi đặt lại chế độ xem cuộn của chúng tôi trở lại đầu trang,

nhập mô tả hình ảnh ở đây


2
@có thể bạn vui lòng cung cấp phương thức render () của mình không?
valerybodak

0

Tôi đang sử dụng một phương pháp đơn giản hơn, nhưng nó chưa hoạt hình. Tôi có một trạng thái thành phần được gọi là "colledUp" mà tôi mặc định là 0, nhưng được đặt thành 1 khi textInput được lấy nét, như sau:

Trên textInput của tôi:

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

Tôi cũng có kiểu cung cấp cho vùng chứa bao gồm mọi thứ trên màn hình đó một lề dưới và lề trên âm, như thế này:

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

Và sau đó trên thùng chứa, tôi đặt các kiểu như thế này:

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

Vì vậy, khi trạng thái "va chạm" được đặt thành 1, kiểu người quản lý vùng đệm sẽ bắt đầu và di chuyển nội dung lên.

Kinda hacky và các lợi nhuận được mã hóa cứng, nhưng nó hoạt động :)


0

Tôi sử dụng câu trả lời brysgo để nâng cao cuối màn hình cuộn của tôi. Sau đó, tôi sử dụng onScroll để cập nhật vị trí hiện tại của chế độ xem cuộn. Sau đó, tôi tìm thấy React Native này: Lấy vị trí của một phần tử để lấy vị trí của textinput. Sau đó, tôi thực hiện một số phép toán đơn giản để tìm xem đầu vào có ở chế độ xem hiện tại hay không. Sau đó, tôi sử dụng scrollTo để chuyển số tiền tối thiểu cộng với một khoản tiền ký quỹ. Nó khá trơn tru. Đây là mã cho phần cuộn:

            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 );
                        }
                     );
                };
            },

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.