前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >《Flutter》-- 6.高级组件

《Flutter》-- 6.高级组件

作者头像
爱学习的程序媛
发布2022-04-07 16:23:22
10.6K0
发布2022-04-07 16:23:22
举报
文章被收录于专栏:学习/读书笔记

参阅书籍:

《Flutter跨平台开发入门与实践》-- 向治洪(著)

6. 高级组件

6.1 可滚动组件

对于列表和长布局的显示溢出问题,可以使用Flutter提供的可滚动组件来处理。

6.1.1 Scrollable组件

在Flutter中,一个可滚动的组件直接或间接包含一个Scrollable组件,它是可滚动组件的基础组件。

代码语言:javascript
复制
Scrollable({
  this.axisDirection = AxisDirection.down,//滚动方向
  this.controller,//用于接收一个ScrollController对象,控制滚动位置和监听滚动事件
  this.physics,//用于接收一个ScrollPhysics对象,可以决定滚动组件响应用户操作的方式
  @required this.viewportBuilder
})
6.1.2 Scrollbar组件

Scrollbar是一个Material风格的滚动指示器组件,如果要给可滚动组件添加滚动条,只需将Scrollbar组件作为可滚动组件的父组件使用即可。

如果一个可滚动组件支持Sliver模型,那么该滚动可以将子组件分成多个部分,只有当子组件出现在视口中时才会去构建它。

目前,可滚动组件中的大部分组件都支持基于Sliver的延迟构建模型,如ListView、GridView。

6.1.3 SingleChildScrollView组件

是一个只能包含单一子组件的可滚动组件,其作用类似于iOS的UIScrollView组件或Android的ScrollView组件。

只能应用于内容不会超过屏幕尺寸太多的情况,因为SingleChildScrollView组件目前还不支持基于Sliver的延迟加载,如果视图内容超出屏幕尺寸太多会导致性能问题。

所谓基于Sliver的延迟加载,是Flutter中提出的薄片(Sliver)概念。如果一个可滚动组件支持Sliver,那么该可滚动组件可以将子组件分成多个Sliver,只有当Sliver出现在视图窗口时才会去构建它,从而提高渲染的性能。

SingleChildScrollView组件的构造函数:

代码语言:javascript
复制
const SingleChildScrollView({
  Key key,
  this.scrollDirection = Axis.vertical,//滚动的方向,默认在垂直方向滚动
  this.reverse = false,//控制从头还是从尾开始滚动,默认false,即从头开始滚动
  this.padding,//插入子组件时的内边距
  bool primary,//是否是与父级关联的主滚动视图
  this.physics,//设置滚动效果
  this.controller,//控制滚动位置,当primary为true时,controller必须为null
  this.child,//列表项内容
  this.dragStrartBehavior = DragStrartBehavior.down,//处理拖拽开始行为的方式
})

示例代码:

代码语言:javascript
复制
import 'package:flutter/material.dart';

void main() => runApp(ScollWidget());

class ScollWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '高级组件--可滚动组件',
      home: Scaffold(
        appBar: AppBar(title: Text('可滚动组件--SingleChildScrollView')),
        body: SingleChildScrollView(
          scrollDirection: Axis.horizontal,
          child: Row(
            children: <Widget>[Text('Hello Flutter ' * 100)]
          ),
        )
      )
    );
  }
}

示例效果:

6.1.4 CustomScrollView组件

可以使用Sliver模型实现自定义滚动组件,可以包含多个子组件,而且可以将这些子组件包裹起来实现一致的滚动效果。

CustomScrollView作为容器组件时,子组件不能是ListView、GridView等可滚动组件,会造成滚动冲突。在实际使用过程中,Flutter提供了SliverList、SliverGrid等可滚动组件的Sliver版本。

ListView、GridView自带滚动模型,SliverList、SliverGrid不包含滚动模型,不会造成滚动冲突。

CustomScrollView组件的构造函数:

