Programlı olarak bir ListView’un sonuna kaydırma


98

ListViewÖğe sayısının dinamik olarak değişebileceği bir kaydırılabilir ürünüm var . Listenin sonuna yeni bir öğe eklendiğinde, programlı ListViewolarak sonuna kadar kaydırmak istiyorum . (örneğin, sonuna yeni mesajların eklenebileceği sohbet mesajı listesi gibi bir şey)

Benim tahminim bir oluşturmanız gerekir ki ScrollControllerbenim de Statenesne ve manuel geçmek ListViewDaha sonra çağrı böylece, yapıcı animateTo()/ jumpTo()denetleyici üzerinde yöntemini. Bununla birlikte, maksimum kaydırma ofsetini kolayca belirleyemediğim için, basitçe bir scrollToEnd()tür işlem gerçekleştirmek imkansız görünüyor (oysa 0.0, başlangıç ​​konumuna kaydırmak için kolayca geçebilirim ).

Bunu başarmanın kolay bir yolu var mı?

Kullanmak reverse: truebenim için mükemmel bir çözüm değil, çünkü ListViewgörüntü alanına sığan çok az sayıda öğe olduğunda öğelerin en üstte hizalanmasını istiyorum .

Yanıtlar:


96

Eğer Psikoloğa sarılı kullanırsanız ListViewile reverse: trueistediğini yapacak, 0.0 kadar kaydırmadan.

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
Bu hile yaptı. Benim özel durumumda, ListView'ı Genişletilmiş bir parçacığa yerleştirmek için iç içe geçmiş Sütunlar kullanmak zorunda kaldım, ancak temel fikir aynı.
yyoon

19
Bir çözüm aramaya geldim, sadece diğer cevabın bunu başarmanın daha iyi bir yolu olabileceğini belirtmek istedim - stackoverflow.com/questions/44141148/…
Animesh Jain

6
Katılıyorum, bu cevaba (ben de yazdım) kesinlikle daha iyi.
Collin Jackson

1
@Dennis Böylece ListView alttan ters sırada başlar.
CopsOnRoad

1
@CopsOnRoad'un cevabı daha iyi görünüyor ve bu cevap bir çözümden çok bir hack gibi görünüyor.
Roopak A Nelliat

118

Bunu başarmak için ScrollController.jumpTo()veya ScrollController.animateTo()yöntemi kullanın .

Misal:

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

Düzgün kaydırma istiyorsanız, jumpToyukarıdaki kullanım yerine

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

4
Firebase Realtime Database için şimdiye kadar 250 ms ile kurtulabildim (bu muhtemelen gerçekten uzun). Merak ediyorum ne kadar alçalabiliriz? Sorun, maxScrollExtent'in yeni öğe eklenerek pencere öğesi yapısının bitmesini beklemesi gerektiğidir. Geri aramadan derlemenin tamamlanmasına kadar geçen ortalama sürenin profili nasıl oluşturulur?
SacWebDeveloper

2
Firebase'den veri almak için streambuilder kullanıldığında bunun düzgün çalışacağını düşünüyor musunuz? Gönder düğmesine de sohbet mesajı eklensin mi? Ayrıca internet bağlantısı yavaşsa işe yarayacak mı? eğer önerebilirsen daha iyi bir şey. Teşekkürler.
Jay Mungara

@SacWebDeveloper Bu sorun artı firebase ile ilgili yaklaşımınız neydi?
Idris Stack

@IdrisStack Aslında, 250ms açıkça yeterince uzun değil, bazen jumpTo güncelleme gerçekleşmeden önce gerçekleşir. Bence daha iyi yaklaşım, güncellemeden sonra liste uzunluğunu karşılaştırmak ve uzunluk bir daha büyükse en alta atlamak olacaktır.
SacWebDeveloper

Teşekkürler @SacWebDeveloper, yaklaşımınız işe yarıyor, belki size Firestore bağlamında başka bir soru sorabilir miyim, abit konusunu yönlendirebilir. Qn. Neden birine mesaj gönderdiğimde liste otomatik olarak en alta kaymıyor, mesajı dinlemenin ve aşağı kaydırmanın bir yolu var mı?
Idris Stack

13

listViewScrollController.animateTo(listViewScrollController.position.maxScrollExtent) en basit yoldur.


1
Merhaba Jack, cevabın için teşekkürler, ben de aynı şeyi yazdığıma inanıyorum, bu yüzden aynı çözümü tekrar eklemenin bir avantajı yok IMHO.
CopsOnRoad

1
Ben bu yaklaşımı kullanıyordum ama animateTo (...) çalıştırılmadan önce ekranın işlenmesi gerektiğinden bunu yapması için biraz ms beklemeniz gerekiyor. Belki de bunu hacklemeden yapmak için iyi uygulamaları kullanmanın güzel bir yolu vardır.
Renan Coelho

1
Doğru cevap bu. Mevcut satırda +100 yaparsanız listenizi aşağı kaydıracaktır. listViewScrollController.position.maxScrollExtent + 100 gibi;
Rizwan Ansar

1
@RenanCoelho'dan ilham alarak, bu kod satırını 100 milisaniye gecikmeli bir Zamanlayıcıya sardım.
ymerdrengene

5

Mükemmel sonuçları elde etmek için Colin Jackson ve CopsOnRoad'un cevaplarını şu şekilde birleştirdim:

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

2

Listenin en altına gitmek için kaydırma denetleyicisini kullanmaya çalışırken o kadar çok sorun yaşıyorum ki başka bir yaklaşım kullanıyorum.

Listeyi en alta göndermek için bir olay oluşturmak yerine mantığımı tersine çevrilmiş bir liste kullanacak şekilde değiştiriyorum.

Bu yüzden, her yeni öğem olduğunda, basitçe, listenin en üstüne eklerim.

// 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
Bununla ilgili yaşadığım tek sorun, herhangi bir kaydırma çubuğunun artık geriye doğru çalışması. Aşağı kaydırdığımda yukarı hareket ediyorlar.
ThinkDigital

1

WidgetBinding'i initstate'e koymayın, bunun yerine, verilerinizi veritabanından alan yönteme koymanız gerekir. örneğin, bunun gibi. İnitstate koyulursa, scrollcontrollerherhangi bir liste görünümüne eklenmez.

    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*heightlistedeki bir satırın yüksekliği olan ve _controllerbu şekilde tanımlandığı yerde bunu kullanabilirsiniz_controller = ScrollController();

(BuildContext context, int pos) {
    if(pos != 0) {
        _controller.animateTo(0.09 * height * (pos - 1), 
                              curve: Curves.easeInOut,
                              duration: Duration(milliseconds: 1400));
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.