Flutter之可滑动Widget

可滚动Widget

  • Flutter和Dart系列文章代码GitHub地址
  • Flutter中, 当内容超过显示视图时,如果没有特殊处理,Flutter则会提示Overflow错误
  • Flutter提供了多种可滚动(Scrollable Widget)用于显示列表和长布局
  • 可滚动Widget都直接或间接包含一个Scrollable, 下面是常用的几个可滚动的Widget
    • SingleChildScrollView
    • ListView
    • GridView
    • CustomScrollView
    • 滚动监听及控制ScrollController

Scrollbar

  • Scrollbar是一个Material风格的滚动指示器(滚动条),如果要给可滚动widget添加滚动条,只需将Scrollbar作为可滚动widget的父widget即可
  • CupertinoScrollbariOS风格的滚动条,如果你使用的是Scrollbar,那么在iOS平台它会自动切换为CupertinoScrollbar
  • ScrollbarCupertinoScrollbar都是通过ScrollController来监听滚动事件来确定滚动条位置,关于ScrollController详细的内容我们将在后面专门一节介绍
  • 下面是ScrollbarCupertinoScrollbar的构造函数, 都只有一个child属性, 用于接受一个可滚动的Widget
const Scrollbar({
    Key key,
    @required this.child,
})

const CupertinoScrollbar({
    Key key,
    @required this.child,
})

主轴和纵轴

  • 在可滚动widget的坐标描述中,通常将滚动方向称为主轴,非滚动方向称为纵轴。
  • 由于可滚动widget的默认方向一般都是沿垂直方向,所以默认情况下主轴就是指垂直方向,水平方向同理

SingleChildScrollView

SingleChildScrollView类似于开发中常用的ScrollView, 不再详细介绍了, 下面看一下具体使用介绍吧

const SingleChildScrollView({
    Key key,
    // 设置滚动的方向, 默认垂直方向
    this.scrollDirection = Axis.vertical,
    // 设置显示方式
    this.reverse = false,
    // 内边距
    this.padding,
    // 是否使用默认的controller
    bool primary,
    // 设置可滚动Widget如何响应用户操作
    this.physics,
    this.controller,
    this.child,
})

scrollDirection

设置视图的滚动方向(默认垂直方向), 需要对应的设置其子WidgetColumn或者Row, 否则会报Overflow错误

scrollDirection: Axis.vertical,

// 枚举值
enum Axis {
  /// 水平滚动
  horizontal,
  /// 垂直滚动
  vertical,
}

reverse

  • 是否按照阅读方向相反的方向滑动
  • 设置水平滚动时
    • reverse: false,则滚动内容头部和左侧对其, 那么滑动方向就是从左向右
    • reverse: true时,则滚动内容尾部和右侧对其, 那么滑动方向就是从右往左。
  • 其实此属性本质上是决定可滚动widget的初始滚动位置是在头还是尾,取false时,初始滚动位置在头,反之则在尾

physics

  • 此属性接受一个ScrollPhysics对象,它决定可滚动Widget如何响应用户操作
  • 比如用户滑动完抬起手指后,继续执行动画;或者滑动到边界时,如何显示。
  • 默认情况下,Flutter会根据具体平台分别使用不同的ScrollPhysics对象,应用不同的显示效果,如当滑动到边界时,继续拖动的话,在iOS上会出现弹性效果,而在Android上会出现微光效果。
  • 如果你想在所有平台下使用同一种效果,可以显式指定,Flutter SDK中包含了两个ScrollPhysics的子类可以直接使用:
    • ClampingScrollPhysics:安卓下微光效果。
    • BouncingScrollPhysicsiOS下弹性效果。

controller

  • 此属性接受一个ScrollController对象
  • ScrollController的主要作用是控制滚动位置和监听滚动事件。
  • 默认情况下,widget中会有一个默认的PrimaryScrollController,如果子widget中的可滚动widget没有显式的指定controller并且primary属性值为true时(默认就为true),可滚动widget会使用这个默认的PrimaryScrollController
  • 这种机制带来的好处是父widget可以控制子树中可滚动widget的滚动,例如,Scaffold使用这种机制在iOS中实现了"回到顶部"的手势

代码示例

class ScrollView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    return Scrollbar(
      child: SingleChildScrollView(
        scrollDirection: Axis.vertical,
        reverse: true,
        padding: EdgeInsets.all(0.0),
        physics: BouncingScrollPhysics(),
        child: Center(
          child: Column( 
            //动态创建一个List<Widget>  
            children: str.split("") 
                //每一个字母都用一个Text显示,字体为原来的两倍
                .map((c) => Text(c, textScaleFactor: 2.0)) 
                .toList(),
          ),
        ),
      ),
    );
  }
}

ListView

  • ListView是最常用的可滚动widget,它可以沿一个方向线性排布所有子widget, 类似于ReactNative中的ListView
  • ListView共有四种构造函数
    • ListView()默认构造函数
    • ListView.builder()
    • ListView.separated()
    • ListView custom()