代码语言:javascript
复制
class CustomScrollView extends ScrollView {
  const CustomScrollView({
    Key key,
    Axis scrollDirection = Axis.vertical,//滚动的方向,默认在垂直方向滚动
    bool reverse = false,//控制从头还是从尾开始滚动,默认false,即从头开始滚动
    ScrollController controller,//控制滚动位置,当primary为true时,controller必须为null
    bool primary,//是否是与父级关联的主滚动视图
    ScrollPhysics physics,//设置滚动效果
    bool shrinkWrap = false,//子组件是否只满足自身大小
    Key center,//子组件的key值
    double anchor = 0.0,//开始滚动的偏移量,默认从坐标原点开始排列
    double cacheExtent,//缓存不可见的列表项,即使这部分区域不可见,也会被加载处理
    this.slivers = const <Widget>[],//列表子元素
    int semanticChildCount,//子项数量
    DragStartBehavior dragStartBehavior = DragStartBehavior.down,//开始处理拖拽行为的方式,默认为检测到拖拽手势时开始处理
  })
}

CustomScrollView组件通常被用于实现复杂的滚动效果,并且可以用来实现复杂的动画效果。

示例代码:

代码语言:javascript
复制
import 'package:flutter/material.dart';

void main() => runApp(ScollWidget());

class ScollWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '高级组件--可滚动组件',
      home: Scaffold(
        appBar: AppBar(title: Text('可滚动组件--CustomScrollView')),
        body: CustomScrollView(
          slivers: <Widget>[
            // 头部
            SliverAppBar(
              pinned: true,
              expandedHeight: 160.0,
              flexibleSpace: FlexibleSpaceBar(
                title: Text('CustomScrollView'),
                background: Image.asset('images/test1.png'),
              ),
            ),
            // 中间
            SliverGrid(
              gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
                maxCrossAxisExtent: 200.0, 
                mainAxisSpacing: 10.0,
                childAspectRatio: 3.0,
              ),
              delegate: SliverChildBuilderDelegate(
                (BuildContext context, int index) {
                  return Card(
                    child: Container(
                      alignment: Alignment.centerLeft,
                      padding: EdgeInsets.all(10),
                      child: Text('grid $index'),
                    ),
                  );
                },
                childCount: 11,
              ),
            ),
            // 底部
            SliverFixedExtentList(
              itemExtent: 60.0,
              delegate: SliverChildListDelegate(
                List.generate(20, (int index) {
                  return GestureDetector(
                    onTap: () => print('单击$index'),
                    child: Card(
                      child: Container(
                        alignment: Alignment.centerLeft,
                        padding: EdgeInsets.all(15),
                        child: Text('list $index'),
                      )
                    )
                  );
                })
              )
            )
          ],
        )
      )
    );
  }
}

示例效果:

6.1.5 ScrollController组件

如果需要监听可滚动组件的滚动过程,可以使用ScrollController组件来进行监听。

ScrollController组件的构造函数:

代码语言:javascript
复制
ScrollController({
  double initialScrollOffset = 0.0,//初始化滚动位置
  this.keepScrollOffset = true,//是否保持滚动位置
  this.debugLabel,
})

当keepScrollOffset的属性值为true时,可滚动组件的滚动位置会被存储到PageStorage中,当可滚动组件重新创建时可以使用PageStorage恢复存储的位置。

ScrollController组件还有如下属性和方法:

offset:可滚动组件当前的滚动位置;

jumpTo():用于跳转到指定的位置;

animateTo():跳转到指定位置,跳转时会执行设置的动画。

示例代码:

代码语言:javascript
复制
import 'package:flutter/material.dart';

void main() => runApp(ScrollControllerPage());

class ScrollControllerPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return ScrollControllerPageState();
  }
}

