โดยทางโปรแกรมจะเลื่อนไปที่จุดสิ้นสุดของ ListView


97

ฉันมีตัวเลื่อนListViewซึ่งจำนวนรายการสามารถเปลี่ยนแปลงได้แบบไดนามิก เมื่อใดก็ตามที่มีการเพิ่มรายการใหม่ในตอนท้ายของรายการฉันต้องการเลื่อนListViewไปยังจุดสิ้นสุดโดยใช้โปรแกรม (เช่นรายการข้อความแชทที่สามารถเพิ่มข้อความใหม่ได้ในตอนท้าย)

ฉันเดาว่าฉันจะต้องสร้างScrollControllerในStateวัตถุของฉันและส่งต่อไปยังตัวListViewสร้างด้วยตนเองดังนั้นฉันจึงสามารถเรียกanimateTo()/ jumpTo()method บนคอนโทรลเลอร์ได้ในภายหลัง อย่างไรก็ตามเนื่องจากฉันไม่สามารถกำหนดออฟเซ็ตการเลื่อนสูงสุดได้อย่างง่ายดายดูเหมือนว่าจะเป็นไปไม่ได้ที่จะดำเนินการเพียงscrollToEnd()ประเภทเดียว (ในขณะที่ฉันสามารถผ่านได้อย่างง่ายดาย0.0เพื่อให้เลื่อนไปที่ตำแหน่งเริ่มต้น)

มีวิธีง่ายๆในการบรรลุเป้าหมายนี้หรือไม่?

การใช้reverse: trueไม่ใช่วิธีแก้ปัญหาที่สมบูรณ์แบบสำหรับฉันเพราะฉันต้องการให้รายการจัดตำแหน่งที่ด้านบนเมื่อมีรายการเพียงเล็กน้อยที่พอดีกับListViewวิวพอร์ต

คำตอบ:


95

ถ้าคุณใช้หดห่อListViewด้วยreverse: 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

19
ฉันมาค้นหาวิธีแก้ปัญหาเพียงแค่อยากจะบอกว่าคำตอบอื่นอาจเป็นวิธีที่ดีกว่าในการบรรลุเป้าหมายนี้ - stackoverflow.com/questions/44141148/…
Animesh Jain

6
ฉันเห็นด้วยคำตอบนั้น (ซึ่งฉันเขียนด้วย) ดีกว่าแน่นอน
Collin Jackson

1
@ เดนนิสเพื่อให้ ListView เริ่มต้นในลำดับย้อนกลับที่มาจากด้านล่าง
CopsOnRoad

1
คำตอบจาก @CopsOnRoad ดูดีกว่าและคำตอบนี้ดูเหมือนแฮ็คมากกว่าวิธีแก้ปัญหา
Roopak A Nelliat

117

ใช้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 ฉันสามารถใช้งานได้ไกลถึง 250 มิลลิวินาที (ซึ่งอาจจะนานมาก) ฉันสงสัยว่าเราจะไปได้ต่ำแค่ไหน? ปัญหาคือ maxScrollExtent ต้องรอให้การสร้างวิดเจ็ตเสร็จสิ้นพร้อมกับเพิ่มรายการใหม่ จะกำหนดระยะเวลาเฉลี่ยจากการโทรกลับไปจนถึงการสร้างเสร็จได้อย่างไร
SacWebDeveloper

2
คุณคิดว่าสิ่งนี้จะทำงานได้ดีกับการใช้ streambuilder เพื่อดึงข้อมูลจาก firebase หรือไม่? ยังเพิ่มข้อความแชทที่ปุ่มส่งด้วย? นอกจากนี้หากการเชื่อมต่ออินเทอร์เน็ตช้าจะใช้งานได้หรือไม่? อะไรจะดีกว่าถ้าคุณสามารถแนะนำ ขอบคุณ.
Jay Mungara

@SacWebDeveloper คุณมีแนวทางอย่างไรกับปัญหานี้พร้อมกับ Firebase
Idris Stack

@IdrisStack จริงๆแล้ว 250ms นั้นไม่นานพอในบางครั้งการกระโดดจะเกิดขึ้นก่อนที่การอัปเดตจะเกิดขึ้น ฉันคิดว่าแนวทางที่ดีกว่าคือการเปรียบเทียบความยาวของรายการหลังการอัปเดตและข้ามไปที่ด้านล่างหากความยาวมากกว่าหนึ่ง
SacWebDeveloper

ขอบคุณ @SacWebDeveloper วิธีการของคุณใช้ได้ผลฉันขอถามคำถามอื่นในบริบทของ Firestore มันอาจเบี่ยงเบนประเด็นไปก็ได้ Qn. ทำไมเวลาส่งข้อความถึงคนที่รายการไม่เลื่อนลงมาด้านล่างโดยอัตโนมัติมีวิธีฟังข้อความและเลื่อนลงไปด้านล่างหรือไม่?
Idris Stack

13

listViewScrollController.animateTo(listViewScrollController.position.maxScrollExtent) เป็นวิธีที่ง่ายที่สุด


1
สวัสดีแจ็คขอบคุณสำหรับคำตอบของคุณฉันเชื่อว่าฉันเขียนเหมือนกันดังนั้นจึงไม่มีข้อได้เปรียบที่จะเพิ่มโซลูชันเดียวกันอีกครั้ง IMHO
CopsOnRoad

1
ฉันใช้วิธีนี้ แต่คุณต้องรอให้ ms ทำเพราะต้องแสดงผลหน้าจอก่อนที่จะเรียกใช้ animateTo (... ) บางทีเราอาจจะเป็นวิธีที่ดีในการใช้แนวทางปฏิบัติที่ดีโดยไม่ต้องแฮ็ค
Renan Coelho

1
นั่นคือคำตอบที่ใช่ มันจะเลื่อนรายการของคุณไปด้านล่างถ้าคุณ +100 ในบรรทัดปัจจุบัน เช่น listViewScrollController.position.maxScrollExtent + 100;
Rizwan Ansar

1
ด้วยแรงบันดาลใจจาก @RenanCoelho ฉันห่อรหัสบรรทัดนี้ไว้ในตัวจับเวลาโดยมีความล่าช้า 100 มิลลิวินาที
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

1

อย่าใส่ widgetBinding ใน initstate แต่คุณต้องใส่ไว้ในวิธีการที่ดึงข้อมูลของคุณจากฐานข้อมูล ตัวอย่างเช่นนี้ หากใส่ใน initstate scrollcontrollerจะไม่แนบกับ listview ใด ๆ

    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');
    }
   }

-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.