flutter3.0学习笔记

SliverAnimatedList详解

Preview

要为 sliver 列表设置动画,我们可以使用SliverAnimatedList。此小部件的SliverAnimatedListState可用于动态插入或删除项目。

  • 首先,让我们创建一个 SliverAppBar,它有两个按钮add,remove用于插入和删除列表项。
CustomScrollView(
    slivers: [
      SliverAppBar(
        title: const Text('SliverAnimatedList'),
        expandedHeight: 60,
        centerTitle: true,
        backgroundColor: Colors.amber,
        leading: IconButton(
          icon: const Icon(Icons.add_circle),
          onPressed: _insert,
          tooltip: 'insert a new item',iconSize: 32,
        ),
        actions: [
          IconButton(
            onPressed: _remove,
            icon: const Icon(Icons.remove_circle),
            tooltip: 'remove item',
            iconSize: 32,
          )
        ],
      )
    ],
    ),

image.png

  • SliverAppBar 下面添加 SliverAnimatedList
 SliverAnimatedList(
    key: _listKey,
    initialItemCount: _list.length,
    itemBuilder: _buildItem
  )
  • 定于key 这里_listKey是一个SliverAnimatedListStateGlobalKey类型。该键有助于插入和删除列表项。 要将项目插入我们可以使用的列表中SliverAnimatedListState.insertItem(),并删除项目使用SliverAnimatedListState.removeItem()
 final _listKey = GlobalKey<SliverAnimatedListState>();
  • initialItemCount initialItemCount定义列表将从多少个项目开始。所以让我们初始化它initState
  • 初始化数据
 late List<int> _list;
  int? _selectedItem;
  late int _nextItem;
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _list = <int>[0,1,2];
    _nextItem = 3;
  }
  • itemBuilder 这个函数有3个参数:
  • context这是条子的上下文。
  • index参数指示项目在列表中的位置。该index参数的值将介于0和initialItemCount加上已插入SliverAnimatedListState.insertItem的项目总数和减去已删除的项目总数之间SliverAnimatedListState.removeItem
  • 给出和之间类型的animation动画值。double 0 1

实现_buildItem内部传递的函数itemBuilder

 Widget _buildItem(BuildContext context,int index,Animation<double> animation){
    return CardItem(
        animation: animation,
      item: _list[index],
      selected: _selectedItem == _list[index],
      onTap: (){
          setState(() {
            _selectedItem = _selectedItem==_list[index] ? null :_list[index];
          });
      },
    );
  }

实现CardItem,selected为真时卡片变为灰色。这个小部件opacity 是基于animation参数的。

class CardItem extends StatelessWidget {
  const CardItem({Key? key, required this.animation, this.onTap, required this.item,  this.selected = false}) : super(key: key);
  final Animation<double> animation;
  final VoidCallback? onTap;
  final int item;
  final bool selected;
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(
        left: 2.0,
        right: 2.0,
        top: 2.0,
        bottom: 0.0,
      ),
      child: FadeTransition(
        opacity: animation,
        child: GestureDetector(
          onTap: onTap,
          child: SizedBox(
            height: 80.0,
            child: Card(
              color: selected?Colors.black12:Colors.primaries[item % Colors.primaries.length],
              child: Center(
                child: Text('Item $item',style: Theme.of(context).textTheme.headline4,),
              ),
            ),
          ),
        ),
      ),
    );
  }
}
  • 插入列表的_insert和函数。_remove并从列表中删除
//插入
 void _insert(){
    final int index = _selectedItem == null?_list.length:_list.indexOf(_selectedItem!);
    _list.insert(index, _nextItem++);
    _listKey.currentState!.insertItem(index);
  }
//删除
  void _remove(){
    final int index = _selectedItem == null?_list.length:_list.indexOf(_selectedItem!);
    _list.removeAt(index);
    _listKey.currentState!.removeItem(index, (context, animation) => SizeTransition(
      sizeFactor: animation,
      child: Card(
        child: Center(
          child: Text('Item $index',style: Theme.of(context).textTheme.headline4,),
        ),
      ),
    ));
    setState(() {
      _selectedItem = null;
    });
  }

完整代码:

import 'package:flutter/material.dart';

class SliverAnimatedListPage extends StatefulWidget {
  const SliverAnimatedListPage({Key? key}) : super(key: key);

  @override
  State<SliverAnimatedListPage> createState() => _SliverAnimatedListPageState();
}

class _SliverAnimatedListPageState extends State<SliverAnimatedListPage> {
  final _listKey = GlobalKey<SliverAnimatedListState>();
  late List<int> _list;
  int? _selectedItem;
  late int _nextItem;
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _list = <int>[0,1,2];
    _nextItem = 3;
  }
  void _insert(){
    final int index = _selectedItem == null?_list.length:_list.indexOf(_selectedItem!);
    _list.insert(index, _nextItem++);
    _listKey.currentState!.insertItem(index);
  }

  void _remove(){
    final int index = _selectedItem == null ? _list.length : _list.indexOf(_selectedItem!);
    _list.removeAt(index);
    _listKey.currentState!.removeItem(
        index,
        (context, animation) => SizeTransition(
          sizeFactor: animation,
          child: Card(
            child: Center(
              child: Text('选中删除',style: Theme.of(context).textTheme.headline4,),
            ),
          ),
        ));
        setState(() {
          _selectedItem = null;
        });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: [
          SliverAppBar(
            title: const Text('SliverAnimatedList'),
            expandedHeight: 60,
            centerTitle: true,
            backgroundColor: Colors.amber,
            leading: IconButton(
              icon: const Icon(Icons.add_circle),
              onPressed: _insert,
              tooltip: 'insert a new item',iconSize: 32,
            ),
            actions: [
              IconButton(
                onPressed: _remove,
                icon: const Icon(Icons.remove_circle),
                tooltip: 'remove item',
                iconSize: 32,
              )
            ],
          ),
          SliverAnimatedList(
            key: _listKey,
            initialItemCount: _list.length,
            itemBuilder: _buildItem,
          )
        ],
      ),
    );
  }
  Widget _buildItem(BuildContext context,int index,Animation<double> animation){
    return CardItem(
      animation: animation,
      item: _list[index],
      selected: _selectedItem == _list[index],
      onTap: (){
          setState(() {
            _selectedItem = _selectedItem==_list[index] ? null :_list[index];
          });
      },
    );
  }
}

class CardItem extends StatelessWidget {
  const CardItem({Key? key, required this.animation, this.onTap, required this.item,  this.selected = false}) : assert(item >= 0), super(key: key);
  final Animation<double> animation;
  final VoidCallback? onTap;
  final int item;
  final bool selected;
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(
        left: 2.0,
        right: 2.0,
        top: 2.0,
        bottom: 0.0,
      ),
      child: FadeTransition(
        opacity: animation,
        child: GestureDetector(
          onTap: onTap,
          child: SizedBox(
            height: 80.0,
            child: Card(
              color: selected?Colors.black12:Colors.primaries[item % Colors.primaries.length],
              child: Center(
                child: Text('Item $item',style: Theme.of(context).textTheme.headline4,),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

Simulator Screen Shot - iPhone 14 Pro Max - 2022-11-04 at 18.12.00.png