Flutter可变形列表视图插件transformable_list_view的使用

发布于 1周前 作者 ionicwang 来自 Flutter

Flutter可变形列表视图插件transformable_list_view的使用

Flutter的transformable_list_view插件提供了一种创建带有自定义变换动画的滚动小部件的方法。此包中包含3个主要的小部件:TransformableListViewTransformableSliverListTransformableSliver,它们分别扩展了ListViewSliverListSliverToBoxAdapter

Features

  • TransformableListView: 扩展了ListView,允许你对列表项应用自定义变换。
  • TransformableSliverList: 扩展了SliverList,提供了类似的功能。
  • TransformableSliver: 扩展了SliverToBoxAdapter,适用于更复杂的布局需求。

每个小部件都接受一个getTransformMatrix回调,在这个回调中你需要返回一个表示当前时刻子元素变换的Matrix4对象。如果你不需要任何变换,可以简单地返回Matrix4.identity()

getTransformMatrix回调中,你会收到一个TransformableListItem对象,它包含了关于列表项的信息:

  • Offset offset: 子元素的主轴偏移量。
  • Size size: 子元素的大小。
  • SliverConstraints constraints: 当前滚动状态的约束条件。
  • int? index: 子元素的索引(对于TransformableSliver为null)。
  • TransformableListItemPosition position: 子元素在视口中的位置。
  • double visibleExtent: 子元素可见部分的高度或宽度。

Usage

示例代码

下面是一个完整的示例代码,展示了如何使用transformable_list_view插件创建一个具有缩放效果的列表:

import 'dart:math';

import 'package:flutter/material.dart';
import 'package:transformable_list_view/transformable_list_view.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Transformable List View Example',
      home: Builder(
        builder: (context) {
          return Scaffold(
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  TextButton(
                    onPressed: () {
                      Navigator.of(context).push(
                        MaterialPageRoute(
                          builder: (context) => const ListViewExample(
                            transformMatrix: TransformMatrices.scaleDown,
                          ),
                        ),
                      );
                    },
                    child: const Text('Scale Down Example'),
                  ),
                  TextButton(
                    onPressed: () {
                      Navigator.of(context).push(
                        MaterialPageRoute(
                          builder: (context) => const ListViewExample(
                            transformMatrix: TransformMatrices.rotate,
                          ),
                        ),
                      );
                    },
                    child: const Text('Rotate Example'),
                  ),
                  TextButton(
                    onPressed: () {
                      Navigator.of(context).push(
                        MaterialPageRoute(
                          builder: (context) => const ListViewExample(
                            transformMatrix: TransformMatrices.wheel,
                          ),
                        ),
                      );
                    },
                    child: const Text('Wheel Example'),
                  ),
                  TextButton(
                    onPressed: () {
                      Navigator.of(context).push(
                        MaterialPageRoute(
                          builder: (context) => const SliverExampleScreen(),
                        ),
                      );
                    },
                    child: const Text('Sliver Example'),
                  ),
                ],
              ),
            ),
          );
        },
      ),
    );
  }
}

class ListViewExample extends StatelessWidget {
  const ListViewExample({
    super.key,
    required this.transformMatrix,
  });

  final TransformMatrixCallback transformMatrix;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('ListView Example'),
      ),
      body: TransformableListView.builder(
        padding: EdgeInsets.zero,
        getTransformMatrix: transformMatrix,
        itemCount: 30,
        itemBuilder: (context, index) {
          return Container(
            height: 100,
            margin: const EdgeInsets.symmetric(
              horizontal: 16,
              vertical: 4,
            ),
            decoration: BoxDecoration(
              color: index.isEven ? Colors.grey : Colors.blueAccent,
              borderRadius: BorderRadius.circular(20),
            ),
            alignment: Alignment.center,
            child: Text(index.toString()),
          );
        },
      ),
    );
  }
}

