【问题标题】:React Native FlatList scrollToBottom not workingReact Native FlatList scrollToBottom 不起作用
【发布时间】:2018-01-31 09:21:05
【问题描述】:

大家好,

我正在研究 react-native FlatList,我的问题是我无法滚动到底部。这是我的FlatList 代码:

<FlatList style = { styles.list }
  ref="flatList1"
  data = {
    this.state.conversation_arr
  }
  onContentSizeChange={(contentWidth, contentHeight) => {
    this.flat_list_height = contentHeight;
    // this.scrollNow();
  }}
  onLayout={this.scrollNow()}
  renderItem = { this._renderItem }
  keyExtractor = { item => item.message_id }
/>

scrollNow() {
  console.log("******",this.refs);
  if(this.refs.flatList1){
    this.refs.flatList1.scrollToEnd();
  }
}

当代码到达时

this.refs.flatList1.scrollToEnd();

屏幕上出现错误提示

Type Error, cannot read property -1 of undefined.

这是截图。

cannot read property -1 of undefined 我基本上是在尝试为我的应用程序实现聊天功能,因此一旦加载聊天,我需要滚动到列表底部。

感谢任何形式的帮助。 谢谢

完整代码

import React, {
    Component
} from 'react';
import {
    StyleSheet,
    View,
    Text,
    TextInput,
    ActivityIndicator,
    TouchableOpacity,
    FlatList,
} from 'react-native';

export default class Conversation extends Component {

state = {
    conversation_arr: undefined,
    last_message_loaded: false,
    show_activty_indicator: true,
    message_text : undefined,
    message_text_length : 0
};


params = this.props.navigation.state;

last_evaluated_key = {
    message_id: undefined,
    timestamp: undefined
}
conversation = undefined;
thread_info = undefined;
flat_list_height = 0;


static navigationOptions = ({ navigation }) => ({
    title: navigation.state.params.header_title,
});

constructor(props) {
    super(props);

    this._renderItem = this._renderItem.bind(this);
    this._fetchConversation();
    this.scrollNow = this.scrollNow.bind(this);
    this.handleInputTextContent = this.handleInputTextContent.bind(this);
    this.sendMessage = this.sendMessage.bind(this);

    console.disableYellowBox = true;

    this.thread_info = this.params.params.thread_info;
}

// scrollNow() {
    // console.log("******",this.refs);
    // if(this.refs.flatList1){
    //     // debugger;
    //     this.refs.flatList1.scrollToEnd();
    // }

