Flutter可变形列表视图插件transformable_list_view的使用
Flutter可变形列表视图插件transformable_list_view的使用
Flutter的transformable_list_view
插件提供了一种创建带有自定义变换动画的滚动小部件的方法。此包中包含3个主要的小部件:TransformableListView
、TransformableSliverList
和TransformableSliver
,它们分别扩展了ListView
、SliverList
和SliverToBoxAdapter
。
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
更多关于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');
},
),
);
}
}
解释
-
依赖添加:在
pubspec.yaml
文件中添加transformable_list_view
依赖。 -
示例应用:
MyApp
:主应用类,定义了应用的标题和主题。TransformableListViewDemo
:包含列表的页面。_TransformableListViewDemoState
:管理列表的状态和动画控制器。
-
列表构建:
- 使用
TransformableListView.builder
来构建列表,其中itemBuilder
参数定义了每个列表项的构建方式。 - 每个列表项使用
TransformableItem
包裹,并设置唯一的key
。
- 使用
-
动画和事件处理:
onTransformStart
和onTransformEnd
回调分别处理列表或列表项开始和结束变换时的逻辑。
这个示例展示了如何在Flutter中使用transformable_list_view
插件来创建一个可变形列表视图,并处理相关的变换事件。你可以根据需要进一步自定义和扩展这个示例。