发布时间:2021年5月24日 – 8分钟阅读
如果你已经读完了之前的文章《使用Flutter WEB实现桌面GUI(第一部分:介绍)》,我正试图说明我在使用Flutter WEB实现FlutterGUI时所遇到的一些主要困难。
在这一部分,我们将尝试实现一个类似于我在FlutterGUI项目上实现的Dock。
我将专注于解释它背后的机制,并通过一些例子来说明它。我将为你保留它,以建立一个具有花哨动画的美丽的。
这些代码与我的FlutterGUI repo中的代码不一样。这是个更干净、更简单的版本。
dock的实现将在Flutter WEB和桌面上运行。
在本文结束时,您将能够构建所有这些Dock和更多。
内容表。
- 设置一个基本的Dock
- 检测鼠标悬停并将其转化为有价值的数据。
- 刷新你的数学
- 实现动画
- 上下文菜单。右键点击!
- 更多例子
那么,让我们来跳动它! ?
设置一个基本的 Dock
在下面的代码中,我只是使用堆栈和定位部件将 Dock 定位到屏幕的底部。
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: _getBody(),
);
}
Widget _getBody() {
return Stack(
children: [
Positioned(
bottom: 40,
child: _getDock())
],
);
}
Widget _getDock() {
// Our dock will go here
}
// Default DockItem size (width & hight)
final _dockItemDefaultSize = 40.0;
// Dock item size + Padding.
final _dockItemDefaultSizeWithSpacing = 100.0;
// List of items I'll be showing in the Dock.
List<int> items = [for(var i=0; i<6; i+=1) i];
复制代码
现在我们将实现Dock本身。
这段代码非常简单,不言自明。
注意:黑体字的参数是最重要的。
Widget _getBody() {
return Stack(
children: [
Positioned(
bottom: 40,
right: 0,
left: 0,
child: _getDock())
],
);
}
Widget _getDock() {
return Center(
child: Container(
color: Colors.black38,
width: _dockItemDefaultSizeWithSpacing*values.length,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: _getChildren(),
),
),
);
}
List<Widget> _getChildren() {
List<Widget> items = [];
for(var i = 0; i< values.length;i++){
items.add(_generateItem(i));
}
return items;
}
Widget _generateItem(int index) {
return Container(
width: _dockItemDefaultSizeWithSpacing,
child: Center(
child: Card(
child: Container(
height: _dockItemDefaultSize,
width: _dockItemDefaultSize,
child: Center(child: Text(values[index].toString()),),
),
),
),
);
}
复制代码
检测鼠标悬停并将其转化为有价值的数据
我们将首先声明两个变量。
_offset
:将保持鼠标在Dock上的位置。_currentIndex
:将保持被悬停的Dock-Item。
(这些变量的使用将在后面说明)
为了检测鼠标的位置,我们可以在MouseRegion小组件中翘起我们的Dock。
var _offset = 0.0;
var _currentIndex = -1;
Widget _getDock() {
return Center(
child: MouseRegion(
onHover: (event){
setState(() {
_offset = event.localPosition.dx;
_currentIndex = (_getOffset()).toInt();
});
},
onExit: (event){
setState(() {
_offset = 0 ;
_currentIndex = -1;
});
},
child: Container(
color: Colors.black38,
width: _dockItemDefaultSizeWithSpacing*values.length,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: _getChildren(),
),
),
),
);
}
double _getOffset(){
return _offset/_dockItemDefaultSize;
}
复制代码
_getOffset()
方法将把_offset
(它需要一个介于0和width_of_Dock
之间的值)转换成一个介于0和6之间的值(项目数)。这只是简单的数学运算!
为了得到_currentIndex
,我们只需去除_getOffset()
的小数部分。
刷新你的数学
假设我们的鼠标正悬停在项目3上。
让我们假设我们希望我们的通用动画取值在0到1之间。所以我们希望项目3的动画为1,他的邻居2和4的动画为0.5,其他的邻居不会被动画化。
这种行为可以很容易地用圆的方程来表示。
一个中心为(x0,y0)、半径为r的圆的方程是 。
我们希望我们的圆以鼠标在x轴上的位置为中心,我们希望它在y轴上以0为中心。
因此,x0将采用_getOffset()
的值,y0将等于0。
我们可以很容易地从这个方程中推导出y的值。
上面的方程描述了一个中心为(x0,0)、半径为r的圆的上半部分。
在这种情况下,y会根据x(Dock Item的位置)和x0(鼠标的位置)在0到r之间取值。
由于我们需要在0和1之间的值来制作动画,我们可以用y除以r。
或者我们可以简单地做。
现在回到编码。
double _getVariation(int x, double x0,double radius){
if(_offset==0) return 0 ;
var z = radius - (x - x0)*(x - x0);
if(z<0) return 0 ;
return sqrt(z/radius);
}
复制代码
实现动画
现在让我们做一个简单的动画来测试我们的代码。
我们将使用我们用_getVariation方程计算出的动画值,在每个项目的底部增加Margin。
我们取为
- x : index + 0.5 = Dock项目的中心。
- x0 :
_getOffset
= 游标的位置。 - r : 等于3 = 我们希望动画影响以x0为中心的3个项目。
Widget _generateItem(int index) {
double dx = _getVariation(index + 0.5 ,_getOffset(),3);
return Container(
width: _dockItemDefaultSizeWithSpacing,
margin: EdgeInsets.only(bottom: dx * 20 ),
child: Center(
child: Card(
color: index==_currentIndex? Colors.blue:Colors.white,
child: Container(
height: _dockItemDefaultSize,
width: _dockItemDefaultSize,
child: Center(child: Text(values[index].toString()),),
),
),
),
);
复制代码
我们到了。
上下文菜单。右键点击!
如果您使用Flutter Web,请在主方法中添加这行代码,以禁用浏览器中默认的右键点击。
import 'dart:html';
void main() {
window.document.onContextMenu.listen((evt) => evt.preventDefault());
// ...
}
复制代码
现在,我们将在Listener widget中翘起我们生成的项目以检测右键事件。
List<Widget> _getChildren() {
List<Widget> items = [];
for(var i = 0; i< values.length;i++){
items.add(Listener(
onPointerDown: (event){
_onPointerDown(event,_currentIndex);
},
child: _generateItem(i)));
}
return items;
}
Future<void> _onPointerDown(PointerDownEvent event,int currentIndex) async {
List<PopupMenuEntry<int>> menuItems;
menuItems = [
PopupMenuItem(child: Text('apply +1'), value: 1),
PopupMenuItem(child: Text('apply -1'), value: 2),
PopupMenuItem(child: Text('set to 0'), value: 3),
];
if (event.kind == PointerDeviceKind.mouse &&
event.buttons == kSecondaryMouseButton) {
final overlay =
Overlay.of(context).context.findRenderObject() as RenderBox;
final menuItem = await showMenu<int>(
context: context,
items: menuItems,
position: RelativeRect.fromSize(
event.position & Size(48.0, 48.0), overlay.size));
switch (menuItem) {
case 0:
// open;
break;
case 1:
// add 1;
values[currentIndex] = values[currentIndex] +1 ;
break;
case 2:
// minus 1 ;
values[currentIndex] = values[currentIndex] - 1 ;
break;
case 3:
// set to 0;
values[currentIndex] = 0 ;
break;
default:
}
setState(() {});
}
}
复制代码
结果:
更多例子
旋转动画
Widget _generateItemWithRotate(int index) {
double dx = _getVariation(index + 0.5, _getOffset(), 1);
print(dx);
return Container(
width: _dockItemDefaultSizeWithSpacing,
child: Center(
child: Transform.rotate(
angle: dx * pi / 2,
child: Card(
color: index == _currentIndex ? Colors.blue : Colors.white,
child: Stack(
children: [
Positioned.fill(
child: Transform.rotate(
angle: -pi / 2, child: Center(child: Text("Click!"))),
),
Opacity(
opacity: 1 - dx,
child: Container(
color: Colors.white,
height: _dockItemDefaultSize,
width: _dockItemDefaultSize,
child: Center(
child: Text(values[index].toString()),
),
),
),
],
),
),
),
),
);
}
复制代码
翻转动画
Widget _generateItemWithFlip(int index) {
double dx = _getVariation(index + 0.5, _getOffset(), 1);
print(dx);
return Container(
width: _dockItemDefaultSizeWithSpacing,
child: Center(
child: Transform(
alignment: FractionalOffset.center,
transform: Matrix4.identity()
..setEntry(3, 2, 0.002)
..rotateX(pi * dx),
child: Card(
color: index == _currentIndex ? Colors.blue : Colors.white,
child: Stack(
children: [
Positioned.fill(
child: Transform.rotate(
angle: pi, child: Center(child: Text("?"))),
),
Opacity(
opacity: 1 - dx,
child: Container(
color: Colors.white,
height: _dockItemDefaultSize,
width: _dockItemDefaultSize,
child: Center(
child: Text(values[index].toString()),
),
),
),
],
),
),
)),
);
}
复制代码
彩色动画
Widget _generateItemColored(int index) {
double dx =
_getVariation(index + 0.5, _getOffset(), values.length.toDouble());
return Container(
width: _dockItemDefaultSizeWithSpacing,
child: Center(
child: Card(
color: Color.lerp(Colors.deepPurple, Colors.blue, dx),
child: Container(
height: _dockItemDefaultSize,
width: _dockItemDefaultSize,
child: Center(
child: Text(
values[index].toString(),
style: TextStyle(
color: Color.lerp(Colors.deepPurple, Colors.white, dx)),
),
),
),
),
),
);
}
复制代码
下一步是什么?
在这一系列的文章中,我将解释我是如何在这个项目中实现一些复杂的小部件的。我希望它能帮助其他开发者做出很酷、很有用的项目。
你可以在下面找到所有的链接。一旦文章准备好了,我就会更新它们。
- 使用 Flutter WEB 实现桌面 GUI(第一部分:介绍)
- 使用Flutter WEB实现桌面GUI(第2部分:Dock)
- 使用 Flutter WEB 实现桌面 GUI(第 3 部分:可拖动和可调整的窗口)
- 使用Flutter WEB实现桌面GUI(第4部分:Windows XP崩溃)
- 使用Flutter WEB实现桌面图形用户界面 (第5部分:全屏动画)
- 使用Flutter WEB实现桌面GUI(第6部分:Github页面和自定义URL)
- 使用 Flutter WEB 实现桌面 GUI(第 2 部分:Dock