Flutter滚动定位插件scroll_positioned的使用

Flutter滚动定位插件scroll_positioned的使用

Scroll Positioned

允许你在滚动时将小部件固定在指定位置。它可以相对于父小部件或绝对位置进行定位。 最佳使用场景是与可滚动的小部件(如 ListView)结合使用,但也可以单独使用。

此插件使用了 PositionedStack 小部件。如果没有 Stack,它会自动创建一个。

对于绝对位置,此插件使用了 Transform.translate 小部件。

特性

  • 将小部件固定在滚动视图中。
  • 支持水平和垂直滚动。
  • 可以设置相对于顶部、左侧、右侧或底部的位置。
  • 可以设置大小。
  • 使用 ScrollPositioned.extend 可以扩展小部件相对于父小部件。
  • 使用 ScrollPositioned.absolute 可以设置小部件的绝对位置。

开始使用

安装插件:

dart pub add scroll_positioned

导入包:

import 'package:scroll_positioned/scroll_positioned.dart';

使用方法

要将小部件固定在当前位置,只需这样做:

ScrollPositioned(
  child: ...,
);

设置相对于父小部件的顶部、左侧、右侧或底部位置:

ScrollPositioned(
  top: 50,
  left: 0,
  right: 0,

  child: ...,
);

通过 widthheight 设置小部件的大小,或者使用 ScrollPositioned.extend 相对于父小部件进行扩展:

ScrollPositioned(
  width: 100,
  height: 100,

  child: ...,
);

ScrollPositioned.extend(
  child: ...,
);

将小部件定位到绝对位置,不能同时设置底部和右侧:

ScrollPositioned.absolute(
  top: 100,
  left: 100,

  child: ...,
);

为了灵活性,可以传递一个滚动控制器。如果不传递,则会尝试查找一个:

final scrollController = ScrollController();
ScrollPositioned(
  controller: scrollController,
  child: ...,
);

在某些情况下,当使用 ScrollPositioned.absolute 并且父小部件的子元素大小发生变化时,可能会导致小部件移动位置。除非重新构建,否则绝对位置不会更新。

示例

示例1

点击以在 DartPad 上互动

final size = MediaQuery.of(context).size;

ListView(
  children: [
    const SizedBox(
      height: 300,
      child: ScrollPositioned.expand(
        child: Center(child: FlutterLogo(size: 150)),
      ),
    ),
    Container(
      height: size.height - 24 - 15 * 2,
      padding: const EdgeInsets.only(top: 30),
      margin: const EdgeInsets.only(left: 15, right: 15, bottom: 15),
      decoration: BoxDecoration(
        color: const Color(0xff30B3EE),
        borderRadius: BorderRadius.circular(30),
      ),
      child: const Center(
        child: Opacity(
          opacity: 0.9,
          child: ColorFiltered(
            colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
            child: FlutterLogo(size: 100),
          ),
        )
      ),
    ),
  ],
);

示例2

点击以在 DartPad 上互动

final size = MediaQuery.of(context).size;

ListView(
  children: [
    Container(height: size.height * 0.25, color: Colors.cyan),
    SizedBox(
      height: size.height * 0.5,
      child: ScrollPositioned.absolute(
        width: size.width,
        height: size.height,
        child: const Center(child: FlutterLogo(size: 200)),
      ),
    ),
    Container(height: size.height * 0.25, color: Colors.blue),
    Container(height: size.height * 0.25, color: Colors.green),
    SizedBox(
      height: size.height * 0.5,
      child: ScrollPositioned.absolute(
        width: size.width,
        height: size.height,
        child: const Center(
          child: ColorFiltered(
            colorFilter: ColorFilter.mode(Colors.green, BlendMode.srcIn),
            child: FlutterLogo(size: 200),
          )
        ),
      ),
    ),
    Container(height: size.height * 0.25, color: Colors.green),
    Container(height: size.height * 0.25, color: Colors.red),
    SizedBox(
      height: size.height * 0.5,
      child: ScrollPositioned.absolute(
        width: size.width,
        height: size.height,
        child: const Center(
          child: ColorFiltered(
            colorFilter: ColorFilter.mode(Colors.red, BlendMode.srcIn),
            child: FlutterLogo(size: 200),
          )
        ),
      ),
    ),
    Container(height: size.height * 0.25, color: Colors.red),
    Container(height: size.height * 0.25, color: Colors.cyan),
    SizedBox(
      height: size.height * 0.5,
      child: ScrollPositioned.absolute(
        width: size.width,
        height: size.height,
        child: const Center(child: FlutterLogo(size: 200)),
      ),
    ),
    Container(height: size.height * 0.25, color: Colors.blue),
  ],
);

示例3

点击以在 DartPad 上互动

final size = MediaQuery.of(context).size;

