프로그래밍 방식으로 ListView 끝으로 스크롤


108

ListView항목 수가 동적으로 변경 될 수 있는 스크롤 가능 이 있습니다. 새 항목이 목록 끝에 추가 될 때마다 프로그래밍 방식으로 ListView끝까지 스크롤하고 싶습니다 . (예 : 마지막에 새 메시지를 추가 할 수있는 채팅 메시지 목록과 같은 것)

내 생각 엔 객체 ScrollController에서 를 만들고 생성자에 State수동으로 전달해야 ListView나중에 컨트롤러에서 animateTo()/ jumpTo()메서드를 호출 할 수 있습니다 . 그러나 최대 스크롤 오프셋을 쉽게 결정할 수 없기 때문에 단순히 scrollToEnd()일종의 작업을 수행하는 것은 불가능 해 보입니다 (단순히 통과 0.0하여 초기 위치로 스크롤 할 수 있음).

이것을 달성하는 쉬운 방법이 있습니까?

뷰포트에 reverse: true맞는 항목 수가 적을 때 항목이 상단에 정렬되기를 원하기 때문에 사용 은 나에게 완벽한 솔루션이 아닙니다 ListView.

답변:


106

당신은 수축 포장 사용하는 경우 ListViewreverse: true당신이 원하는 것을 할 것입니다 0.0로 스크롤합니다.

import 'dart:collection';

import 'package:flutter/material.dart';

void main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Example',
      home: new MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<Widget> _messages = <Widget>[new Text('hello'), new Text('world')];
  ScrollController _scrollController = new ScrollController();

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new Center(
        child: new Container(
          decoration: new BoxDecoration(backgroundColor: Colors.blueGrey.shade100),
          width: 100.0,
          height: 100.0,
          child: new Column(
            children: [
              new Flexible(
                child: new ListView(
                  controller: _scrollController,
                  reverse: true,
                  shrinkWrap: true,
                  children: new UnmodifiableListView(_messages),
                ),
              ),
            ],
          ),
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        child: new Icon(Icons.add),
        onPressed: () {
          setState(() {
            _messages.insert(0, new Text("message ${_messages.length}"));
          });
          _scrollController.animateTo(
            0.0,
            curve: Curves.easeOut,
            duration: const Duration(milliseconds: 300),
          );
        }
      ),
    );
  }
}

1
이것은 트릭을했다. 내 특정한 경우에는 ListView를 확장 위젯에 배치하기 위해 중첩 된 열을 사용해야했지만 기본 아이디어는 동일합니다.
yyoon

21
- 내가 솔루션을 찾고 온, 단지 다른 대답이 달성하기 위해 더 나은 방법이 될 수 있음을 언급하고 싶었 stackoverflow.com/questions/44141148/...
Animesh 자이나교

6
나는 그 대답 (내가 쓴) 확실히 더 낫다는 것에 동의합니다.
Collin Jackson

1
@Dennis ListView가 아래쪽에서 역순으로 시작되도록합니다.
CopsOnRoad

1
@CopsOnRoad의 답변은 더 좋아 보이며이 답변은 솔루션 이라기보다는 해킹처럼 보입니다.
Roopak A Nelliat

128

사용 ScrollController.jumpTo()또는ScrollController.animateTo()이를 달성하기 위해 방법.

예:

final _controller = ScrollController();

@override
Widget build(BuildContext context) {
  
  // After 1 second, it takes you to the bottom of the ListView
  Timer(
    Duration(seconds: 1),
    () => _controller.jumpTo(_controller.position.maxScrollExtent),
  );

  return ListView.builder(
    controller: _controller,
    itemCount: 50,
    itemBuilder: (_, __) => ListTile(title: Text('ListTile')),
  );
}

부드러운 스크롤을 원하면 jumpTo위의 사용 대신

_controller.animateTo(
  _controller.position.maxScrollExtent,
  duration: Duration(seconds: 1),
  curve: Curves.fastOutSlowIn,
);

4
Firebase Realtime Database의 경우 지금까지 250ms (아마도 정말 길어질 것임)로 벗어날 수있었습니다. 우리가 얼마나 낮게 갈 수 있는지 궁금해? 문제는 maxScrollExtent가 추가 된 새 항목으로 위젯 빌드가 완료 될 때까지 기다려야한다는 것입니다. 콜백에서 빌드 완료까지의 평균 기간을 프로파일 링하는 방법은 무엇입니까?
SacWebDeveloper

2
firebase에서 데이터를 가져 오기 위해 streambuilder를 사용하면 이것이 제대로 작동한다고 생각하십니까? 보내기 버튼에도 채팅 메시지를 추가 하시겠습니까? 또한 인터넷 연결이 느리면 작동합니까? 당신이 제안 할 수 있다면 더 나은 것을. 감사.
제이 Mungara

@SacWebDeveloper이 문제와 firebase에 대한 접근 방식은 무엇입니까?
Idris Stack

