Bagaimana cara menggeser jendela secara otomatis dari belakang keyboard saat TextInput memiliki fokus?


90

Saya telah melihat peretasan ini untuk aplikasi asli untuk menggulir jendela secara otomatis, tetapi bertanya-tanya cara terbaik untuk melakukannya di React Native ... Ketika sebuah <TextInput>bidang mendapat fokus dan diposisikan rendah dalam tampilan, keyboard akan menutupi bidang teks.

Anda dapat melihat masalah ini dalam contoh tampilan UIExplorer TextInputExample.js.

Apakah ada yang punya solusi bagus?


3
Saya sarankan menambahkan ini sebagai masalah di pelacak Github dan melihat apakah ada yang terjadi, karena ini akan menjadi keluhan yang sangat umum.
Colin Ramsay

Jawaban:


83

Jawaban 2017

Ini KeyboardAvoidingViewmungkin cara terbaik untuk pergi sekarang. Lihat dokumennya di sini . Ini sangat sederhana dibandingkan dengan Keyboardmodul yang memberi pengembang lebih banyak kontrol untuk melakukan animasi. Spencer Carli mendemonstrasikan semua cara yang mungkin di blog medianya .

2015 Jawaban

Cara yang benar untuk melakukannya react-nativetidak memerlukan pustaka eksternal, memanfaatkan kode asli, dan menyertakan animasi.

Pertama tentukan fungsi yang akan menangani onFocusacara untuk masing-masing TextInput(atau komponen lain yang ingin Anda gulirkan):

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

Kemudian, dalam fungsi render Anda:

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

Ini menggunakan RCTDeviceEventEmitteruntuk acara dan ukuran keyboard, mengukur posisi komponen menggunakan RCTUIManager.measureLayout, dan menghitung gerakan gulir tepat yang diperlukan di scrollResponderInputMeasureAndScrollToKeyboard.

Anda mungkin ingin bermain-main dengan additionalOffsetparameter, agar sesuai dengan kebutuhan desain UI spesifik Anda.


5
Ini adalah penemuan yang bagus, tetapi bagi saya itu tidak cukup, karena meskipun ScrollView akan memastikan TextInput ada di layar, ScrollView masih menampilkan konten di bawah keyboard yang tidak dapat digulir pengguna. Menyetel properti ScrollView "keyboardDismissMode = on-drag" memungkinkan pengguna menutup keyboard, tetapi jika tidak ada cukup konten gulir di bawah keyboard, pengalaman akan sedikit mengganggu. Jika ScrollView hanya harus menggulir karena keyboard di tempat pertama, dan Anda menonaktifkan pemantulan maka sepertinya tidak ada cara untuk menutup keyboard & menampilkan konten di bawah
miracle2k

2
@ miracle2k - Saya memiliki fungsi yang menyetel ulang posisi tampilan gulir saat input dikaburkan, yaitu saat keyboard menutup. Mungkin ini bisa membantu dalam kasus Anda?
Sherlock

2
@Sherlock Seperti apa fungsi reset tampilan gulir buram itu? Solusi luar biasa dengan cara :)
Ryan McDermott

8
Dalam versi React Native yang lebih baru Anda harus memanggil: * import ReactNative dari 'react-native'; * sebelum memanggil * ReactNative.findNodeHandle () * Jika tidak, aplikasi akan mogok
amirfl

6
Sekarang import {findNodeHandle} from 'react-native' stackoverflow.com/questions/37626851/…
antoine129

26

Facebook open source KeyboardAvoidingView di react native 0.29 untuk mengatasi masalah ini. Dokumentasi dan contoh penggunaan dapat ditemukan di sini .


32
Waspadalah terhadap KeyboardAvoidingView, ini tidak mudah digunakan. Itu tidak selalu berperilaku seperti yang Anda harapkan. Dokumentasi praktis tidak ada.
Renato Kembali

