Amin Memariani
Amin Memariani

Reputation: 912

How to make sure a widget is visible on the screen?

In my Application, I have an AnimatedList in one of my pages. Items are being added to the list by pressing a button. I animate the scrollView when an Item is being inserted to the list. Sometimes the list grows How can I find out than an Item is still visible on the screen at a moment when the list gets too long?

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'dart:async';

class AnimatedListSample extends StatefulWidget {
  @override
  _AnimatedListSampleState createState() => _AnimatedListSampleState();
}

class _AnimatedListSampleState extends State<AnimatedListSample> {
  final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
  ListModel<int> _list;
  final ScrollController _controller = ScrollController();
  int _nextItem;

  @override
  void initState() {
    super.initState();
    _list = ListModel<int>(
      listKey: _listKey,
      initialItems: <int>[0, 1, 2],
    );
    _nextItem = 3;
  }

  Widget _buildItem(
      BuildContext context, int index, Animation<double> animation) {
    return CardItem(
      animation: animation,
      item: _list[index],
    );
  }

  void _insert() {
    _list.insert(_list.length, _list.length + 1);
    Timer(
        Duration(milliseconds: 300),
        () => _controller.animateTo(
              _controller.position.maxScrollExtent,
              curve: Curves.easeIn,
              duration: const Duration(milliseconds: 300),
            ));
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('AnimatedList'),
          actions: <Widget>[
            IconButton(
              icon: const Icon(Icons.add_circle),
              onPressed: _insert,
            )
          ],
        ),
        body: AnimatedList(
          controller: _controller,
          key: _listKey,
          initialItemCount: _list.length,
          itemBuilder: _buildItem,
        ),
      ),
    );
  }
}

class ListModel<E> {
  ListModel({
    @required this.listKey,
    Iterable<E> initialItems,
  })  : assert(listKey != null),
        _items = List<E>.from(initialItems ?? <E>[]);

  final GlobalKey<AnimatedListState> listKey;
  final List<E> _items;

  AnimatedListState get _animatedList => listKey.currentState;

  void insert(int index, E item) {
    _items.insert(index, item);
    _animatedList.insertItem(index);
  }

  int get length => _items.length;
  E operator [](int index) => _items[index];
  int indexOf(E item) => _items.indexOf(item);
}

class CardItem extends StatelessWidget {
  const CardItem({Key key, @required this.animation, @required this.item})
      : assert(animation != null),
        assert(item != null && item >= 0),
        super(key: key);

  final Animation<double> animation;
  final int item;

  @override
  Widget build(BuildContext context) {
    TextStyle textStyle = Theme.of(context).textTheme.headline4;
    return SizeTransition(
      axis: Axis.vertical,
      sizeFactor: animation,
      child: GestureDetector(
        behavior: HitTestBehavior.opaque,
        child: SizedBox(
          height: 80,
          child: Card(
            color: Colors.primaries[item % Colors.primaries.length],
            child: Center(
              child: Text('Item $item', style: textStyle),
            ),
          ),
        ),
      ),
    );
  }
}

Upvotes: 3

Views: 4538

Answers (1)

Christopher Moore
Christopher Moore

Reputation: 17123

You can use the visibility_detector package, which fires a callback whenever the visibility of a widget changes. So you can wrap each of the widgets in your list with the VisibilityDetector widget and have the callback change the state as the visibility changes. You can then handle visibility changes however you need based on your application.

Upvotes: 7

Related Questions