前言
gsy_flutter_demo
是一个关于各种小案例和小问题的方案解决。项目是由flutter大佬恋猫de小郭维护的
项目地址:https://github.com/CarGuo/gsy_flutter_demo
感兴趣的可以看一下大佬的文章:Flutter完整开发实战详解系列,GSY Flutter 系列专栏整合
关于该项目的学习呢,不会都学习,只会学习自己比较感兴趣的东西。
布局切换动画
动画效果
原文地址
Flutter 小技巧之有趣的动画技巧
代码
class YcHomeBody extends StatefulWidget {
const YcHomeBody({Key? key, required this.width, required this.height})
: super(key: key);
// 容器的宽高
final double width;
final double height;
State<YcHomeBody> createState() => _YcHomeBodyState();
}
class _YcHomeBodyState extends State<YcHomeBody>
with SingleTickerProviderStateMixin {
int currentIndex = 0;
initState() {
super.initState();
// 创建吗一个定时器
Timer.periodic(const Duration(seconds: 2), (timer) {
setState(() {
currentIndex += 1;
if (currentIndex == 100) {
currentIndex = 0;
}
// print("当前值:$currentIndex");
});
});
}
PositionedItemData getIndexPosition(int index, Size size) {
switch (index) {
case 0:
return PositionedItemData(
width: size.width / 2 - 5,
height: size.height,
left: 0,
top: 0,
);
case 1:
return PositionedItemData(
width: size.width / 2 - 5,
height: size.height / 2 - 5,
left: size.width / 2 + 5,
top: 0,
);
case 2:
return PositionedItemData(
width: size.width / 2 - 5,
height: size.height,
left: size.width / 2 + 5,
top: size.height / 2 + 5,
);
}
return PositionedItemData(
width: size.width / 2 - 5,
height: size.height,
left: 0,
top: 0,
);
}
Widget build(BuildContext context) {
return Center(
child: Container(
height: widget.height,
width: widget.width,
margin: const EdgeInsets.symmetric(horizontal: 20),
// 根据父widget的约束条件来构建自身的布局,适合在需要根据父widget的约束条件来动态调整布局的场景中使用
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
var f = getIndexPosition(currentIndex % 3, constraints.biggest);
var s = getIndexPosition((currentIndex + 1) % 3, constraints.biggest);
var t = getIndexPosition((currentIndex + 2) % 3, constraints.biggest);
return Stack(
fit: StackFit.expand,
children: [
// InkWell为其子widget提供水波纹效果和触摸事件处理
PositionItem(f,
child: InkWell(
onTap: () {
// print("red");
},
child: Container(color: Colors.redAccent),
)),
PositionItem(s,
child: InkWell(
onTap: () {
//print("green");
},
child: Container(color: Colors.greenAccent),
)),
PositionItem(t,
child: InkWell(
onTap: () {
// print("yello");
},
child: Container(color: Colors.yellowAccent),
)),
],
);
},
),
));
}
}
class PositionItem extends StatelessWidget {
final PositionedItemData data;
final Widget child;
const PositionItem(this.data, {Key? key, required this.child})
: super(key: key);
Widget build(BuildContext context) {
// AnimatedPositioned 用于动画定位位置的widget
return AnimatedPositioned(
duration: const Duration(seconds: 1), // 动画持续时间
curve: Curves.fastOutSlowIn, // 动画曲线
left: data.left,
top: data.top,
// AnimatedContainer,根据指定的时间段自动过渡其属性,用于实现大小的过渡
child: AnimatedContainer(
duration: const Duration(seconds: 1),
curve: Curves.fastOutSlowIn,
width: data.width,
height: data.height,
child: child,
),
);
}
}
// 用于规范元素的宽高和位置
class PositionedItemData {
final double left;
final double top;
final double width;
final double height;
PositionedItemData({
required this.left,
required this.top,
required this.width,
required this.height,
});
}
使用
YcHomeBody(width: 240, height: 300,)
列表滑动监听
class _MyHomePageState extends State<MyHomePage> {
// 是否到底
bool isEnd = false;
// 偏移量
int offset = 0;
// 通知
String notify = '';
// 列表container
final ScrollController _controller = ScrollController();
void initState() {
super.initState();
_controller.addListener(() {
setState(() {
// 这里进行取整,不然小数位数太多
offset = _controller.offset.floor();
// 判断当前滚动位置的像素值是否等于可滚动区域最大值
isEnd =
_controller.position.pixels == _controller.position.maxScrollExtent;
});
});
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: NotificationListener(
onNotification: (dynamic notification) {
// 在这里处理滚动通知
if (notification is ScrollStartNotification) {
// 滚动开始
notify = "滚动开始";
} else if (notification is ScrollUpdateNotification) {
// 滚动更新
notify = "滚动更新";
} else if (notification is ScrollEndNotification) {
// 滚动结束
notify = "滚动结束";
}
setState(() {});
// 返回true表示阻止通知冒泡,返回false则继续向上传递通知
return false;
},
child: ListView.builder(
itemCount: 100,
controller: _controller,
itemBuilder: (context, index) {
return Card(
child: Container(
height: 60,
alignment: Alignment.centerLeft,
child: Text("Item $index"),
));
})),
// 页脚按钮
persistentFooterButtons: [
Align(
alignment: Alignment.center,
child: Text("通知:$notify,偏移量:$offset,到达底部:${isEnd ? '是' : '否'}"),
)
], // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
NotificationListener
是一个widget,用于监听并处理各种通知。它可以将通知分发给子widget,并根据需要执行相应的操作
列表滑动到指定位置
作者提供了两种实现方式,一种是使用第三库,另一种是需要自己进行计算。本着能使用第三方库就使用第三库的原则,我们使用第三库来实现。
官方地址
https://pub-web.flutter-io.cn/packages/scroll_to_index
安装
flutter pub add scroll_to_index
class _MyHomePageState extends State<MyHomePage> {
// 列表container
late final AutoScrollController _controller;
// 列表项高度,100~300间的整数
List<int> items =
List.generate(50, (index) => 100 + math.Random().nextInt(300 - 100));
void initState() {
super.initState();
// 初始化
_controller = AutoScrollController(
viewportBoundaryGetter: () => Rect.fromLTRB(0, 0, 0,
MediaQuery.of(context).padding.bottom), // 获取底部边界值,保证滚动到底部时,内容不会被遮挡
axis: Axis.vertical);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: ListView.builder(
itemCount: 50,
controller: _controller,
itemBuilder: (context, index) {
return AutoScrollTag(
key: ValueKey(index),
controller: _controller,
index: index,
highlightColor: Colors.black.withOpacity(0.1),
child: Container(
height: items[index] + 0.0,
alignment: Alignment.topCenter,
margin: const EdgeInsets.all(10),
decoration: BoxDecoration(
border: Border.all(color: Colors.blue, width: 1),
borderRadius: BorderRadius.circular(10)),
child: Text("items ${index + 1},height:${items[index]}"),
));
}),
// 页脚按钮
persistentFooterButtons: [
ElevatedButton(
onPressed: () async {
// 滚动
await _controller.scrollToIndex(0,
preferPosition: AutoScrollPosition.begin); // 以列表的上边框为准
// 高亮
_controller.highlight(0);
},
child: const Text("第1个")),
ElevatedButton(
onPressed: () async {
// 滚动
await _controller.scrollToIndex(19,
preferPosition: AutoScrollPosition.begin);
// 高亮
_controller.highlight(19);
},
child: const Text("第20个"))
], // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
高斯模糊
Stack(
children: [
Positioned(
child: Image.network(
'https://scpic2.chinaz.net/files/default/imgs/2023-07-24/f81ebfa03646059a_s.jpg',
fit: BoxFit.fill,
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
),
),
// 高斯模糊
Center(
child: SizedBox(
width: 300,
height: 300,
child: ClipRRect(
borderRadius: BorderRadius.circular(15.0),
child: BackdropFilter(
// 设置视频和垂直方向的模糊程度
filter: ImageFilter.blur(sigmaX: 8.0,sigmaY: 8.0),
child: const Text("高斯模糊"),
),
),
),
)
],
)