dokter dan perilaku menjadi lebih baik sekarang
antoine129

Masalah yang saya miliki, adalah KeyboardAvoidingView mengukur ketinggian keyboard sebagai 65 pada simulator iPhone 6 saya sehingga pandangan saya masih tersembunyi di balik keyboard.
Marc

-Satunya cara aku bisa manajer itu melalui pendekatan bottompadding dipicu olehDeviceEventEmitter.addListener('keyboardDidShow', this.keyboardDidShow.bind(this));
Marc

12

Kami menggabungkan beberapa bentuk kode react-native-keyboard-spacer dan kode dari @Sherlock untuk membuat komponen KeyboardHandler yang dapat digabungkan dengan setiap View dengan elemen TextInput. Bekerja seperti pesona! :-)

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

Adakah yang mudah / jelas yang akan mencegah keyboard ditampilkan di simulator iOS?
seigel

1
Apakah Anda mencoba Command + K (Hardware-> Keyboard-> Toggle Software Keboard)?
John kendall

Coba versi modifikasinya di sini: gist.github.com/dbasedow/f5713763802e27fbde3fc57a600adcd3 Saya yakin ini lebih baik karena tidak bergantung pada batas waktu yang menurut saya rapuh.
CoderDave

10

Pertama, Anda perlu menginstal react-native-keyboardevents .

  1. Di XCode, di navigator proyek, klik kanan Libraries ➜ Add Files to [nama proyek Anda] Buka node_modules ➜ react-native-keyboardevents dan tambahkan file .xcodeproj
  2. Di XCode, di navigator proyek, pilih proyek Anda. Tambahkan lib * .a dari proyek keyboardevents ke Fase Build proyek Anda ➜ Tautkan Biner Dengan Perpustakaan Klik file .xcodeproj yang Anda tambahkan sebelumnya di navigator proyek dan buka tab Build Settings. Pastikan 'Semua' diaktifkan (bukan 'Dasar'). Cari Jalur Pencarian Header dan pastikan itu berisi $ (SRCROOT) /../ react-native / React dan $ (SRCROOT) /../../ React - tandai keduanya sebagai rekursif.
  3. Jalankan proyek Anda (Cmd + R)

Kemudian kembali ke tanah javascript:

Anda perlu mengimpor react-native-keyboardevents.

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

Kemudian dalam tampilan Anda, tambahkan beberapa status untuk ruang keyboard dan perbarui dari mendengarkan acara keyboard.

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

Terakhir, tambahkan spacer ke fungsi render Anda di bawah semuanya sehingga ketika itu meningkatkan ukuran, itu akan meningkatkan barang Anda.

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

Dimungkinkan juga untuk menggunakan api animasi, tetapi demi kesederhanaan kita hanya menyesuaikan setelah animasi.


1
Akan sangat luar biasa untuk melihat contoh kode / beberapa info lebih lanjut tentang cara melakukan animasi. Lompatannya cukup tersendat, dan bekerja hanya dengan metode "akan menunjukkan" dan "memang menunjukkan", saya tidak tahu bagaimana menebak berapa lama animasi keyboard akan atau berapa tinggi dari "akan ditampilkan".
Stephen

2
react-native@0.11.0-rc sekarang mengirimkan peristiwa keyboard (misalnya, "keyboardWillShow") melalui DeviceEventEmitter, sehingga Anda dapat mendaftarkan listener untuk peristiwa ini. Namun, ketika berurusan dengan ListView, saya menemukan bahwa memanggil scrollTo () pada scrollview ListView bekerja lebih baik: this.listView.getScrollResponder().scrollTo(rowID * rowHeight); Ini dipanggil pada TextInput baris saat menerima peristiwa onFocus.
Jed Lau

4
Jawaban ini tidak lagi valid, karena RCTDeviceEventEmitter melakukan tugasnya.
Sherlock


6

Coba ini:

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

...