class ScrollControllerPageState extends State<ScrollControllerPage> {
  ScrollController controller = new ScrollController();
  bool showTopBtn = false;//是否显示按钮
  @override
  void initState() {
    super.initState();
    controller.addListener(() {
      if(controller.offset < 500 && showTopBtn) {
        setState(() {
          showTopBtn = false;
        });
      } else if (controller.offset >= 500 && !showTopBtn) {
        setState(() {
          showTopBtn = true;
        });
      }
    });
  }
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '高级组件--可滚动组件',
      home: Scaffold(
        appBar: AppBar(title: Text('可滚动组件--ScrollController')),
        body: ListView.builder(
          itemCount: 100,
          itemExtent: 50.0,
          controller: controller,
          itemBuilder: (context, index) {
            return ListTile(title: Text('列表Item $index'));
          },
        ),
        floatingActionButton: !showTopBtn ? null : 
          FloatingActionButton(
            child: Icon(Icons.arrow_upward),
            onPressed: () {
              controller.jumpTo(0);
            }
          ) 
      )
    );
  }
}

示例效果:

在有多个组件嵌套的组件树中,组件树的子组件可以通过发送通知来与父组件进行通信,父组件则可以通过NotificationListener组件来监听自己关注的通知,这种跨组件的通信方式通常被称为事件冒泡。

接收滚动事件的参数类型为ScrollNotification,它提供了一个metrics属性,该属性包含了当前可视窗口和滚动位置等信息。

NotificationListener组件支持的属性如下:

pixels:当前滚动位置;

maxScrollExtent:最大可滚动长度;

extentBefore:距离滚出视图窗口顶部的长度;

extentInside:视图窗口内部长度,大小等于屏幕显示的列表长度;

extentAfter:列表中未滑入视图窗口部分的长度;

atEdge:是否滚动到了可滚动组件的边界。 示例代码:

代码语言:javascript
复制
import 'package:flutter/material.dart';

void main() => runApp(ScrollNotificationPage());

class ScrollNotificationPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return ScrollNotificationPageState();
  }
}

class ScrollNotificationPageState extends State<ScrollNotificationPage> {
  String _progress = '0%';
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '高级组件--可滚动组件',
      home: Scaffold(
        appBar: AppBar(title: Text('可滚动组件--NotificationListener')),
        body: Scrollbar(
          child: NotificationListener<ScrollNotification>(
            onNotification: (ScrollNotification notification) {
              double progress = notification.metrics.pixels / notification.metrics.maxScrollExtent;
              setState(() {
                _progress = '${(progress * 100).toInt()}%';
              });
              return null;
            },
            child: Stack(
              alignment: Alignment.center,
              children: <Widget>[
                ListView.builder(
                  itemCount: 100,
                  itemExtent: 50.0,
                  itemBuilder: (context, index) {
                    return ListTile(title: Text('标题 $index'));
                  },
                ),
                CircleAvatar(
                  radius: 30.0,
                  child: Text(_progress),
                  backgroundColor: Colors.black54,
                )
              ]
            )
          )
        )
      )
    );
  }
}

示例效果:

NotificationListener组件和ScrollController组件都可以实现列表滚动的监听。NotificationListener组件可以监听可滚动组件的整个组件树,并且监听到的信息更多,ScrollController则只能监听关联的可滚动组件的相关信息。

6.2 列表组件

6.2.1 ListView

ListView,即列表组件,作用类似于Android的RecyclerView或ListView。ListView可以沿一个线性方向排布相同或相似的子组件元素,并支持基于Sliver的延迟。

ListView的默认构造函数:

代码语言:javascript
复制
class ListView extends BoxScrollView {
  ListView({
    Key key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    EdgeInsetsGeometry padding,
    bool shrinkWrap = false,//是否根据列表项的总长度来设置ListView的长度,默认为false
    this.itemExtent,//列表项的大小。如果滚动方向是垂直方向,则表示子组件的高度;如果滚动方向为水平方向,则表示子组件的长度。
    bool addAutomaticKeepAlives = true,//是否将列表项包裹在AutomaticKeepAlive组件中,默认值为true,表示列表项滑出视图窗口时不会被垃圾回收,会保存之前的状态。
    bool addRepaintBoundaries = true,//是否将列表项包裹在RepaintBoundary组件中,默认值为true,可以避免列表项的重绘,提高渲染的性能。
    bool addSemanticIndexes = true,
    double cacheExtent, 
    List<Widget> children = const <Widget>[],
    int semanticChildCount,
    DragStartBehavior dragStartBehavior = DragStartBehavior .down,
  })
}

