TextInput odağa sahipken pencere klavyenin arkasından nasıl otomatik olarak kaydırılır?


90

Yerel uygulamaların pencereyi otomatik olarak kaydırmasına yönelik bu saldırıyı gördüm, ancak bunu React Native'de yapmanın en iyi yolunu merak ediyorum ... Bir <TextInput>alan odaklandığında ve görünümün altına yerleştirildiğinde klavye metin alanını kaplayacaktır.

Bu sorunu örnek UIExplorer'in görüşünde TextInputExample.jsgörebilirsiniz.

İyi bir çözümü olan var mı?


3
Bunu Github izleyicisine bir sorun olarak eklemenizi ve herhangi bir şey gelip gelmediğini görmenizi öneririm, çünkü bu çok yaygın bir şikayet olacak.
Colin Ramsay

Yanıtlar:


83

2017 Cevap

KeyboardAvoidingViewMuhtemelen şimdi gitmek için en iyi yoldur. Dokümanlara buradan göz atın . KeyboardGeliştiriciye animasyonları gerçekleştirmek için daha fazla kontrol sağlayan modüle kıyasla gerçekten basittir . Spencer Carli , orta blogunda mümkün olan tüm yolları gösterdi .

2015 Cevap

Bunu yapmanın doğru yolu react-nativeharici kitaplıklar gerektirmez, yerel koddan yararlanır ve animasyonlar içerir.

Öncelikle onFocusher biri TextInput(veya kaydırmak istediğiniz herhangi bir bileşen) için olayı işleyecek bir işlev tanımlayı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);
}

Ardından, oluşturma işlevinizde:

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

Bu, RCTDeviceEventEmitterfor klavye olaylarını ve boyutlandırmayı kullanır RCTUIManager.measureLayout, kullanarak bileşenin konumunu ölçer ve gereken tam kaydırma hareketini hesaplar scrollResponderInputMeasureAndScrollToKeyboard.

additionalOffsetÖzel kullanıcı arayüzü tasarımınızın ihtiyaçlarına uyması için parametre ile oynamak isteyebilirsiniz .


5
Bu güzel bir keşif, ancak benim için yeterli değildi, çünkü ScrollView TextInput'un ekranda olmasını sağlarken, ScrollView hala klavyenin altında kullanıcının kaydıramadığı içeriği gösteriyordu. "KeyboardDismissMode = on-drag" ScrollView özelliğinin ayarlanması, kullanıcının klavyeyi kapatmasına izin verir, ancak klavyenin altında yeterli kaydırma içeriği yoksa, deneyim biraz sarsıcıdır. ScrollView yalnızca klavye nedeniyle kaydırmak zorundaysa ve zıplamayı devre dışı bırakırsanız, klavyeyi
kapatmanın

2
@ miracle2k - Bir giriş bulanık olduğunda, yani klavye kapandığında kaydırma görünümü konumunu sıfırlayan bir işlevim var. Belki bu senin durumunda yardımcı olabilir?
Sherlock

2
@Sherlock Bu bulanık kaydırma görünümü sıfırlama işlevi neye benziyor? Bu arada harika bir çözüm :)
Ryan McDermott

8
Daha yeni React Native sürümlerinde şunları çağırmanız gerekir: * ReactNative'i 'react-native' içinden içe aktarın; * aramadan önce * ReactNative.findNodeHandle () * Aksi takdirde uygulama
çökecektir

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

26

Facebook açık kaynaklı KeyboardAvoidingView , bu sorunu çözmek için yerel 0.29'a tepki veriyor. Belgeler ve kullanım örneği burada bulunabilir .


33
KeyboardAvoidingView'e dikkat edin, kullanımı kolay değil. Her zaman olması gerektiği gibi davranmaz. Belgeleme pratikte yok.
Renato Geri

doc ve davranış şimdi daha iyi hale geliyor
antoine129

Sorun şu ki, KeyboardAvoidingView klavye yüksekliğini iPhone 6 simülatörümde 65 olarak ölçüyor ve bu yüzden görüşüm hala klavyenin arkasında gizli.
Marc