Itu berhasil untuk saya. Tampilan pada dasarnya menyusut saat keyboard ditampilkan, dan tumbuh kembali saat disembunyikan.


Selain itu, solusi ini bekerja dengan baik (RN 0.21.0) stackoverflow.com/a/35874233/3346628
pomo

gunakan this.keyboardWillHide.bind (this) daripada self
animekun

Gunakan Keyboard sebagai gantinya DeviceEventEmitter
Madura Pradeep


4

Mungkin terlambat, tetapi solusi terbaik adalah menggunakan pustaka asli, IQKeyboardManager

Cukup seret dan lepas direktori IQKeyboardManager dari proyek demo ke proyek iOS Anda. Itu dia. Anda juga dapat mengatur beberapa valus, seperti isToolbar diaktifkan, atau spasi antara input teks dan keyboard di file AppDelegate.m. Detail lebih lanjut tentang penyesuaian ada di tautan halaman GitHub yang telah saya tambahkan.


1
Ini adalah opsi yang sangat baik. Lihat juga github.com/douglasjunior/react-native-keyboard-manager untuk versi yang dibungkus untuk ReactNative - mudah dipasang.
loevborg

3

Saya menggunakan TextInput.onFocus dan ScrollView.scrollTo.

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

2

@Fenny_fenny

Jika Anda tidak keberatan tidak menganimasikan ketinggian pada kecepatan yang sama persis dengan kemunculan keyboard, Anda dapat menggunakan LayoutAnimation, sehingga setidaknya ketinggian tidak melompat ke tempatnya. misalnya

import LayoutAnimation dari react-native dan tambahkan metode berikut ke komponen Anda.

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

Beberapa contoh animasi adalah (Saya menggunakan pegas di atas):

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

MEMPERBARUI:

Lihat jawaban @ sherlock di bawah ini, mulai react-native 0.11, pengubahan ukuran keyboard dapat diselesaikan menggunakan fungsionalitas bawaan.


2

Anda dapat menggabungkan beberapa metode menjadi sesuatu yang sedikit lebih sederhana.

Lampirkan pendengar onFocus pada masukan Anda

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

Metode gulir ke bawah kami terlihat seperti:

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

Ini memberi tahu tampilan gulir kami (ingat untuk menambahkan referensi) untuk menggulir ke bawah ke posisi input terfokus kami - 200 (kira-kira seukuran keyboard)

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

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

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

Di sini kami mengatur ulang tampilan gulir kami kembali ke atas,

masukkan deskripsi gambar di sini


2
@ Bisakah Anda memberikan metode render ()?
valerybodak

0

Saya menggunakan metode yang lebih sederhana, tetapi ini belum beranimasi. Saya memiliki status komponen yang disebut "bumpedUp" yang defaultnya adalah 0, tetapi disetel ke 1 saat textInput mendapatkan fokus, seperti ini:

Di teks sayaInput:

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

Saya juga memiliki gaya yang memberi wadah pembungkus semua yang ada di layar itu margin bawah dan margin atas negatif, seperti ini:

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

Dan kemudian pada wadah pembungkus, saya mengatur gaya seperti ini:

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

Jadi, saat status "bumpedUp" disetel ke 1, gaya bumpedcontainer berlaku dan memindahkan konten ke atas.

Agak hacky dan marginnya di-hardcode, tetapi berfungsi :)


0

Saya menggunakan jawaban brysgo untuk menaikkan bagian bawah scrollview saya. Kemudian saya menggunakan onScroll untuk memperbarui posisi scrollview saat ini. Saya kemudian menemukan ini React Native: Mendapatkan posisi elemen untuk mendapatkan posisi input teks. Saya kemudian melakukan beberapa matematika sederhana untuk mengetahui apakah input ada dalam tampilan saat ini. Lalu saya menggunakan scrollTo untuk memindahkan jumlah minimum ditambah margin. Cukup mulus. Berikut kode untuk bagian bergulir:

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

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.