@IdrisStack 사실, 250ms는 때때로 업데이트가 발생하기 전에 jumpTo가 발생하는 것처럼 충분히 길지 않습니다. 더 나은 접근 방식은 업데이트 후 목록 길이를 비교하고 길이가 하나 더 크면 맨 아래로 점프하는 것입니다.
SacWebDeveloper

감사합니다 @SacWebDeveloper, 귀하의 접근 방식이 작동합니다. Firestore의 맥락에서 다른 질문을해도 될까요? 주제를 우회 할 수 있습니다. Qn. 다른 사람에게 메시지를 보낼 때 목록이 자동으로 아래로 스크롤되지 않는 이유는 무엇입니까? 메시지를 듣고 아래로 스크롤하는 방법이 있습니까?
Idris Stack

14

listViewScrollController.animateTo(listViewScrollController.position.maxScrollExtent) 가장 간단한 방법입니다.


2
안녕하세요 Jack, 답변 주셔서 감사합니다. 동일한 내용을 썼다고 생각하므로 동일한 솔루션을 다시 추가하는 이점이 없습니다.
CopsOnRoad

1
이 방법을 사용하고 있었지만 animateTo (...)를 실행하기 전에 화면을 렌더링해야하므로이를 수행하려면 몇 ms를 기다려야합니다. 아마도 우리는 해킹없이 그것을하기 위해 좋은 관행을 사용하는 좋은 방법이있을 것입니다.
Renan Coelho

1
정답입니다. 현재 줄에서 +100이면 목록을 아래로 스크롤합니다. listViewScrollController.position.maxScrollExtent + 100처럼;
Rizwan Ansar

1
@RenanCoelho의 영감을 받아이 코드 줄을 100 밀리 초의 지연 시간으로 Timer로 래핑했습니다.
ymerdrengene

5

완벽한 결과를 얻기 위해 Colin Jackson과 CopsOnRoad의 답변을 다음과 같이 결합했습니다.

_scrollController.animateTo(
                              _scrollController.position.maxScrollExtent,
                              curve: Curves.easeOut,
                              duration: const Duration(milliseconds: 500),
                            );

2

스크롤 컨트롤러를 사용하여 목록의 맨 아래로 이동하는 데 너무 많은 문제가있어서 다른 접근 방식을 사용합니다.

목록을 맨 아래로 보내는 이벤트를 만드는 대신 반전 된 목록을 사용하도록 논리를 변경합니다.

그래서 매번 새 항목이있을 때마다 목록 맨 위에 삽입하여 만들었습니다.

// add new message at the begin of the list 
list.insert(0, message);
// ...

// pull items from the database
list = await bean.getAllReversed(); // basically a method that applies a descendent order

// I remove the scroll controller
new Flexible(
  child: new ListView.builder(
    reverse: true, 
    key: new Key(model.count().toString()),
    itemCount: model.count(),
    itemBuilder: (context, i) => ChatItem.displayMessage(model.getItem(i))
  ),
),

2
내가 가진 유일한 문제는 모든 스크롤 막대가 이제 뒤로 작동한다는 것입니다. 아래로 스크롤하면 위로 이동합니다.
ThinkDigital

2

widgetBinding을 initstate에 두지 마십시오. 대신 데이터베이스에서 데이터를 가져 오는 메소드에 넣어야합니다. 예를 들면 이렇게요. initstate에 넣으면 목록보기에 scrollcontroller첨부되지 않습니다.

    Future<List<Message>> fetchMessage() async {

    var res = await Api().getData("message");
    var body = json.decode(res.body);
    if (res.statusCode == 200) {
      List<Message> messages = [];
      var count=0;
      for (var u in body) {
        count++;
        Message message = Message.fromJson(u);
        messages.add(message);
      }
      WidgetsBinding.instance
          .addPostFrameCallback((_){
        if (_scrollController.hasClients) {
          _scrollController.jumpTo(_scrollController.position.maxScrollExtent);
        }
      });
      return messages;
    } else {
      throw Exception('Failed to load album');
    }
   }

0

StreamBuilder내 데이터베이스에서 데이터를 가져 오기 위해 위젯을 사용할 때이 문제가 발생했습니다 . 나는 WidgetsBinding.instance.addPostFrameCallback위젯의 build방법 위에 올려 놓았고 끝까지 스크롤되지 않았습니다. 다음과 같이 수정했습니다.

...
StreamBuilder(
  stream: ...,
  builder: (BuildContext context, AsyncSnapshot snapshot) {
    // Like this:
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (_controller.hasClients) {
        _controller.jumpTo(_controller.position.maxScrollExtent);
      } else {
        setState(() => null);
      }
     });

     return PutYourListViewHere
}),
...

나도 시도했지만 _controller.animateTo작동하지 않는 것 같습니다.


-2

0.09*height목록에서 행의 높이 _controller가 다음과 같이 정의 되는 곳에서 사용할 수 있습니다._controller = ScrollController();

(BuildContext context, int pos) {
    if(pos != 0) {
        _controller.animateTo(0.09 * height * (pos - 1), 
                              curve: Curves.easeInOut,
                              duration: Duration(milliseconds: 1400));
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.