flatlist - Cómo desplazarse hasta la parte inferior de React Native ListView
refreshcontrol react native example (11)
A partir de React Native 0.41, tanto ListView
como ScrollView
tienen métodos scrollToEnd()
. ListView
está documentado en https://facebook.github.io/react-native/docs/listview.html#scrolltoend
Deberá usar una ref
para almacenar una referencia a su ListView
cuando lo represente:
<ListView
dataSource={yourDataSource}
renderRow={yourRenderingCallback}
ref={listView => { this.listView = listView; }}
</ListView>
Luego, puede simplemente llamar this.listView.scrollToEnd()
para desplazarse al final de la lista. Si desea hacer esto cada vez que cambia el contenido de ListView
(por ejemplo, cuando se agrega contenido), hágalo desde la función onContentSizeChange
prop de ListView
, al igual que con ScrollView
.
Estoy escribiendo una aplicación usando React Native. Tiene un ListView
para mostrar la lista de elementos.
Añado nuevos elementos en la parte inferior cuando hay nuevos disponibles. Me gustaría desplazarme hacia abajo en el ListView
para mostrar los nuevos elementos automáticamente. ¿Cómo puedo hacer esto?
Aquí hay otra variación. Personalmente estoy usando esto para desplazarme hacia la parte inferior de una vista de lista cuando alguien comenta. Lo prefiero a los otros ejemplos, ya que es más conciso.
listViewHeight se puede determinar a través de varios medios, pero personalmente lo obtengo de una animación que se usa para animar la altura de la vista de lista para que se salga del camino del teclado.
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})
}
Consulte esto: https://github.com/650Industries/react-native-invertible-scroll-view
Este componente invierte la vista de desplazamiento original. Entonces, cuando lleguen nuevos artículos, se desplazará automáticamente a la parte inferior.
Sin embargo, tenga en cuenta dos cosas
Su "matriz de datos" también debe revertirse. Es decir, debería ser
[new_item, old_item]
y cualquier artículo nuevo que llegue se debe insertar en la parte frontal.
Aunque usan
ListView
en su ejemplo de README, todavía hay algunos defectos al usar este complemento conListView
. En cambio, te sugiero que solo usesScrollView
, que funciona bastante bien.
Un ejemplo para la vista de desplazamiento invertido:
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>
);
}
});
Entonces, para hacer un desplazamiento automático al final de ListView, debe agregar prop ''onContentSizeChange'' a ListView y tomar el contenido respectivo de su argumento, como este:
<ListView
ref=''listView''
onContentSizeChange={(contentWidth, contentHeight) => {
this.scrollTo(contentHeight);
}}
...
/>
Por lo tanto, en mi caso, debería presentar mi lista verticalmente, por eso utilicé contentHeight, en el caso de una lista horizontal, solo tenía que usar contentWeight.
Aquí la función scrollTo debería ser:
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 })
}
}
Eso es. Espero que esta explicación pueda ayudar a alguien a ahorrar tiempo.
Esto es lo que uso con React Native 0.14. Este contenedor ListView se desplaza hacia abajo siempre que:
- Los cambios de altura del contenido
- Los cambios de altura del contenedor
- El teclado se vuelve visible o invisible
Refleja el comportamiento estándar de la mayoría de las aplicaciones de chat.
Esta implementación depende de los detalles de implementación de RN0.14 ListView y, por lo tanto, podría necesitar ajustes para ser compatible con las futuras versiones de React Native.
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);
});
}
}
Hay una solución bastante simple para este problema. Puede envolver su ListView dentro de un componente Scrollview. Esto proporcionará todos los métodos necesarios para determinar la posición inferior de su lista.
Primero envuelve tu listaVer
<ScrollView>
<MyListViewElement />
</ScrollView>
Luego use el método onLayout que devuelve la altura del componente (scrollView). y guárdalo en el estado.
// 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 })
}}
A continuación, utilice el método onContentSizeChange que devuelve la altura de los componentes internos (listView). y guárdalo en el estado. Esto sucederá cada vez que agregue o elimine un elemento de la lista, o cambie la altura. Esencialmente cada vez que alguien agrega un nuevo mensaje a su lista.
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 })
}}
Para desplazarse, necesitará el método scrollTo que se encuentra dentro de ScrollView. Puede acceder a esto guardándolo en la ref en el estado como tal.
<ScrollView
ref={ (component) => this._scrollView = component }
…
>
</ScrollView>
Ahora tiene todo lo que necesita para calcular y activar su desplazamiento al final de la lista. Puede elegir hacer esto en cualquier lugar de su componente, lo agregaré a componentDidUpdate()
modo que cada vez que se represente el componente, se scrollTo la parte inferior.
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 })
}
Y eso es. Así es como debería verse tu ScrollView al final
<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>
UNA MANERA MÁS SIMPLE Estaba usando un ListView y me pareció mucho más fácil hacer esto al usar un scrollView, por simplicidad, lo recomiendo. Aquí hay una copia directa de mi módulo de Mensajes para la función de desplazamiento hacia abajo. Espero eso ayude.
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
Me gustó la mejor solución de @ComethTheNerd, y aquí hay una versión más común (pasó todas las reglas de ESlinting de airbnb):
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 });
}}
/>
E invoque el método scrollToBottom
siguiente manera:
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 });
}
Me temo que no hay una manera ultra limpia de hacer esto. Por lo tanto, lo haremos manualmente, no se preocupe, es fácil y no es complicado.
Paso 1 : declare esas variables en su estado
constructor(props) {
super(props);
this.state={
lastRowY:0,
}
}
Paso 2 : crea la función scrollToBottom así:
scrollToBottom(){
if(!!this.state.lastRowY){
let scrollResponder = this.refs.commentList.getScrollResponder();
scrollResponder.scrollResponderScrollTo({x: 0, y: this.state.lastRowY, animated: true});
}
}
Paso 3 : agregue las siguientes propiedades a su ListView
:
Un
ref
para poder acceder a él (en este ejemplo,commentList
)ref="commentList"
La siguiente función
onLayout
dentro de la barra de representación, en su elemento de fila:onLayout={(event) => { var {y} = event.nativeEvent.layout; this.setState({ lastRowY : y });
Tu ListView
debería verse más o menos así:
<ListView
ref="commentList"
style={styles.commentsContainerList}
dataSource={this.state.commentDataSource}
renderRow={()=>(
<View
onLayout={(event)=>{
let {y} = event.nativeEvent.layout;
this.setState({
lastRowY : y
});
}}
/>
</View>
)}
/>
Paso 4 : Luego, en cualquier parte de tu código, simplemente llama a this.scrollToBottom();
.
Disfrutar..
Para aquellos que están interesados en implementarlo simplemente con 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});
}
Puedes usar la función _focusOnLastMessage donde quieras, por ejemplo, la uso siempre que cambie el tamaño del contenido. Probé los códigos con [email protected]
Resuelvo esto para ScrollView
. Aquí hay un ejemplo 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>
);
}
}
Gracias por @ccheever
Tuve el mismo problema, y se me ocurrió esta solución:
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>
}}
/>
)
}
Básicamente, presento un pie de página vacío para determinar el desplazamiento en Y de la lista inferior. De esto puedo derivar el desplazamiento de desplazamiento hacia abajo, según la altura del contenedor de lista.
NOTA: La última condición if comprueba si la longitud del contenido sobrepasa la altura de la lista y solo se desplaza si lo hace. Si lo necesita o no, ¡depende de su diseño!
Espero que esta solución ayude a otros en la misma posición.
FWIW No me InvertibleScrollView complemento InvertibleScrollView discutido en otra respuesta porque hace una transformación de escala en toda la lista, y cada elemento de la lista ... ¡que suena caro!