ListView(
  children: [
    SizedBox(height: (size.height - 200) / 2),
    SizedBox(
      height: 200,
      child: ScrollPositioned.absolute(
        width: size.width,
        height: size.height,
        child: const Center(child: FlutterLogo(size: 200)),
      ),
    ),
    SizedBox(
      height: 150,
      child: ScrollPositioned.absolute(
        width: size.width,
        height: size.height,
        child: const Center(
          child: ColorFiltered(
            colorFilter: ColorFilter.mode(Colors.green, BlendMode.srcIn),
            child: FlutterLogo(size: 200),
          ),
        ),
      ),
    ),
    SizedBox(
      height: 150,
      child: ScrollPositioned.absolute(
        width: size.width,
        height: size.height,
        child: const Center(
          child: ColorFiltered(
            colorFilter: ColorFilter.mode(Colors.red, BlendMode.srcIn),
            child: FlutterLogo(size: 200),
          ),
        ),
      ),
    ),
    SizedBox(
      height: 150,
      child: ScrollPositioned.absolute(
        width: size.width,
        height: size.height,
        child: const Center(
          child: ColorFiltered(
            colorFilter: ColorFilter.mode(Colors.orange, BlendMode.srcIn),
            child: FlutterLogo(size: 200),
          ),
        ),
      ),
    ),
    SizedBox(
      height: 150,
      child: ScrollPositioned.absolute(
        width: size.width,
        height: size.height,
        child: const Center(
          child: ColorFiltered(
            colorFilter: ColorFilter.mode(Colors.yellow, BlendMode.srcIn),
            child: FlutterLogo(size: 200),
          ),
        ),
      ),
    ),
    SizedBox(
      height: 200,
      child: ScrollPositioned.absolute(
        width: size.width,
        height: size.height,
        child: const Center(child: FlutterLogo(size: 200)),
      ),
    ),
    SizedBox(height: (size.height - 200) / 2),
  ],
);

小贴士

要为它添加动画效果,可以使用滚动控制器 (ScrollController),animateTo 方法和 Timer.periodic

ScrollController? controller;

[@override](/user/override)
void initState() {
  controller = ScrollController();
  bool tick = false;
  Timer.periodic(const Duration(seconds: 4), (timer) {
    tick = !tick;
    controller?.animateTo(
      tick ? controller?.position.maxScrollExtent ?? 0 : 0,
      duration: const Duration(seconds: 3),
      curve: Curves.easeInOut,
    );
  });
  super.initState();
}

更多关于Flutter滚动定位插件scroll_positioned的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter滚动定位插件scroll_positioned的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


在Flutter中,scroll_positioned 插件可以帮助你在滚动视图(如 ListViewCustomScrollView)中定位到特定的子项。虽然 scroll_positioned 不是一个官方的 Flutter 插件,但假设你指的是如何在 Flutter 中实现滚动定位功能,我们可以使用 Flutter 自带的 ScrollController 和一些自定义逻辑来实现。

以下是一个使用 ScrollControllerScrollPositionWithOffset 来实现滚动定位的代码示例:

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Scroll Position Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: ScrollPositionDemo(),
    );
  }
}

class ScrollPositionDemo extends StatefulWidget {
  @override
  _ScrollPositionDemoState createState() => _ScrollPositionDemoState();
}

class _ScrollPositionDemoState extends State<ScrollPositionDemo> {
  final ScrollController _scrollController = ScrollController();
  final List<String> _items = List<String>.generate(100, (i) => "Item $i");

  @override
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }

  void _scrollToIndex(int index) {
    // 计算目标位置,假设每个item的高度是50
    final double itemHeight = 50.0;
    final double targetOffset = index * itemHeight;
    _scrollController.animateTo(
      targetOffset,
      duration: Duration(milliseconds: 300),
      curve: Curves.ease,
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Scroll Position Demo'),
      ),
      body: Column(
        children: <Widget>[
          Expanded(
            child: ListView.builder(
              controller: _scrollController,
              itemCount: _items.length,
              itemBuilder: (context, index) {
                return ListTile(
                  title: Text(_items[index]),
                );
              },
            ),
          ),
          SizedBox(height: 10),
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: List<int>.generate(10, (i) => i * 10)
                .map<Widget>((int index) {
              return ElevatedButton(
                onPressed: () => _scrollToIndex(index),
                child: Text("Scroll to Item ${index}"),
              );
            }).toList(),
          ),
        ],
      ),
    );
  }
}

在这个示例中,我们创建了一个包含100个项目的 ListView。每个项目的高度假设为50。我们提供了一个 ScrollController 来控制滚动视图,并通过 _scrollToIndex 方法计算并滚动到指定的项目。

注意:

  1. 实际应用中,如果每个项目的高度不同,你可能需要使用 RenderBoxGlobalKey 来获取每个项目的实际高度。
  2. scroll_positioned 插件如果存在,通常会有更简洁的API来处理滚动定位,但Flutter社区和官方文档中并没有直接提及这个名称,因此上述示例展示了使用核心功能实现相同效果的方法。

希望这个示例能帮助你理解如何在Flutter中实现滚动定位功能。

回到顶部