示例代码:

代码语言:javascript
复制
import 'package:flutter/material.dart';

void main() => runApp(ListViewWidget());

class ListViewWidget extends StatelessWidget {
  final _items = List<Widget>.generate(10, 
    (index) => Container(
      padding: EdgeInsets.all(16.0),
      child: Text('Item $index')
    )
  );
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '高级组件--列表组件',
      home: Scaffold(
        appBar: AppBar(title: Text('可滚动组件--列表组件')),
        body: ListView(children: _items)
      )
    );
  }
}

示例效果:

默认的构造函数适合只含有少量子组件的情况,因为它不支持基于Sliver的延迟加载,当列表的元素较多时,容易出现卡顿现象。

6.2.2 ListView.builder

使用ListView.builder创建的列表是基于Sliver的延迟加载创建的,渲染性能比较高,适合用于列表元素比较多的情况。

ListView.builder特有的属性:

1)itemBuilder:用于构建列表项的可见子组件构建器,只有索引>= 0且< itemCount时才会被调用;

2)itemCount:列表项的数量,如果为null,则列表为无限列表。

示例代码:

代码语言:javascript
复制
import 'package:flutter/material.dart';

void main() => runApp(ListViewWidget());

class ListViewWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '高级组件--列表组件',
      home: Scaffold(
        appBar: AppBar(title: Text('高级组件--列表组件')),
        body: ListView.builder(
          itemCount: 100,
          itemExtent: 50.0,
          itemBuilder: (BuildContext context, int index) {
            return ListTile(title: Text('Item $index'));
          }
        )
      )
    );
  }
}

示例效果:

6.2.3 ListView.separated

和ListView.builder相比,ListView.separated多了一个separatorBuilder属性,该属性可以在生成的列表项之间添加一条分割线。

示例代码:

代码语言:javascript
复制
import 'package:flutter/material.dart';

void main() => runApp(ListViewWidget());

class ListViewWidget extends StatelessWidget {
  Widget divider = Divider(color: Colors.grey);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '高级组件--列表组件',
      home: Scaffold(
        appBar: AppBar(title: Text('高级组件--列表组件')),
        body: ListView.separated(
          itemCount: 100,
          itemBuilder: (BuildContext context, int index) {
            return ListTile(title: Text('Item $index'));
          },
          separatorBuilder: (BuildContext context, int index) {
            return divider;
          }, 
        )
      )
    );
  }
}

示例效果:

6.2.4 ListView.custom

ListView.custom适用于自定义列表的场景。其中,childrenDelegate是它的必传参数,需要传入一个实现了SliverChildDelegate抽象类的组件,用来给ListView组件添加列表项。

SliverChildDelegate是一个抽象类,它的实现类有SliverChildListDelegate和SliverChildBuilderDelegate,并且SliverChildDelegate的build()可以对单个子组件进行自定义样式处理。

示例代码:

代码语言:javascript
复制
import 'package:flutter/material.dart';

void main() => runApp(ListViewWidget());

class ListViewWidget extends StatelessWidget {
  final _items = List<Widget>.generate(100, 
    (i) => Container(
      padding: EdgeInsets.all(16.0),
      child: Text('Item $i')
    )
  );
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '高级组件--列表组件',
      home: Scaffold(
        appBar: AppBar(title: Text('高级组件--列表组件')),
        body: ListView.custom(
          childrenDelegate: SliverChildListDelegate(_items),
        )
      )
    );
  }
}

示例效果:

