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?

McG
sumber
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.

Sherlock
sumber
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 .

jauh
sumber
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;
John kendall
sumber
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.

brysgo
sumber
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
[email protected] 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.

pomo
sumber
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.

Adrian Zghibarta
sumber
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);
},
shohey1226
sumber
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.

deanmcpherson
sumber
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

Nath
sumber
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 :)

Stirman
sumber
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 );
                        }
                     );
                };
            },
aintnorest
sumber
0

Saya juga menemui pertanyaan ini. Akhirnya, saya menyelesaikannya dengan menentukan ketinggian setiap adegan, seperti:

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

Dan, saya juga menggunakan modul pihak ketiga https://github.com/jaysoo/react-native-extra-dimensions-android untuk mendapatkan ketinggian yang sebenarnya.

Renguang Dong
sumber