ListView({
    // 公共参数上面都介绍过了
    Key key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    EdgeInsetsGeometry padding,
    
    // 是否根据子widget的总长度来设置ListView的长度,默认值为false
    bool shrinkWrap = false,
    // cell高度
    this.itemExtent,
    // 子widget是否包裹在AutomaticKeepAlive中
    bool addAutomaticKeepAlives = true,
    // 子widget是否包裹在RepaintBoundary中
    bool addRepaintBoundaries = true,
    bool addSemanticIndexes = true,
    // 设置预加载的区域, moren 0.0
    double cacheExtent,
    //子widget列表
    List<Widget> children = const <Widget>[],
    // 子widget的个数
    int semanticChildCount,
})

属性介绍

shrinkWrap
  • 表示是否根据子widget的总长度来设置ListView的长度,默认值为false
  • 默认情况下,ListView的会在滚动方向尽可能多的占用空间
  • ListView在一个无边界(滚动方向上)的容器中时,shrinkWrap必须为true
itemExtent
  • 该参数如果不为null,则会强制children的"长度"为itemExtent的值
  • 这里的"长度"是指滚动方向上子widget的长度,即如果滚动方向是垂直方向,则代表子widget的高度,如果滚动方向为水平方向,则代表子widget的长度
  • ListView中,指定itemExtent比让子widget自己决定自身长度会更高效,这是因为指定itemExtent后,滚动系统可以提前知道列表的长度,而不是总是动态去计算,尤其是在滚动位置频繁变化时
addAutomaticKeepAlives
  • 表示是否将列表项包裹在AutomaticKeepAlive
  • 在一个懒加载列表中,如果将列表项包裹在AutomaticKeepAlive中,在该列表项滑出视口时该列表项不会被GC,它会使用KeepAliveNotification来保存其状态
  • 如果列表项自己维护其KeepAlive状态,那么此参数必须置为false
addRepaintBoundaries
  • 性表示是否将列表项包裹在RepaintBoundary
  • 当可滚动widget滚动时,将列表项包裹在RepaintBoundary中可以避免列表项重绘,但是当列表项重绘的开销非常小(如一个颜色块,或者一个较短的文本)时,不添加RepaintBoundary反而会更高效
  • addAutomaticKeepAlive一样,如果列表项自己维护其KeepAlive状态,那么此参数必须置为false
使用示例
class ScrollView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ListView(
      itemExtent: 60,
      cacheExtent: 100,
      addAutomaticKeepAlives: false,
      children: r
### 实现 Flutter DataTable2 行内容水平滑动效果 在 Flutter 中实现 `DataTable2` 的行内容水平滑动效果,可以通过结合 `SingleChildScrollView` 和自定义的 `Row` 布局来完成。以下是一个详细的解决方案[^1]。 首先,`DataTable2` 是一个增强版的 `DataTable`,它提供了更多的功能和灵活性。为了实现行内容的水平滑动,可以将每一行的内容包裹在一个 `SingleChildScrollView` 中,并设置其滚动方向为水平(`Axis.horizontal`)。以下是具体代码示例: ```dart import 'package:flutter/material.dart'; import 'package:data_table_2/data_table_2.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: Text('DataTable2 水平滑动示例')), body: SingleChildScrollView( child: DataTable2( columnSpacing: 12, horizontalMargin: 12, minWidth: 800, columns: [ DataColumn2(label: Text('ID'), size: ColumnSize.S), DataColumn2(label: Text('Name'), size: ColumnSize.M), DataColumn2(label: Text('Description'), size: ColumnSize.L), ], rows: List<DataRow>.generate( 10, (index) => DataRow( cells: [ DataCell(Container( width: 50, child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Text('ID-${index + 1}'), ), )), DataCell(Container( width: 100, child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Text('Name-${index + 1}'), ), )), DataCell(Container( width: 300, child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Text( 'This is a long description for row ${index + 1}. It should be scrollable horizontally.'), ), )), ], ), ), ), ), ), ); } } ``` #### 代码解析 1. **`DataTable2`**:使用 `DataTable2` 替代标准的 `DataTable`,因为它支持更多自定义功能。 2. **`SingleChildScrollView`**:为每个单元格的内容添加一个 `SingleChildScrollView`,并设置 `scrollDirection: Axis.horizontal`,以实现水平滑动[^2]。 3. **`Container` 宽度限制**:通过设置 `Container` 的宽度,确保内容超出容器时可以触发滚动条。 #### 注意事项 - 如果需要全局水平滚动,可以将整个表格包裹在一个 `SingleChildScrollView` 中[^1]。 - 对于性能优化,建议使用 `ListView.builder` 或 `DataTable2` 的分页功能,避免一次性加载过多数据[^2]。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值