如果滚动视图中出现列表嵌套的场景,为了不造成滚动时的冲突,需要对子组件添加禁止滚动属性。

代码语言:javascript
复制
ListView.builder(
  ...
  physics: NeverScrollableScrollPhysics(),//禁止滚动
  ...
)
6.3 网格组件
6.3.1 GridView基础

GridView是一个可以构建二维网格的列表组件,作用类似于原生Android中的GridView/RecyclerView或者iOS的UICollectionView。

GridView的默认构造函数:

代码语言:javascript
复制
GridView({
  Key key,
  Axis scrollDirection = Axis.vertical,
  bool reverse = false,
  ScrollController controller,
  bool primary, 
  ScrollPhysics physics,
  bool shrinkWrap = false,
  EdgeInsetsGeometry padding,
  @required this.gridDelegate,//类型是SliverGridDelegate,控制GridView子组件的排列方式
  bool addAutomaticKeepAlives = true,
  bool addRepaintBoundaries = true,
  bool addSemanticIndexes = true,
  double cacheExtent,
  List<Widget> children = const <Widget>[],
  int semanticChildCount,  
})

SliverGridDelegate是一个抽象类,是一个控制子元素排列方式的接口,有两个实现类:

1)SliverGridDelegateWithFixedCrossAxisCount:用于列数固定的场景

代码语言:javascript
复制
SliverGridDelegateWithFixedCrossAxisCount({
  @required double crossAxisCount,//列数
  double mainAxisSpacing = 0.0,//主轴方向上子组件的间距
  double crossAxisSpacing = 0.0,//横轴方向上子组件的间距
  double childAspectRatio = 1.0,//子组件的宽高比  
})

示例代码:

代码语言:javascript
复制
import 'package:flutter/material.dart';

void main() => runApp(GridWidget());

class GridWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '高级组件--网格组件',
      home: Scaffold(
        appBar: AppBar(title: Text('高级组件--网格组件')),
        body: GridView(
          gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 3,
            childAspectRatio: 1.5
          ),
          children: <Widget>[
            Icon(Icons.ac_unit),
            Icon(Icons.airport_shuttle),
            Icon(Icons.all_inclusive),
            Icon(Icons.beach_access),
            Icon(Icons.cake),
            Icon(Icons.free_breakfast),
          ],
        )
      )
    );
  }
}

示例效果:

SliverGridDelegateWithFixedCrossAxisCount还可以使用GridView.count进行代替:

代码语言:javascript
复制
...
body: GridView.count(
  crossAxisCount: 3,
  childAspectRatio: 1.5,
  children: <Widget>[
    Icon(Icons.ac_unit),
    ...
  ],
)
...

2)SliverGridDelegateWithMaxCrossAxisExtent:用于子元素有最大宽度限制的场景

代码语言:javascript
复制
SliverGridDelegateWithMaxCrossAxisExtent({
  @required this.maxCrossAxisExtent,//子元素在横轴上的最大长度
  this.mainAxisSpacing = 0.0,//主轴方向上子组件的间距
  this.crossAxisSpacing = 0.0,//横轴方向上子组件的间距
  this.childAspectRatio = 1.0,//子组件的宽高比 
})

示例代码:

代码语言:javascript
复制
import 'package:flutter/material.dart';

void main() => runApp(GridWidget());

class GridWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '高级组件--网格组件',
      home: Scaffold(
        appBar: AppBar(title: Text('高级组件--网格组件')),
        body: GridView(
          padding: EdgeInsets.zero,
          gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
            maxCrossAxisExtent: 120.0,
            childAspectRatio: 2.0
          ),
          children: <Widget>[
            Icon(Icons.ac_unit),
            Icon(Icons.airport_shuttle),
            Icon(Icons.all_inclusive),
            Icon(Icons.beach_access),
            Icon(Icons.cake),
            Icon(Icons.free_breakfast),
          ],
        )
      )
    );
  }
}

