flutter instant messaging chat pull-down refresh more message ideas and code implementation

1, Review of ideas
First of all, the chat area is a scrollable interface as a whole. Our demand is to obtain a new chat record through the user's pull-down gesture and add it to the top of the original chat record to create a non sensory connection experience for the user. After loading or loading, the user should be prompted for information.

That is, the idea can be divided into the following steps:
1. In a limited height, twenty chat records are loaded to form a scrollable area
2. When sliding to the [top] of the scroll area, call the interface that triggers the acquisition of 20 new messages, and there is a prompt text at the top of the process, "loading..."
3. After the interface obtains the data, the data shall be spliced into the original data after necessary processing, and there shall be sufficient space at the top for the new data. If there are no more 20 new messages, change the prompt text to "loaded display all messages".

However, if the above is implemented with a normal ListView, there will be a problem. When new data appears and the data is inserted into the position before article 0 through the insertAll method, there will be the effect of new data replacing the old data. As for why this happens, I guess it is because when we slide to the top, we do not leave a new distance between the top and the first message, resulting in the rolling distance still staying at the top, but the last 20 items of data have become new 20 items, so there will be a [replacement] effect, but what we need is a splicing effect.

So I found a solution on the Internet (after all, I didn't think of it myself...), and when I found it, I still felt that everyone was smart... But I still wanted to straighten out the whole idea myself
Original address: https://cloud.tencent.com/developer/article/1647244

2, The correct and final realization idea is:
Listview is used, and the list scrolling of the flitter also has the attribute of reverse, that is, the whole list can be rotated and reversed, so that our bottom becomes the top. The top is actually the bottom of the list. When new data is spliced, a part of the distance will be automatically reserved below, so as not to cause the effect of replacement.
Specific ideas:
1. When the overall history message is not empty, return the scrollable area and wrap the Listview with ScrollConfiguration to achieve the desired behavior.
2. reverse the Listview, and itemcount is the data length + 1, leaving a space for displaying the loading prompt
3. The prompt information is loaded at the position of data length + 1 (the top we see), and the message body is loaded at other positions
4. Monitor the rolling. When the rolling position is greater than the current maximum rolling distance, modify the loading status,
5. The loading prompt information is controlled separately according to the loading status
The code of the whole ListView is as follows:

Widget buildMessageListContainer() {
  if (_historyMessageList.length == 0) {
    return Container(
        height: Adapt.px(50),
        alignment: Alignment.center,
        child: Text(
          'No historical news',
          style: TextStyle(color: Colors.grey, fontSize: Adapt.px(13)),
        ));
  } else {
    return Column(
      children: [
        Expanded(
          child: ScrollConfiguration(
            // color: Colors.green,
            behavior: ChatScrollBehavior(),
            child: ListView.builder(
                controller: _scrollController,
                reverse: true,
                physics: ChatScrollPhysics(
                    parent: AlwaysScrollableScrollPhysics()),
                itemCount: _historyMessageList.length + 1,
                itemBuilder: (BuildContext context, int index) {
                  if (index == _historyMessageList.length) {
                    return loadingView();
                  } else {
                    return getMessageRow(index);
                  }
                }),
          ),
        ),
        SizedBox(
          height: INPUT_CONTAINER_HEIGHT,
        )
      ],
    );
  }
}

ChatScrollPhysics and ChatScrollBehavior are original bloggers who have rewritten the method in order to optimize the rolling effect and behavior.
ChatScrollPhysics: the purpose is to realize the elastic effect of sliding loading belt and upward sliding shielding elastic effect
ChatScrollBehavior: use ScrollConfiguration to wrap the sliding component behavior and set it to its own behavior.

Scroll monitor:

void scrollListener() {
  if (_scrollController!.position.pixels >=
      _scrollController!.position.maxScrollExtent) {
    _getMoreData();
  }
}
_scrollController!.addListener(scrollListener);

Get new data and change loading prompt information

void _getMoreData() async {
  if (_loadStatus == LoadingStatus.STATUS_IDLE) {
    _loadStatus = LoadingStatus.STATUS_LOADING;
    loadText = 'Loading...';
    List moreList = [];
    var sentTime =
        this._historyMessageList[_historyMessageList.length - 1].sentTime;
    var list = await RongImSdk.getHistoryMessage(_groupId, sentTime, 20, 0);
    if (list.length > 0) {
      //Remove the first one because the first one is already displayed
      for (int i = 1; i < list.length; i++) {
        moreList.add(list[i]);
      }
      if (moreList.length > 0) {
        _loadStatus = LoadingStatus.STATUS_IDLE;
        this._historyMessageList.addAll(moreList);
      } else {
        _loadStatus = LoadingStatus.STATUS_COMPLETED;
        loadText = 'All messages are displayed';
      }
    } else {
      _loadStatus = LoadingStatus.STATUS_COMPLETED;
      loadText = 'All messages are displayed';
    }
    setState(() {});
  }
}

Load prompt style:

Widget loadingView() {
  var loadingTs =
      TextStyle(color: Constants.ITEM_LABEL_COLOR_999, fontSize: 12);
  var loadingText = Padding(
    padding: EdgeInsets.only(left: Adapt.px(0)),
    child: Text(
      loadText,
      style: loadingTs,
    ),
  );
  var loadingIndicator = SizedBox(
    child: CircularProgressIndicator(
        strokeWidth: Adapt.px(2),
        valueColor: AlwaysStoppedAnimation(Colors.blue)),
    width: Adapt.px(12),
    height: Adapt.px(12),
  );
  return Padding(
        padding: EdgeInsets.only(top: Adapt.px(20), bottom: Adapt.px(20)),
        child: Row(
          children: <Widget>[
            _loadStatus == LoadingStatus.STATUS_LOADING
                ? loadingIndicator
                : Text(''),
            loadingText
          ],
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
        ));
}

Keywords: Flutter

Added by ldtiw on Fri, 17 Dec 2021 10:56:05 +0200