skip to Main Content

I see there is a variable in test environment of Flutter called this:

tester.allWidgets

It can return all widgets within the view port at the moment.

Is there anyway I can do the same with Flutter code, not inside a unit test?

The specific requirement I have is to locate all scrollable widgets within the current view port. Wondering if there’s an easy way to achieve that.

2

Answers


  1. use: https://pub.dev/packages/visibility_detector

    VisibilityDetector will detect child’s visibility. wrap the children with VisibilityDetector, and use a list to containing all show widget.
    you will get list of

    widget within the view port

    here’s a example:

    import 'package:flutter/material.dart';
    import 'package:visibility_detector/visibility_detector.dart';
    
    class VisibleWidgetList extends StatefulWidget {
      const VisibleWidgetList({super.key});
    
      @override
      State<VisibleWidgetList> createState() => _VisibleWidgetListState();
    }
    
    class _VisibleWidgetListState extends State<VisibleWidgetList> {
    
      List<int> numbers = List.generate(100, (i) => i);
    
      List<Key> visibleWidgets = []; // visible children
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: ListView.builder(
            itemCount: numbers.length,
            itemBuilder: (context, index) {
              return VisibilityDetector(
                key: Key(index.toString()),
                onVisibilityChanged: (info) {
                  if (info.visibleFraction > 0) {
                    visibleWidgets.add(info.key);
                  } else {
                    visibleWidgets.remove(info.key);
                  }
    
                  print(visibleWidgets);
                },
                child: ListTile(
                  title: Text('Item ${numbers[index]}'),
                ),
              );
            },
          )
        );
      }
    }
    
    
    Login or Signup to reply.
  2. You can try element tree traversal.

    You locate widgets within the element tree that are scrollable and are within the current viewport by checking their position and size constraints.

    Here is a sample of how you would do it:

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(
              title: Text('Find Scrollable Widgets in Viewport'),
            ),
            body: HomeScreen(),
          ),
        );
      }
    }
    
    class HomeScreen extends StatefulWidget {
      @override
      _HomeScreenState createState() => _HomeScreenState();
    }
    
    class _HomeScreenState extends State<HomeScreen> {
      List<Widget> scrollableWidgets = [];
    
      @override
      void initState() {
        super.initState();
        WidgetsBinding.instance.addPostFrameCallback((_) {
          findScrollableWidgets();
        });
      }
    
      void findScrollableWidgets() {
        // Root element of the widget tree
        final element = context as Element;
    
        // List to hold scrollable widgets found
        final List<Widget> scrollables = [];
    
        // Function to recursively find scrollable widgets
        void findScrollableElements(Element element) {
          element.visitChildren((child) {
            if (child.widget is ScrollView) {
              final RenderBox renderBox = child.renderObject as RenderBox;
              if (renderBox.hasSize) {
                final Size size = renderBox.size;
                // Check if the render box is within the viewport
                if (size.height > 0 && size.width > 0) {
                  scrollables.add(child.widget);
                }
              }
            }
            findScrollableElements(child);
          });
        }
    
        findScrollableElements(element);
    
        setState(() {
          scrollableWidgets = scrollables;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Column(
          children: [
            // A scrollable
            Expanded(
              child: ListView.builder(
                itemCount: 20,
                itemBuilder: (context, index) {
                  return ListTile(
                    title: Text('Item $index'),
                  );
                },
              ),
            ),
            // A scrollable
            Expanded(
              child: GridView.builder(
                gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 2,
                ),
                itemCount: 20,
                itemBuilder: (context, index) {
                  return Card(
                    child: Center(child: Text('Card $index')),
                  );
                },
              ),
            ),
            ElevatedButton(
              onPressed: () {
                print('Found ${scrollableWidgets.length} scrollable widgets');
                for (var widget in scrollableWidgets) {
                  print(widget.runtimeType);
                }
              },
              child: Text('Find Scrollable Widgets'),
            ),
          ],
        );
      }
    }
    
    

    We do a renderBox.hasSize to check if the RenderBox has been laid out by the framework and has a valid size.

    We do a if (size.height > 0 && size.width > 0) to check if the RenderBox has a positive width and height as zero height or width means that the widget is not visible or not rendered within the current viewport.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search