示例效果:

SliverGridDelegateWithFixedCrossAxisCount还可以使用GridView.extent()代替:

代码语言:javascript
复制
...
body: GridView.extent(
  padding: EdgeInsets.zero,
  maxCrossAxisExtent: 120.0,
  childAspectRatio: 2.0,
  children: <Widget>[
    Icon(Icons.ac_unit),
    ...
  ],
)
...
6.3.2 GridView构造函数

GridView的构造函数一共有5个:

1)GridView():默认构造函数,适用于元素个数有限的场景,会一次性全部渲染children属性中的子元素组件;

2)GridView.builder():适用于构建大量或无限长的列表,它只会构建那些可见的组件,对于不可见的会动态销毁,减少内存销毁,渲染更高效;必须要传入gridDelegate和itemBuilder属性;

3)GridView.count():SliverGridDelegateWithFixedCrossAxisCount实现类的简写,用于创建横轴数量固定的网格视图;

4)GridView.extent():SliverGridDelegateWithFixedCrossAxisCount实现类的简写,用于创建横轴子元素宽度固定的网格视图;

5)GridView.custom():自定义的网格视图,需要同时传入gridDelegate和childrenDelegate。 示例代码:

代码语言:javascript
复制
import 'package:flutter/material.dart';

void main() => runApp(GridViewWidget());

class ItemViewModel {
  final String icon;
  final String title;
  const ItemViewModel({this.icon, this.title});
}

class GridItem extends StatelessWidget {
  final ItemViewModel data;
  GridItem({Key key, this.data}): super(key: key);
  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.only(bottom: 5),
      child: Column(
        children: <Widget>[
          Image.asset(this.data.icon, width: 55, fit: BoxFit.fitWidth),
          Text(this.data.title)
        ],
      ),
    );
  }
}

const List<ItemViewModel> list = [
  ItemViewModel(title: '微信', icon: 'images/wx.png'),
  ItemViewModel(title: 'QQ', icon: 'images/qq.png'),
  ItemViewModel(title: '微信', icon: 'images/wx.png'),
  ItemViewModel(title: 'QQ', icon: 'images/qq.png'),
  ItemViewModel(title: '微信', icon: 'images/wx.png'),
  ItemViewModel(title: 'QQ', icon: 'images/qq.png'),
  ItemViewModel(title: '微信', icon: 'images/wx.png'),
  ItemViewModel(title: 'QQ', icon: 'images/qq.png'),
];

class GridViewWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '高级组件--网格组件',
      home: Scaffold(
        appBar: AppBar(title: Text('高级组件--网格组件')),
        body: GridView.builder(
          gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4), 
          itemCount: list.length,
          padding: EdgeInsets.symmetric(vertical: 10),
          itemBuilder: (context, index) {
            return GridItem(data: list[index]);
          }
        )
      )
    );
  }
}

示例效果:

6.4 滑动切换组件

PageView是一个滑动视图列表组件,它继承自CustomScrollView,作用类似于Android的ViewPager,可以用它实现视图的左右滑动切换功能。

PageView的构造函数:

1)PageView():默认构造函数,创建一个可滚动列表,适合子组件比较少的场景;

代码语言:javascript
复制
PageView({
  Key key,
  this.scrollDirection = Axis.horizontal,
  this.reverse = false,
  PageController controller,
  this.physics,
  this.pageSnapping = true,
  this.onPageChanged,//页面滑动切换时调用
  this.semanticChildCount,//列表项的数量
  List<Widget> children = const <Widget>[],//PageView的列表项
  this.dragStartBehavior = DragStartBehavior.down,//处理拖拽开始行为的方式,默认为检测到拖拽手势时开始执行滚动拖拽行为
})

2)PageView.builder():创建一个滚动列表,适合子组件比较多的场景,需要指定子组件的数量;

3)PageView.custom():创建一个可滚动的列表,需要自定义子项。

示例代码:

代码语言:javascript
复制
import 'package:flutter/material.dart';