    // console.log("******", this.flatlist1);
    // if(this.flatlist1){
    //     this.flatlist1.scrollToEnd();
    // }
    // console.log("###", this.flat_list_height);
// }
scrollNow = () => {
    console.log("******", this.flatList1);
    if(this.state.conversation_arr && this.state.conversation_arr.length > 0 && this.flatList1){
        // debugger;

        // setTimeout(() => {
            // console.log("timer over now");
            this.flatList1.scrollToEnd();
        // }, 1000)

        // this.flatList1.scrollToEnd();
        console.log("Scrolling now at length", this.state.conversation_arr.length);

    }



    // !!this.refs["myFlatList"] && this.refs["myFlatList"].scrollToEnd()

}

componentDidUpdate(){
    console.log("componentDidUpdatecomponentDidUpdate")
}

componentWillUnmount(){
    console.log("componentWillUnmountv")
}

render() {
    return (
        <View style={ styles.container }>
            {   
                this.state.show_activty_indicator &&
                <View style={[styles.activity_indicator_container]}>
                    <ActivityIndicator
                        animating = { this.state.show_activty_indicator }
                        size="large"
                        color="#444444" 
                    />
                </View>
            }
            <FlatList style = { styles.list }
                // inverted
                // ref="myFlatList"
                // ref={(ref) => this.flatList1 = ref}
                ref={(ref) => { this.flatList1 = ref; }}
                data = {
                    this.state.conversation_arr
                }
                onContentSizeChange={(contentWidth, contentHeight) => {
                    this.flat_list_height = contentHeight;
                    // this.scrollNow();
                }}
                onLayout={this.scrollNow}
                renderItem = { this._renderItem }
                keyExtractor = { item => item.message_id }
            />

            <View style={ styles.bottom_box }>
                <Text style={ styles.message_length }> {this.state.message_text_length}/160 </Text>
                <View style={ styles.message_box }>
                    <View style={{flex: 3}} >
                        <TextInput
                            style={styles.input_text}
                            value={this.state.message_text}
                            onChangeText={this.handleInputTextContent}
                            placeholder='Type a message here...'
                            underlineColorAndroid='transparent'
                        />
                    </View>
                    <View style={{flex: 1}} >
                        <TouchableOpacity
                            style={styles.send_button} 
                            onPress={this.sendMessage} >
                                <Text style={[styles.label_send]}>SEND</Text>
                        </TouchableOpacity>
                    </View>
                </View>
            </View>
        </View>
    );

}

handleInputTextContent(text){

    if(text.length > 160){
        text = text.substring(0,159)
        this.setState({ message_text : text });
    } else {
        this.setState({ message_text : text });
        this.setState({ message_text_length : text.length });
    }
}

sendMessage(){
    console.log(this.state.message_text);
    if(this.state.message_text && this.state.message_text.length > 0) {
       console.log("Send message now");
    }
}

_renderItem(item, index){
    // console.log(item.item);
    item = item.item;

    let view = (
            <Text style = { [styles.msg_common] }> { item.message_content } </Text>
        )

    return   view ;
}

processPaymentMessages(dataArr) {
    return dataArr;
}

_fetchConversation() {

            new WebApi().fetchThreadConversation().then(result => {
                console.log("resutlresult", result);

                if (result && result.data && result.data.LastEvaluatedKey) {

                    this.last_evaluated_key.message_id = result.data.LastEvaluatedKey.message_id;
                    this.last_evaluated_key.timestamp = result.data.LastEvaluatedKey.timestamp;

                } else {
                    this.setState({
                        last_message_loaded: true
                    });
                    this.last_evaluated_key = null;
                }

                if (!this.conversation) {
                    this.conversation = [];
                }


                for (let i = 0; i < result.data.Items.length; i++) {

                        item = result.data.Items[i];
                        this.conversation.push(item);
                }

                // console.log(this.conversation);
                this.setState({ conversation_arr : this.conversation });
                this.setState({ show_activty_indicator : false });

                console.log(this.state.conversation_arr.length);


            }, error => {
                console.log("web api fetch data error", error);
            })
    }
}
    const styles = StyleSheet.create({
        activity_indicator_container: {
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: '#fff'
    },
container: {
    flex: 1,
    paddingTop: 8,
    backgroundColor:"#fff"
},

list : {
    paddingLeft: 8,
    paddingRight:8,
    marginBottom : 85,
},

gray_item: {
    padding: 10,
    height: 44,
    backgroundColor:"#fff"
},
white_item : {
    padding: 10,
    height: 44,
    backgroundColor:"#f5f5f5"
},

msg_common : {
    flex: 1,
    justifyContent: 'flex-end',
    alignItems: 'flex-end',
},
colored_item_common : {
    padding : 12,
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center'

}, 
item_insider_common : {
    backgroundColor:"#fff",
    fontSize : 16,
    padding : 12,
    borderWidth : 1,
    borderRadius : 8,
    width : "90%",
    fontWeight : "bold",
    textAlign: 'center',

},
purple_item_insider : {
    color : "#7986cb",
    borderColor: "#7986cb",

}, 
green_item_insider : {
    color : "#66bb6a",
    borderColor: "#66bb6a",

},

bottom_box:{
    position: 'absolute',
    left: 0,
    right: 0,
    bottom: 0,
    flex: 1,
},
message_length: {
    height : 20,
    textAlign: 'right', alignSelf: 'stretch'
},
message_box: {
    // position: 'absolute',
    // left: 0,
    // right: 0,
    // bottom: 0,
    flex: 1, 
    flexDirection: "row"
},

input_text: {
    width: '100%',
    height: 60,
    color: '#000',
    backgroundColor:"#eee",
    padding: 16,
},
send_button: {
    alignItems: 'center',
    flex: 1, 
    height: 60,
    padding: 16,
    backgroundColor : '#f1592b'
},
label_send: {
    color: '#fff'
}
  })

【问题讨论】:

  • ref="flatList1" 已被弃用。您应该使用一个函数来获取引用,即ref={(ref) =&gt; this.flatList1 = ref}
  • @Johan 我试过这种方法,但现在错误是cannot read property "scrollToEnd" of undefined
  • 如果你使用该功能,那么你需要使用this.flatlist1.scrollToEnd()而不是this.refs.flatlist1.scrollToEnd()
  • 还是一样 :/ this.flatlist1 未定义
  • 如果它是一个聊天应用程序,应该更容易跟踪消息数。你考虑过scrollToIndex({ index: index, animated: true, viewPosition: 1})吗?

标签: android react-native scroll react-native-flatlist


【解决方案1】:

