SingleChildScrollView вызывает сильное торможение

У меня есть экран приложения на нем должен отображаться длинный список элементов, изначально за скролл элементов отвечал ListView.builder. Но по мере нарастания приложения получилось так что на этом же экране мне понадобилось разместить другие виджеты, например поисковая строка. По умолчанию ListViewзанимает всю область экрана для отрисовки элементов списка, я включил свойство shrinkWrap: true и отключил физику скролла у самого ListView. И обернул полностью весь виджет который подгружает на экран элементы в SingleChildScrollView для того чтобы можно было на этом же экране разместить другие виджеты:

Widget build(BuildContext context) {
    return SafeArea(
      child: SingleChildScrollView(
         child:Column(
          children: [
            SizedBox(height: getProportionateScreenHeight(20)),
            ProductHeader(),
            SizedBox(height: getProportionateScreenHeight(10)),
            Product() // Здесь вызывается список элементов
          ],
        ),
      ),
    );

Место где вызывается ListView.builder:

class Product extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetX<ProductController>(
      init: ProductController(),
      builder: (controller) {
        return FutureBuilder<List<ProductsCombined>>(
          future: controller.productList.value,
          builder: (context, snapshot) {
            if (snapshot.hasData) {
              return ListView.builder(
                physics: NeverScrollableScrollPhysics(),
                itemCount: snapshot.data.length,
                shrinkWrap: true,
                itemBuilder: (BuildContext context, int index) =>
                    ProductCards(
                  // index: index,
                  product: snapshot.data[index],
                ),
              );
            } else {
              return Center(child: CircularProgressIndicator());
            }
          },
        );
      },
    );
  }
}

Когда на экране отображается около 30 элементов списка, торможений не замечено, но когда я попытался вывести больше 1000 элементов, началось жуткое торможение. Причем если скроллом занимается непосредственно сам ListView.builder и список занимает всю область экрана торможений нет.

В общем торможение вызывается насколько понял из за того что я отключаю скролл на самом ListView и передаю этот скроллинг другому виджету SingleChildScrollView.

Есть какие идеи из за чего это может происходить, возможно у меня допущены какие либо ошибки?


Ответы (2 шт):

Автор решения: MiT

SingleChildScrollView рендерит все виджеты в child. Также вложенность scroll виджетов в друг друга влияют на производительность. По этому и происходит просадка при 1000 элементов.

Решение вашей проблемы это использовать sliver виджеты. Вот небольшой пример как это должно быть:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: MyWidget(),
      ),
    );
  }
}

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CustomScrollView(
      slivers: <Widget>[
        SliverPadding(padding: EdgeInsets.symmetric(vertical: 20)),
        SliverToBoxAdapter(
          child: Card(
            margin: EdgeInsets.all(16),
            child: Padding(
              padding: EdgeInsets.all(16),
              child: Text('Test'),
            ),
          ),
        ),
        SliverPadding(padding: EdgeInsets.symmetric(vertical: 20)),
        SliverList(
          delegate: SliverChildBuilderDelegate(
            (context, index) {
              return Material(
                child: ListTile(
                  title: Text(index.toString()),
                ),
              );
            },
            childCount: 100,
          ),
        )
      ],
    );
  }
}
→ Ссылка
Автор решения: che4len

Оказалось что нужно было всего то сделать такую обертку Expanded -> Container-> ListView.builder, тогда ListView.builder не будет занимать всю область экрана и можно будет смещать его любым образом. Данный способ не нуждается в подключении свойства shrinkWrap = true, так как скроллом элементов в данном случае будет заниматься сам ListView.builder.

Пример исправленного кода, класс Product:

class Product extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetX<ProductController>(
      init: ProductController(),
      builder: (controller) {
        return FutureBuilder<List<ProductsCombined>>(
          future: controller.filterProduct.value,
          builder: (context, snapshot) {
            if (snapshot.hasData) {
              return Expanded(
                child: Container(
                  child: ListView.builder(
                    physics: BouncingScrollPhysics(),
                    itemCount: snapshot.data.length,
                    itemBuilder: (BuildContext context, int index) =>
                        ProductCards(
                      // index: index,
                      product: snapshot.data[index],
                    ),
                  ),
                ),
              );
            } else {
              return Center(child: CircularProgressIndicator());
            }
          },
        );
      },
    );
  }
}

Класс Body:

class Body extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Column(
        children: [
          SizedBox(height: getProportionateScreenHeight(10)),
          ProductHeader(),
          SizedBox(height: getProportionateScreenHeight(20)),
          Product()
        ],
      ),
    );
  }
}
→ Ссылка