void main() => runApp(PageViewWidget());

const List<String> items = [
  'images/test1.png',
  'images/test2.png',
];

class PageViewWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '高级组件--滑块切换组件',
      home: Scaffold(
        appBar: AppBar(title: Text('高级组件--滑块切换组件')),
        body: PageView.builder(
          onPageChanged: (index) {
            print('current page $index');
          },
          itemCount: items.length,
          itemBuilder: (context, index) {
            return Image.asset(items[index]);
          }
          )
      )
    );
  }
}

示例效果:

6.5 自定义组件
6.5.1 组合组件

按照从上到下、从左到右的方式去拆解布局结构即可。

6.5.2 自绘组件

在Flutter中创建自绘组件需要用到CustomPaint和CustomPainter两个类:CustomPaint在绘制阶段提供一个Canvas,即画布;CustomPainter在绘制阶段提供画笔,可配置画笔的颜色、样式和粗细等属性。

示例代码:

代码语言:javascript
复制
import 'dart:math';
import 'package:flutter/material.dart';

void main() => runApp(PiePageWidget());

class PiePageWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '高级组件--自绘组件',
      home: Scaffold(
        appBar: AppBar(title: Text('高级组件--自绘组件')),
        body: Center(
          child: CustomPaint(
            size: Size(300, 300),
            painter: PiePainter()
          )
        )
      )
    );
  }
}

class PiePainter extends CustomPainter {
  Paint getPaint(Color color) {
    Paint paint = Paint();
    paint.color = color;
    return paint;
  }
  @override
  void paint(Canvas canvas, Size size) {
    double wheelSize = min(size.width, size.height) / 2;
    double nbElem = 6;
    double radius = (2 * pi) / nbElem;
    Rect boundingRect = Rect.fromCircle(center: Offset(wheelSize, wheelSize), radius: wheelSize);
    canvas.drawArc(boundingRect, 0, radius, true, getPaint(Colors.red));
    canvas.drawArc(boundingRect, radius, radius, true, getPaint(Colors.black38));
    canvas.drawArc(boundingRect, radius * 2, radius, true, getPaint(Colors.green));
    canvas.drawArc(boundingRect, radius * 3, radius, true, getPaint(Colors.amber));
    canvas.drawArc(boundingRect, radius * 4, radius, true, getPaint(Colors.blue));
    canvas.drawArc(boundingRect, radius * 5, radius, true, getPaint(Colors.purple));
  }
  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;//是否需要执行重绘
  }
}

示例效果:

创建Flutter自绘组件时,可以做以下两点性能优化:

1)尽可能利用好shouldRepaint()的返回值

如果绘制的内容不需要依赖外部状态,返回false即可;如果绘制过程需要依赖外部状态,可以在shouldRepaint()中判断依赖的状态是否改变,如果已改变,则返回true并执行重绘操作,反之则返回false不执行重绘;

2)绘制应尽可能多地进行分层

因为复杂的自绘组件都是由很多功能构成的,如果都写在一个方法中,不利于阅读,而且全部重绘带来的性能开销也很大。分层渲染可以降低视图渲染带来的性能开销。

无论是创建组合组件还是创建自绘组件,首先需要考虑如何将复杂的布局简化,把大问题拆分成若干小问题。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-04-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 爱学习的程序媛 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 6.1 可滚动组件
    • 6.1.1 Scrollable组件
      • 6.1.2 Scrollbar组件
        • 6.1.3 SingleChildScrollView组件
          • 6.1.4 CustomScrollView组件
            • 6.1.5 ScrollController组件
            • 6.2 列表组件
            • 6.3 网格组件
              • 6.3.1 GridView基础
                • 6.3.2 GridView构造函数
                • 6.4 滑动切换组件
                • 6.5 自定义组件
                  • 6.5.1 组合组件
                    • 6.5.2 自绘组件
                    相关产品与服务
                    容器服务
                    腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档