Yönetebilmemin tek yolu, şu şekilde tetiklenen bir dip kaplama yaklaşımıydıDeviceEventEmitter.addListener('keyboardDidShow', this.keyboardDidShow.bind(this));
Marc

12

TextInput öğeleriyle herhangi bir View etrafına sarılabilen bir KeyboardHandler bileşeni oluşturmak için, react-native-keyboard-spacer kod formunun bir kısmını ve @Sherlock kodunu birleştirdik. Tıkır tıkır çalışıyor! :-)

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

Klavyenin bir iOS simülatöründe görünmesini engelleyecek kolay / açık bir şey var mı?
seigel

1
Command + K'yi (Hardware-> Keyboard-> Toggle Software Keboard) denediniz mi?
John kendall

Bunun değiştirilmiş versiyonunu burada deneyin: gist.github.com/dbasedow/f5713763802e27fbde3fc57a600adcd3 Bunun daha iyi olduğuna inanıyorum çünkü kırılgan imo olduğunu düşündüğüm herhangi bir zaman aşımına dayanmıyor.
CoderDave

10

Öncelikle react-native-keyboardevents'ı yüklemeniz gerekir .

  1. XCode'da, proje gezgininde Kitaplıklar'a sağ tıklayın ➜ Dosyaları [projenizin adına] ekleyin node_modules'e gidin ➜ react-native-keyboardevents ve .xcodeproj dosyasını ekleyin
  2. XCode'da, proje gezgininde projenizi seçin. Keyboardevents projesinden lib * .a'yı projenizin Derleme Aşamalarına ekleyin ➜ İkiliyi Kitaplıklarla Bağlayın Proje gezgininde daha önce eklediğiniz .xcodeproj dosyasına tıklayın ve Yapı Ayarları sekmesine gidin. "Tümü" seçeneğinin açık olduğundan emin olun ("Temel" yerine). Başlık Arama Yollarını arayın ve hem $ (SRCROOT) /../ react-native / React hem de $ (SRCROOT) /../../ React içerdiğinden emin olun - her ikisini de özyinelemeli olarak işaretleyin.
  3. Projenizi çalıştırın (Cmd + R)

Ardından javascript alanına geri dönün:

React-native-klavye olaylarını içe aktarmanız gerekir.

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

Ardından, görünümünüzde klavye alanı için bir durum ekleyin ve klavye olaylarını dinleyerek güncelleme yapın.

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

Son olarak, oluşturma işlevinize her şeyin altına bir boşluk ekleyerek boyutu artırdığında öğelerinizi yukarı kaldırır.

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

Animasyon api'sini kullanmak da mümkündür, ancak basitlik uğruna sadece animasyondan sonra ayarlıyoruz.


1
Bir kod örneği / animasyonun nasıl yapılacağına dair daha fazla bilgi görmek harika olurdu. Atlama oldukça sarsıcı ve sadece "gösterecek" ve "gösterildi" yöntemleriyle çalışırken, klavye animasyonunun ne kadar süreceğini veya "gösterecek" ne kadar uzun olacağını tahmin edemiyorum.
Stephen

2
react-native@0.11.0-rc artık DeviceEventEmitter aracılığıyla klavye olaylarını (ör. "keyboardWillShow") gönderiyor, böylece bu olaylar için dinleyiciler kaydedebilirsiniz. Ancak ListView ile uğraşırken, ListView'ın kaydırma görünümünde scrollTo () çağrısının daha iyi çalıştığını buldum: this.listView.getScrollResponder().scrollTo(rowID * rowHeight); Bu, bir onFocus olayı aldığında satırın TextInput'unda çağrılır.
Jed Lau

4
RCTDeviceEventEmitter işi yaptığı için bu yanıt artık geçerli değildir.
Sherlock


6

Bunu dene:

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

...

Benim için çalıştı. Görünüm, klavye görüntülendiğinde küçülür ve gizlendiğinde yeniden büyür.