class SliverExampleScreen extends StatelessWidget {
  const SliverExampleScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: [
          const SliverAppBar(
            title: Text('Sliver Example'),
            floating: true,
            snap: true,
          ),
          TransformableSliverList.builder(
            itemCount: 10,
            getTransformMatrix: TransformMatrices.scaleDown,
            itemBuilder: (context, index) {
              return Container(
                height: 100,
                margin: const EdgeInsets.symmetric(
                  horizontal: 16,
                  vertical: 4,
                ),
                decoration: BoxDecoration(
                  color: index.isEven ? Colors.grey : Colors.blueAccent,
                  borderRadius: BorderRadius.circular(20),
                ),
                alignment: Alignment.center,
                child: Text(index.toString()),
              );
            },
          ),
          TransformableSliverList.builder(
            itemCount: 10,
            getTransformMatrix: TransformMatrices.rotate,
            itemBuilder: (context, index) {
              return Container(
                height: 100,
                margin: const EdgeInsets.symmetric(
                  horizontal: 16,
                  vertical: 4,
                ),
                decoration: BoxDecoration(
                  color: index.isEven ? Colors.grey : Colors.green,
                  borderRadius: BorderRadius.circular(20),
                ),
                alignment: Alignment.center,
                child: Text(index.toString()),
              );
            },
          ),
          TransformableSliverList.builder(
            itemCount: 10,
            getTransformMatrix: TransformMatrices.wheel,
            itemBuilder: (context, index) {
              return Container(
                height: 100,
                margin: const EdgeInsets.symmetric(
                  horizontal: 16,
                  vertical: 4,
                ),
                decoration: BoxDecoration(
                  color: index.isEven ? Colors.grey : Colors.amber,
                  borderRadius: BorderRadius.circular(20),
                ),
                alignment: Alignment.center,
                child: Text(index.toString()),
              );
            },
          ),
        ],
      ),
    );
  }
}

class TransformMatrices {
  static Matrix4 scaleDown(TransformableListItem item) {
    /// 最终缩放比例
    const endScaleBound = 0.3;

    /// 动画进度
    final animationProgress = item.visibleExtent / item.size.height;

    /// 结果矩阵
    final paintTransform = Matrix4.identity();

    /// 仅当项目位于边缘时进行动画
    if (item.position != TransformableListItemPosition.middle) {
      final scale = endScaleBound + ((1 - endScaleBound) * animationProgress);

      paintTransform
        ..translate(item.size.width / 2)
        ..scale(scale)
        ..translate(-item.size.width / 2);
    }

    return paintTransform;
  }

  static Matrix4 rotate(TransformableListItem item) {
    /// 旋转90度
    const maxRotationTurnsInRadians = pi / 2.0;

    /// 动画进度
    final animationProgress = 1 - item.visibleExtent / item.size.height;

    /// 结果矩阵
    final paintTransform = Matrix4.identity();

    /// 仅当项目位于边缘时进行动画
    if (item.position != TransformableListItemPosition.middle) {
      /// 左转或右转
      final isEven = item.index?.isEven ?? false;

      /// 选择旋转的角点
      final FractionalOffset fractionalOffset;
      final int rotateDirection;

      switch (item.position) {
        case TransformableListItemPosition.topEdge:
          fractionalOffset = isEven
              ? FractionalOffset.bottomLeft
              : FractionalOffset.bottomRight;
          rotateDirection = isEven ? -1 : 1;
          break;
        case TransformableListItemPosition.middle:
          return paintTransform;
        case TransformableListItemPosition.bottomEdge:
          fractionalOffset =
              isEven ? FractionalOffset.topLeft : FractionalOffset.topRight;
          rotateDirection = isEven ? 1 : -1;
          break;
      }

      final rotateAngle = animationProgress * maxRotationTurnsInRadians;
      final translation = fractionalOffset.alongSize(item.size);

      paintTransform
        ..translate(translation.dx, translation.dy)
        ..rotateZ(rotateDirection * rotateAngle)
        ..translate(-translation.dx, -translation.dy);
    }

    return paintTransform;
  }

