Как прокрутить до нижней части React Native ListView
Я пишу приложение с помощью React Native. Он имеет ListView
, чтобы отобразить список элементов.
Я добавляю новые элементы внизу, когда есть новые.
Я хотел бы прокрутить вниз в ListView
, чтобы автоматически показывать новые элементы. Как я могу это сделать?
Ответы
Ответ 1
Начиная с React Native 0.41, ListView
и ScrollView
имеют scrollToEnd()
. ListView
документирован по адресу https://facebook.github.io/react-native/docs/listview.html#scrolltoend.
Вы должны будете использовать ref
, чтобы сохранить ссылку на ваш ListView
, когда вы сделать его:
<ListView
dataSource={yourDataSource}
renderRow={yourRenderingCallback}
ref={listView => { this.listView = listView; }}
</ListView>
Затем вы можете просто вызвать this.listView.scrollToEnd()
чтобы прокрутить до конца списка. Если вы хотите делать это каждый раз, когда изменяется содержимое ListView
(например, при добавлении контента), то делайте это из обратного вызова ListView
onContentSizeChange
prop, как в ScrollView
.
Ответ 2
Я решаю это для ScrollView
. Вот простой пример:
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>
);
}
}
Спасибо за @ccheever
Ответ 3
Theres довольно простое решение этой проблемы. Вы можете обернуть свой ListView внутри компонента Scrollview. Это обеспечит все необходимые методы для определения нижней позиции вашего списка.
Сначала оберните ваш listView
<ScrollView>
<MyListViewElement />
</ScrollView>
Затем используйте метод onLayout, который возвращает высоту компонента (scrollView). и сохранить его в состояние.
// 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 })
}}
Затем используйте метод onContentSizeChange, который возвращает высоту внутренних компонентов (listView). и сохранить его в состояние. Это будет происходить каждый раз, когда вы добавляете, удаляете элемент из списка или изменяете высоту. Практически каждый раз, когда кто-то добавляет новое сообщение в ваш список.
onContentSizeChange={ (contentWidth, contentHeight) => {
// save the height of the content to the state when theres a change to the list
// this will trigger both on layout and any general change in height
this.setState({listHeight: contentHeight })
}}
Для прокрутки вам понадобится метод scrollTo, найденный внутри ScrollView. Вы можете получить к нему доступ, сохранив его в ссылке в таком состоянии.
<ScrollView
ref={ (component) => this._scrollView = component }
…
>
</ScrollView>
Теперь у вас есть все, что вам нужно рассчитать и запустить прокрутку внизу списка. Вы можете сделать это в любом месте вашего компонента, я добавлю его в componentDidUpdate()
чтобы при каждом отображении компонента он прокручивался вниз.
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 })
}
И это все. Вот так должен выглядеть ваш ScrollView в конце
<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>
ПРОСТОЙ ПУТЬ Я использовал ListView и обнаружил, что сделать это проще, используя scrollView, для простоты, я рекомендую его. Вот прямая копия моего модуля сообщений для функции прокрутки до дна. Надеюсь, поможет.
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
Ответ 4
У меня была та же проблема, и я придумал это решение:
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>
}}
/>
)
}
В принципе, я делаю пустой нижний колонтитул, чтобы установить смещение Y нижней части списка. Из этого я могу получить смещение прокрутки вниз, основываясь на высоте контейнера списка.
ПРИМЕЧАНИЕ.. Последнее условие if проверяет, переполняет ли длина содержимого высоту списка и только прокручивает, если это произойдет. Нужно ли вам это или нет, зависит от вашего дизайна!
Надеемся, что это решение поможет другим в том же положении.
FWIW Мне не понравился плагин InvertibleScrollView, обсуждаемый в другом ответе, потому что он выполняет масштабное преобразование во всем списке и каждом элементе списка. который звучит дорого!
Ответ 5
Пожалуйста, проверьте это:
https://github.com/650Industries/react-native-invertible-scroll-view
Этот компонент инвертирует исходный вид прокрутки. Поэтому, когда появляются новые предметы, он автоматически прокручивается до нижней части.
Однако обратите внимание на две вещи.
-
Ваш "массив данных" также должен быть возвращен. То есть, это должно быть
[new_item, old_item]
и любой новый элемент должен быть вставлен на передний край.
-
Хотя они используют ListView
в своем примере README, все еще есть некоторые недостатки при использовании этого плагина с ListView
. Вместо этого я предлагаю вам просто использовать ScrollView
, который работает очень хорошо.
Пример перевернутого вида прокрутки:
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>
);
}
});
Ответ 6
Я боюсь, что это не ультра чистый способ сделать это. Поэтому мы будем делать это вручную, не волнуйтесь, его легко и не беспорядочно.
Шаг 1. Объявите эти переменные в своем состоянии.
constructor(props) {
super(props);
this.state={
lastRowY:0,
}
}
Шаг 2. Создайте функцию scrollToBottom следующим образом:
scrollToBottom(){
if(!!this.state.lastRowY){
let scrollResponder = this.refs.commentList.getScrollResponder();
scrollResponder.scrollResponderScrollTo({x: 0, y: this.state.lastRowY, animated: true});
}
}
Шаг 3: добавьте следующие свойства в ListView
:
-
A ref
, чтобы иметь к нему доступ (в этом примере commentList
)
ref="commentList"
-
Следующая функция onLayout
внутри renderRow в элементе строки:
onLayout={(event) => {
var {y} = event.nativeEvent.layout;
this.setState({
lastRowY : y
});
Ваш ListView
должен выглядеть примерно так:
<ListView
ref="commentList"
style={styles.commentsContainerList}
dataSource={this.state.commentDataSource}
renderRow={()=>(
<View
onLayout={(event)=>{
let {y} = event.nativeEvent.layout;
this.setState({
lastRowY : y
});
}}
/>
</View>
)}
/>
Шаг 4. Затем в любом месте вашего кода просто вызовите this.scrollToBottom();
.
Enjoy..
Ответ 7
Для тех, кто заинтересован в его реализации с помощью 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});
}
Вы можете использовать функцию _focusOnLastMessage везде, где хотите, например, я использую ее всякий раз, когда изменяется размер содержимого. Я проверил коды с помощью [email protected]
Ответ 8
Таким образом, чтобы сделать автоматическую прокрутку до конца ListView, вы должны добавить prop 'onContentSizeChange' в ListView и взять соответствующий контент из его аргумента, например так:
<ListView
ref='listView'
onContentSizeChange={(contentWidth, contentHeight) => {
this.scrollTo(contentHeight);
}}
...
/>
Так что для моего случая я должен визуализировать мой список по вертикали, поэтому я использовал contentHeight, в случае горизонтального перечисления вы просто должны использовать contentWeight.
Здесь функция scrollTo должна быть:
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 })
}
}
Это. Я надеюсь, что это объяснение поможет кому-то сэкономить время.
Ответ 9
расширенный от @jonasb попробуйте этот код ниже, используйте событие onContentSizeChange для выполнения метода scrollToEnd
<ListView
ref={ ( ref ) => this.scrollView = ref }
dataSource={yourDataSource}
renderRow={yourRenderingCallback}
onContentSizeChange={ () => {
this.scrollView.scrollToEnd( { animated: true })
}}
</ListView>
Ответ 10
Я объединил самые простые решения, которые помогли мне.
При этом ScrollView прокручивается вниз не только при изменении содержимого, но и при изменении высоты. Только проверено с IOS.
<ScrollView
ref="scrollView"
onContentSizeChange={(width, height) =>
this.refs.scrollView.scrollTo({ y: height })
}
onLayout={e => {
const height = e.nativeEvent.layout.height;
this.refs.scrollView.scrollTo({ y: height });
}
}
>
......
</ScrollView>
"реакция-натив": "0.59.10",
Ответ 11
Вот что я использую с React Native 0.14. Эта оболочка ListView прокручивается вниз, когда:
- Изменение высоты содержимого
- Изменение высоты контейнера
- Клавиатура становится видимой или невидимой.
Он отражает стандартное поведение большинства чат-приложений.
Эта реализация зависит от деталей реализации RN0.14 ListView и, следовательно, может потребоваться корректировка для совместимости с будущими версиями 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);
});
}
}
Ответ 12
Здесь другой вариант. Я лично использую это для прокрутки до нижней части списка, когда кто-то комментирует. Я предпочитаю его другим примерам, поскольку он более краток.
listViewHeight может быть определен различными способами, но я лично получаю его из анимации, которая использовалась для анимации высоты списка, чтобы убрать клавиатуру.
render() {
let initialListSize = 5
if (this.state.shouldScrollToBottom) {
initialListSize = 100000 // A random number that 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})
}
Ответ 13
Лучшее решение для Liked @ComethTheNerd, и здесь более регулярное (прошло все правила ESLinting 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 });
}}
/>
И вызовите метод scrollToBottom
следующим образом:
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 });
}
Ответ 14
Это все, что вам нужно
componentDidUpdate: function() {
this._scrollToBottom();
},
_scrollToBottom: function() {
var ul = this.refs.messageList;
ul.scrollTop = ul.scrollHeight;
},