Comment faire défiler vers le bas de React Native ListView
j'écris une application en utilisant React Native. Il a un ListView
pour afficher la liste des éléments.
j'ajoute de nouveaux articles en bas quand il y en a de nouveaux disponibles.
J'aimerais faire défiler vers le bas, dans le ListView
pour afficher les nouveaux éléments automatiquement. Comment puis-je faire cela?
11 réponses
en tant Que de Réagir Natif de 0,41 à la fois ListView
et ScrollView
scrollToEnd()
méthodes. ListView
's est documentée à https://facebook.github.io/react-native/docs/listview.html#scrolltoend
Vous aurez besoin d'utiliser un ref
pour stocker une référence à votre ListView
lorsque vous effectuez le rendu:
<ListView
dataSource={yourDataSource}
renderRow={yourRenderingCallback}
ref={listView => { this.listView = listView; }}
</ListView>
Ensuite, vous pouvez simplement appeler this.listView.scrollToEnd()
pour faire défiler vers le bas de la liste. Si vous souhaitez le faire à chaque fois le contenu de la ListView
changements (par exemple, lorsque le contenu est ajouté), puis le faire à partir de l'intérieur de l' ListView
onContentSizeChange
prop rappel, tout comme avec un ScrollView
.
- je résoudre ce ScrollView
. Voici un exemple simple:
class MessageList extends Component {
componentDidUpdate() {
let innerScrollView = this._scrollView.refs.InnerScrollView;
let scrollView = this._scrollView.refs.ScrollView;
requestAnimationFrame(() => {
innerScrollView.measure((innerScrollViewX, innerScrollViewY, innerScrollViewWidth, innerScrollViewHeight) => {
scrollView.measure((scrollViewX, scrollViewY, scrollViewWidth, scrollViewHeight) => {
var scrollTo = innerScrollViewHeight - scrollViewHeight + innerScrollViewY;
if (innerScrollViewHeight < scrollViewHeight) {
return;
}
this._scrollView.scrollTo(scrollTo);
});
});
});
}
render() {
return (
<ScrollView ref={component => this._scrollView = component}>
{this.props.messages.map((message, i) => {
return <Text key={i}>{message}</Text>;
})}
</ScrollView>
);
}
}
Merci @ccheever
j'ai eu le même problème, et est venu avec cette solution:
render()
{
if("listHeight" in this.state &&
"footerY" in this.state &&
this.state.footerY > this.state.listHeight)
{
var scrollDistance = this.state.listHeight - this.state.footerY;
this.refs.list.getScrollResponder().scrollTo(-scrollDistance);
}
return (
<ListView ref="list"
onLayout={(event) => {
var layout = event.nativeEvent.layout;
this.setState({
listHeight : layout.height
});
}}
renderFooter={() => {
return <View onLayout={(event)=>{
var layout = event.nativeEvent.layout;
this.setState({
footerY : layout.y
});
}}></View>
}}
/>
)
}
fondamentalement, je rends un pied de page vide afin de vérifier le décalage de Y de la liste en bas. De ceci je peux dériver le déplacement de rouleau vers le bas, basé sur la hauteur de conteneur de liste.
NOTE: la condition last if vérifie si la longueur du contenu déborde la hauteur de la liste, et ne scrolls si elle déborde. Que vous en ayez besoin ou non dépend de votre conception!
J'espère que cette solution aidera d'autres dans la même position.
FWIW, je n'ai pas envie de l' InvertibleScrollView plugin discuté dans une autre réponse parce qu'il fait une transformation d'échelle sur l'ensemble de la liste, et chaque élément de liste.. qui semble cher!
il y a une solution assez simple à ce problème. Vous pouvez envelopper votre ListView dans un composant Scrollview. Cela fournira toutes les méthodes nécessaires pour déterminer la position inférieure de votre liste.
d'Abord l'enveloppe de votre listView
<ScrollView>
<MyListViewElement />
</ScrollView>
Puis onLayout méthode qui retourne la hauteur du composant (scrollView). et l'enregistrer à l'état.
// add this method to the scrollView component
onLayout={ (e) => {
// get the component measurements from the callbacks event
const height = e.nativeEvent.layout.height
// save the height of the scrollView component to the state
this.setState({scrollViewHeight: height })
}}
Puis onontentsizechange méthode renvoie la hauteur des composants internes (listView). et l'enregistrer à l'état. Cela se produit à chaque fois que vous ajoutez ou supprimez un élément de la liste, ou changez la hauteur. Essentiellement, chaque fois que quelqu'un ajoute un nouveau message à votre liste.
onContentSizeChange={ (contentWidth, contentHeight) => {
// save the height of the content to the state when there’s a change to the list
// this will trigger both on layout and any general change in height
this.setState({listHeight: contentHeight })
}}
pour faire défiler, vous aurez besoin de la méthode scrollTo trouvée dans le ScrollView. Vous pouvez y accéder en le sauvegardant sur le ref dans l'état ainsi.
<ScrollView
ref={ (component) => this._scrollView = component }
…
>
</ScrollView>
Maintenant, vous avez tout ce dont vous avez besoin pour calculer et déclencher votre défiler vers le bas de la liste. Vous pouvez choisir de faire cela n'importe où dans votre composant, je vais l'ajouter à la componentDidUpdate()
donc à chaque fois que le composant est rendu il scrollTo le bas.
componentDidUpdate(){
// calculate the bottom
const bottomOfList = this.state.listHeight - this.state.scrollViewHeight
// tell the scrollView component to scroll to it
this.scrollView.scrollTo({ y: bottomOfList })
}
Et c'est tout. Voici à quoi devrait ressembler votre ScrollView à la fin
<ScrollView
ref={ (component) => this._scrollView = component }
onContentSizeChange={ (contentWidth, contentHeight) => {
this.setState({listHeight: contentHeight })
}}
onLayout={ (e) => {
const height = e.nativeEvent.layout.heigh
this.setState({scrollViewHeight: height })
}}
>
<MyListViewElement />
</ScrollView>
UNE VOIE PLUS SIMPLE J'utilisais un ListView et j'ai trouvé beaucoup plus facile de le faire en utilisant un scrollView, pour des raisons de simplicité, je le recommande. Voici un copie directe du module Mes Messages pour la fonction de défilement vers le bas. Espérons que cela aide.
class Messages extends Component {
constructor(props){
super(props)
this.state = {
listHeight: 0,
scrollViewHeight: 0
}
}
componentDidUpdate(){
this.scrollToBottom()
}
scrollToBottom(){
const bottomOfList = this.state.listHeight - this.state.scrollViewHeight
console.log('scrollToBottom');
this.scrollView.scrollTo({ y: bottomOfList })
}
renderRow(message, index){
return (
<Message
key={message.id}
{...message}
/>
);
}
render(){
return(
<ScrollView
keyboardDismissMode="on-drag"
onContentSizeChange={ (contentWidth, contentHeight) => {
this.setState({listHeight: contentHeight })
}}
onLayout={ (e) => {
const height = e.nativeEvent.layout.height
this.setState({scrollViewHeight: height })
}}
ref={ (ref) => this.scrollView = ref }>
{this.props.messages.map( message => this.renderRow(message) )}
</ScrollView>
)
}
}
export default Messages
s'il vous Plaît vérifier cela: https://github.com/650Industries/react-native-invertible-scroll-view
cette composante inverse la vue de défilement originale. Ainsi, lorsque de nouveaux articles arrivent, ils défilent automatiquement vers le bas.
Toutefois, veuillez noter deux choses
Votre "tableau de données" besoin d'être refaites. Qui est, il faut le
[new_item, old_item]
et tout nouvel article doit être inséré dans le avant la plupart.
bien qu'ils utilisent
ListView
dans leur exemple README, il y a encore quelques défauts quand on utilise ce plugin avecListView
. Au lieu de cela, je vous suggère juste d'utiliser leScrollView
, ce qui fonctionne assez bien.
un exemple pour la vue de défilement inversée:
var MessageList = React.createClass({
propTypes: {
messages: React.PropTypes.array,
},
renderRow(message) {
return <Text>{message.sender.username} : {message.content}</Text>;
},
render() {
return (
<InvertibleScrollView
onScroll={(e) => console.log(e.nativeEvent.contentOffset.y)}
scrollEventThrottle={200}
renderScrollView={
(props) => <InvertibleScrollView {...props} inverted />
}>
{_.map(this.props.messages, this.renderRow)}
</InvertibleScrollView>
);
}
});
j'ai bien peur qu'il n'y ait pas une façon ultra propre de faire ça. Par conséquent, nous le ferons manuellement, ne vous inquiétez pas son facile et son pas salissant.
Etape 1: déclarez ces variables dans votre état
constructor(props) {
super(props);
this.state={
lastRowY:0,
}
}
Etape 2: Créer le scrollToBottom fonction comme ceci:
scrollToBottom(){
if(!!this.state.lastRowY){
let scrollResponder = this.refs.commentList.getScrollResponder();
scrollResponder.scrollResponderScrollTo({x: 0, y: this.state.lastRowY, animated: true});
}
}
Etape 3: Ajouter les propriétés suivantes à votre ListView
:
ref
pour pouvoir y accéder (dans cet exemple,commentList
)ref="commentList"
La suite
onLayout
fonction au sein de l'renderRow, sur votre élément de ligne:onLayout={(event) => { var {y} = event.nativeEvent.layout; this.setState({ lastRowY : y });
ListView
devrait ressembler à quelque chose comme ceci:
<ListView
ref="commentList"
style={styles.commentsContainerList}
dataSource={this.state.commentDataSource}
renderRow={()=>(
<View
onLayout={(event)=>{
let {y} = event.nativeEvent.layout;
this.setState({
lastRowY : y
});
}}
/>
</View>
)}
/>
Etape 4: Puis n'importe où dans votre code, il suffit d'appeler this.scrollToBottom();
.
Profiter..
Pour ceux qui sont intéressés dans la mise en œuvre avec seulement ListView
// initialize necessary variables
componentWillMount() {
// initialize variables for ListView bottom focus
this._content_height = 0;
this._view_height = 0;
}
render() {
return (
<ListView
...other props
onLayout = {ev => this._scrollViewHeight = ev.nativeEvent.layout.height}
onContentSizeChange = {(width, height)=>{
this._scrollContentHeight = height;
this._focusOnBottom();
}}
renderScrollComponent = {(props) =>
<ScrollView
ref = {component => this._scroll_view = component}
onLayout = {props.onLayout}
onContentSizeChange = {props.onContentSizeChange} /> } />
);
}
_focusOnLastMessage() {
const bottom_offset = this._content_height - this._view_height;
if (bottom_offset > 0 &&)
this._scroll_view.scrollTo({x:0, y:scrollBottomOffsetY, false});
}
Vous pouvez utiliser la fonction _focusOnLastMessage où vous voulez, par exemple, je l'utilise chaque fois que la taille du contenu change. J'ai testé les codes avec react-native@0.32
donc pour faire un défilement automatique vers le bas de ListView, vous devriez ajouter prop 'onContentSizeChange' à ListView et prendre le contenu respectif de son argument, comme ceci:
<ListView
ref='listView'
onContentSizeChange={(contentWidth, contentHeight) => {
this.scrollTo(contentHeight);
}}
...
/>
donc pour mon cas, je devrais rendre ma liste verticalement c'est pourquoi j'ai utilisé contentHeight, en cas de liste horizontale vous avez juste eu à utiliser contentWeight.
ici la fonction scrollTo devrait être:
scrollTo = (y) => {
if (y<deviceHeight-120) {
this.refs.listView.scrollTo({ y: 0, animated: true })
}else{
let bottomSpacing = 180;
if (this.state.messages.length > 0) {
bottomSpacing = 120;
}
this.refs.listView.scrollTo({ y: y - deviceHeight + bottomSpacing, animated: true })
}
}
c'est tout. J'espère que mes ce explication pourrait aider quelqu'un pour gagner du temps.
voici ce que j'utilise avec React Native 0.14. Cette fenêtre ListView défile vers le bas à chaque fois que:
- la hauteur du contenu change
- la hauteur du conteneur change
- Le clavier devient visible ou invisible
il reflète le comportement standard de la plupart des applications de chat.
cette implémentation dépend de RN0.14 détails de la mise en œuvre de ListView et pourrait donc avoir besoin d'ajustements pour être compatible avec les futures Réagir versions Natives
var React = require('react-native');
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
var RCTUIManager = require('NativeModules').UIManager;
var {
ListView,
} = React;
export default class AutoScrollListView extends React.Component {
componentWillMount() {
this._subscribableSubscriptions = [];
}
componentDidMount() {
this._addListenerOn(RCTDeviceEventEmitter, 'keyboardWillShow', this._onKeyboardWillShow);
this._addListenerOn(RCTDeviceEventEmitter, 'keyboardWillHide', this._onKeyboardWillHide);
var originalSetScrollContentLength = this.refs.listView._setScrollContentLength;
var originalSetScrollVisibleLength = this.refs.listView._setScrollVisibleLength;
this.refs.listView._setScrollContentLength = (left, top, width, height) => {
originalSetScrollContentLength(left, top, width, height);
this._scrollToBottomIfContentHasChanged();
};
this.refs.listView._setScrollVisibleLength = (left, top, width, height) => {
originalSetScrollVisibleLength(left, top, width, height);
this._scrollToBottomIfContentHasChanged();
};
}
componentWillUnmount() {
this._subscribableSubscriptions.forEach(
(subscription) => subscription.remove()
);
this._subscribableSubscriptions = null;
}
render() {
return
}
_addListenerOn = (eventEmitter, eventType, listener, context) => {
this._subscribableSubscriptions.push(
eventEmitter.addListener(eventType, listener, context)
);
}
_onKeyboardWillShow = (e) => {
var animationDuration = e.duration;
setTimeout(this._forceRecalculationOfLayout, animationDuration);
}
_onKeyboardWillHide = (e) => {
var animationDuration = e.duration;
setTimeout(this._forceRecalculationOfLayout, animationDuration);
}
_forceRecalculationOfLayout = () => {
requestAnimationFrame(() => {
var scrollComponent = this.refs.listView.getScrollResponder();
if (!scrollComponent || !scrollComponent.getInnerViewNode) {
return;
}
RCTUIManager.measureLayout(
scrollComponent.getInnerViewNode(),
React.findNodeHandle(scrollComponent),
() => {}, //Swallow error
this.refs.listView._setScrollContentLength
);
RCTUIManager.measureLayoutRelativeToParent(
React.findNodeHandle(scrollComponent),
() => {}, //Swallow error
this.refs.listView._setScrollVisibleLength
);
});
}
_scrollToBottomIfContentHasChanged = () => {
var scrollProperties = this.refs.listView.scrollProperties;
var hasContentLengthChanged = scrollProperties.contentLength !== this.previousContentLength;
var hasVisibleLengthChanged = scrollProperties.visibleLength !== this.previousVisibleLength;
this.previousContentLength = scrollProperties.contentLength;
this.previousVisibleLength = scrollProperties.visibleLength;
if(!hasContentLengthChanged && !hasVisibleLengthChanged) {
return;
}
this.scrollToBottom();
}
scrollToBottom = () => {
var scrollProperties = this.refs.listView.scrollProperties;
var scrollOffset = scrollProperties.contentLength - scrollProperties.visibleLength;
requestAnimationFrame(() => {
this.refs.listView.getScrollResponder().scrollTo(scrollOffset);
});
}
}
Voici une autre variation. J'utilise personnellement ceci pour faire défiler vers le bas d'une vue de liste quand quelqu'un commente. Je le préfère aux autres exemples car il est plus concis.
listViewHeight peut être déterminé par différents moyens mais je l'obtiens personnellement à partir d'une animation qui est utilisée pour animer la hauteur de vue de liste pour sortir du chemin du clavier.
render() {
let initialListSize = 5
if (this.state.shouldScrollToBottom) {
initialListSize = 100000 // A random number that's going to be more rows than we're ever going to see in the list.
}
return (<ListView
ref="listView"
initialListSize={ initialListSize }
otherProps...
renderFooter={() => {
return <View onLayout={(event)=>{
if (this.state.shouldScrollToBottom) {
const listViewHeight = this.state.height._value
this.refs.listView.scrollTo({y: event.nativeEvent.layout.y - listViewHeight + 64}) // 64 for iOS to offset the nav bar height
this.setState({shouldScrollToBottom: false})
}
}}></View>
}}
/>)
}
scrollToBottom() {
self.setState({shouldScrollToBottom: true})
}
Aimé @ComethTheNerd 's solution meilleure et plus régulière(passé tous airbnb est ESlinting règles) version:
state={
listHeight: 0,
footerY: 0,
}
// dummy footer to ascertain the Y offset of list bottom
renderFooter = () => (
<View
onLayout={(event) => {
this.setState({ footerY: event.nativeEvent.layout.y });
}}
/>
);
...
<ListView
ref={(listView) => { this.msgListView = listView; }}
renderFooter={this.renderFooter}
onLayout={(event) => {
this.setState({ listHeight: event.nativeEvent.layout.height });
}}
/>
et invoquez le scrollToBottom
méthode comme ceci:
componentDidUpdate(prevProps, prevState) {
if (this.state.listHeight && this.state.footerY &&
this.state.footerY > this.state.listHeight) {
// if content is longer than list, scroll to bottom
this.scrollToBottom();
}
}
scrollToBottom = () => {
const scrollDistance = this.state.footerY - this.state.listHeight;
this.msgListView.scrollTo({ y: scrollDistance, animated: true });
}