Ayrıca, bu çözüm iyi çalışıyor (RN 0.21.0) stackoverflow.com/a/35874233/3346628
pomo

self yerine this.keyboardWillHide.bind (this) kullanın
animekun

Bunun yerine Klavyeyi kullanın DeviceEventEmitter
Madura Pradeep


4

Belki geç kalmış olabilir, ancak en iyi çözüm yerel bir kitaplık, IQKeyboardManager kullanmaktır

Demo projesinden IQKeyboardManager dizinini iOS projenize sürükleyip bırakmanız yeterlidir. Bu kadar. Ayrıca, AppDelegate.m dosyasında isToolbar etkinleştirildiği gibi bir miktar değer veya metin girişi ile klavye arasındaki boşluk ayarlayabilirsiniz. Özelleştirme hakkında daha fazla ayrıntı, eklediğim GitHub sayfası bağlantısında.


1
Bu mükemmel bir seçenektir. ReactNative için paketlenmiş bir sürüm için github.com/douglasjunior/react-native-keyboard-manager adresine de bakın - kurulumu kolaydır.
loevborg

3

TextInput.onFocus ve ScrollView.scrollTo kullandım.

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

2

@Evleneceksen

Yüksekliğin klavyenin göründüğü hızda hareket etmesini istemiyorsanız, en azından yüksekliğin yerine sıçramaması için LayoutAnimation'ı kullanabilirsiniz. Örneğin

LayoutAnimation'ı react-native'den içe aktarın ve bileşeninize aşağıdaki yöntemleri ekleyin.

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

Bazı örnek animasyonlar (yukarıdaki yayı kullanıyorum):

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

GÜNCELLEME:

Aşağıdaki @ sherlock'un cevabına bakın, react-native 0.11'den itibaren klavyenin yeniden boyutlandırılması yerleşik işlevsellik kullanılarak çözülebilir.


2

Yöntemlerden birkaçını biraz daha basit bir şekilde birleştirebilirsiniz.

Girişlerinize bir onFocus dinleyicisi ekleyin

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

Aşağı kaydırma yöntemimiz şuna benzer:

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

Bu, kaydırma görünümümüze (bir ref eklemeyi unutmayın), odaklanmış girdimizin - 200 (kabaca klavye boyutunda) konumuna ilerlemesini söyler

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

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

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

Burada kaydırma görünümümüzü en üste sıfırlıyoruz,

görüntü açıklamasını buraya girin


2
@ Lütfen render () metodunuzu sağlar mısınız?
valerybodak

0

Daha basit bir yöntem kullanıyorum, ancak henüz canlandırılmadı. Varsayılan olarak 0 olarak ayarladığım, ancak textInput odaklandığında 1 olarak ayarladığım "bumpedUp" adlı bir bileşen durumum var, örneğin:

Benim textInput'umda:

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

Ayrıca, o ekrandaki her şeyin sarma kabına bir alt kenar boşluğu ve negatif üst kenar boşluğu veren bir stile sahibim, şöyle:

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

Ve sonra sarma kabında stilleri şu şekilde ayarlıyorum:

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

Bu nedenle, "bumpedUp" durumu 1 olarak ayarlandığında, bumped container stili devreye girer ve içeriği yukarı taşır.

Biraz hacky ve kenar boşlukları kodlanmış, ancak işe yarıyor :)


0

Kaydırma görünümümün alt kısmını yükseltmek için brysgo cevabını kullanıyorum. Ardından, kaydırma görünümünün geçerli konumunu güncellemek için onScroll'u kullanıyorum. Sonra bu React Native'i buldum: Metin girişinin konumunu almak için bir öğenin konumunu alma. Daha sonra girdinin mevcut görünümde olup olmadığını anlamak için bazı basit matematik işlemleri yaparım. Ardından minimum miktarı artı bir marjı taşımak için scrollTo kullanıyorum. Oldukça pürüzsüz. Kaydırma kısmının kodu şöyledir:

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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.