你需要像这样给 onLayout 一个回调:

onLayout={this.scrollNow}

更新后的代码是:我试过了。它有效。

import React, { Component } from 'react';
import { FlatList, Text } from 'react-native';

export default class App extends Component {
  render() {
    return (
      <FlatList
        ref={(ref) => { this.flatList1 = ref; }}
        data={[0, 0, 0, 0, 0, 0, 0, 0]}
        onContentSizeChange={(contentWidth, contentHeight) => { this.flat_list_height = contentHeight; }}
        onLayout={this.scrollNow}
        renderItem={this.renderItem}
        keyExtractor={item => item.message_id}
      />
    );
  }
  renderItem =() => <Text style={{marginTop: 100}}>Just Some Text</Text>
  scrollNow =() => {
    if (this.flatList1) {
      this.flatList1.scrollToEnd();
    }
  }
}

【讨论】:

  • 感谢您的回答,但我仍然遇到同样的问题,正如我在上面的问题中提到的那样
  • 试过你的代码,现在 Flatlist 滚动但不滚动到底部,它停在屏幕中间
【解决方案2】:

当“this.state.conversation_arr”未定义或为空时,您是否尝试过传递一个空数组。检查 this._renderItem 也很有帮助,因为它试图访问任何参数的 -1。

【讨论】:

    【解决方案3】:

    您的代码中有 2 部分错误。

    1. FlatList 中的 ref 是一个函数,而不是字符串。
    2. 如果不使用箭头函数,FlatList 无法访问 scrollNow() 函数中的“this”。

    我用类似的代码进行了测试。下面的代码应该可以工作。

    import * as React from 'react';
    import { StyleSheet, View, Text, FlatList } from 'react-native';
    export default class Conversation extends React.Component {
      flatList1;
      _interval;
      state = { conversation_arr: [] };
    
      componentDidMount() {
        let buffer = [];
        for (let i = 1; i < 40; i++) {
          buffer.push({ message_id: i, message_content: `I'm the message ${i}` });
        }
    
        this._interval = setInterval(() => {
          this.setState(() => {
            return { conversation_arr: buffer };
          });
          this.flatList1.scrollToIndex({
            index: buffer.length - 1,
            viewPosition: 1,
            viewOffset: 0,
          });
        }, 1000);
      }
    
      componentWillUnmount() {
        clearInterval(this._interval);
      }
    
      render() {
        return (
          <View style={styles.container}>
            <FlatList
              ref={ref => {
                this.flatList1 = ref;
              }}
              data={this.state.conversation_arr}
              renderItem={this._renderItem}
              keyExtractor={item => item.message_id}
              getItemLayout={(data, index) => ({
                length: 40,
                offset: 40 * index,
                index,
              })}
            />
          </View>
        );
      }
    
      _renderItem = ({ item }) => {
        return (
          <View>
            <Text style={styles.msg_common}>{item.message_content}</Text>
          </View>
        );
      };
    }
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
      },
      msg_common: {
        height: 40,
        fontSize: 18,
        paddingHorizontal: 10,
      },
    });
    

    PS:如果您的列表很长,比如说超过 100 个。使用 onLayout 向下滚动可能需要一些时间进行初始渲染,这会提供非常糟糕的用户体验。更好的方法可能是

    1. 在渲染前反转列表
    2. 提供排序按钮并在排序过程中设置onRefresh为true
    3. 提供滚动到底部按钮列表的页脚

    更新: 我注意到 onContentSizeChange 属性不在文档中。 https://facebook.github.io/react-native/docs/flatlist.html#scrolltoindex 您使用的是哪个版本的 React-Native?

    更新 2: 基于您的 TypeScript 中的完整代码。我使用setInterval 来模拟 API 调用的延迟。

    任何异步函数都应该在componentDidMount 中,因为构造函数不会等待。

    getItemLayout 用于帮助 FlatList 计算高度。请注意,这是在数据到达之前设置的。

    【讨论】:

    • 谢谢@LeekInPajama,我会测试你建议的代码,如果它有效,会告诉你。
    • 我试过你建议的代码,但!!that.refs.flatList1 是假的
    • 在哪里初始化this.refs,又在哪里调用this.scrollNow()方法?我在我的 FlatList 中测试了类似的代码。好像没问题。
    • 我不知道this.refs 是否需要在FlatList 组件内部以外的任何地方进行初始化,并且我还在flatlist 中调用this.scrollNow() @i-e onLayout={this.scrollNow()}
    • 我已经用我的完整代码更新了我的问题,在其中创建了FlatList 的类。