  static Matrix4 wheel(TransformableListItem item) {
    /// 旋转36度
    const maxRotationTurnsInRadians = pi / 5.0;
    const minScale = 0.6;
    const maxScale = 1.0;

    /// 旋转时的深度感知
    const depthFactor = 0.01;

    /// 偏移量
    final medianOffset = item.constraints.viewportMainAxisExtent / 2;
    final animationProgress =
        1 - item.offset.dy.clamp(0, double.infinity) / medianOffset;
    final scale = minScale + (maxScale - minScale) * animationProgress.abs();

    /// 对齐方式
    final translationOffset = FractionalOffset.center.alongSize(item.size);
    final rotationMatrix = Matrix4.identity()
      ..setEntry(3, 2, depthFactor)
      ..rotateX(maxRotationTurnsInRadians * animationProgress)
      ..scale(scale);

    final result = Matrix4.identity()
      ..translate(translationOffset.dx, translationOffset.dy)
      ..multiply(rotationMatrix)
      ..translate(-translationOffset.dx, -translationOffset.dy);

    return result;
  }
}

Additional Information

你可以通过这篇博客了解更多关于Flutter中矩阵变换的知识。此外,欢迎提交反馈和Pull Request来帮助改进这个插件。该插件由TBR Group开发。


更多关于Flutter可变形列表视图插件transformable_list_view的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter可变形列表视图插件transformable_list_view的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter中使用transformable_list_view插件的示例代码。这个插件允许你创建一个可变形(例如,可以缩放和旋转)的列表视图。首先,你需要确保已经在pubspec.yaml文件中添加了该插件的依赖:

dependencies:
  flutter:
    sdk: flutter
  transformable_list_view: ^最新版本号  # 请替换为实际最新版本号

然后运行flutter pub get来安装依赖。

下面是一个完整的示例代码,展示了如何使用transformable_list_view

import 'package:flutter/material.dart';
import 'package:transformable_list_view/transformable_list_view.dart';

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

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

class TransformableListViewDemo extends StatefulWidget {
  @override
  _TransformableListViewDemoState createState() => _TransformableListViewDemoState();
}

class _TransformableListViewDemoState extends State<TransformableListViewDemo> with SingleTickerProviderStateMixin {
  final List<String> items = List.generate(20, (index) => "Item ${index + 1}");
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(milliseconds: 300),
      vsync: this,
    )..repeat(reverse: true);
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Transformable ListView Demo'),
      ),
      body: TransformableListView.builder(
        itemCount: items.length,
        itemBuilder: (context, index) {
          return TransformableItem(
            key: ValueKey(index),  // 使用ValueKey以确保列表项的唯一性
            child: ListTile(
              title: Text(items[index]),
            ),
            onTransformStart: () {
              print('Item $index transform start');
            },
            onTransformEnd: (details) {
              print('Item $index transform end with details: $details');
            },
          );
        },
        onTransformStart: () {
          print('List view transform start');
        },
        onTransformEnd: (details) {
          print('List view transform end with details: $details');
        },
      ),
    );
  }
}

解释

  1. 依赖添加:在pubspec.yaml文件中添加transformable_list_view依赖。

  2. 示例应用

    • MyApp:主应用类,定义了应用的标题和主题。
    • TransformableListViewDemo:包含列表的页面。
    • _TransformableListViewDemoState:管理列表的状态和动画控制器。
  3. 列表构建

    • 使用TransformableListView.builder来构建列表,其中itemBuilder参数定义了每个列表项的构建方式。
    • 每个列表项使用TransformableItem包裹,并设置唯一的key
  4. 动画和事件处理

    • onTransformStartonTransformEnd回调分别处理列表或列表项开始和结束变换时的逻辑。

这个示例展示了如何在Flutter中使用transformable_list_view插件来创建一个可变形列表视图,并处理相关的变换事件。你可以根据需要进一步自定义和扩展这个示例。

回到顶部