釋出時間: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