[Flutter翻譯]使用Flutter WEB實現桌面GUI(第2部分:Dock)

Sunbreak發表於2021-05-26

原文地址:medium.com/nerd-for-te…

原文作者:achraf-feydi.medium.com/

釋出時間:2021年5月24日 - 8分鐘閱讀

如果你已經讀完了之前的文章《使用Flutter WEB實現桌面GUI(第一部分:介紹)》,我正試圖說明我在使用Flutter WEB實現FlutterGUI時所遇到的一些主要困難。

image.png


在這一部分,我們將嘗試實現一個類似於我在FlutterGUI專案上實現的Dock。

1.gif

我將專注於解釋它背後的機制,並通過一些例子來說明它。我將為你保留它,以建立一個具有花哨動畫的美麗的。

這些程式碼與我的FlutterGUI repo中的程式碼不一樣。這是個更乾淨、更簡單的版本。

dock的實現將在Flutter WEB和桌面上執行。

在本文結束時,您將能夠構建所有這些Dock和更多。

2.gif

3.gif

4.gif

5.gif

內容表。

  1. 設定一個基本的Dock
  2. 檢測滑鼠懸停並將其轉化為有價值的資料。
  3. 重新整理你的數學
  4. 實現動畫
  5. 上下文選單。右鍵點選!
  6. 更多例子

那麼,讓我們來跳動它! ?

設定一個基本的 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()),),
        ),
      ),
    ),
  );
  
}
複製程式碼

image.png

檢測滑鼠懸停並將其轉化為有價值的資料

我們將首先宣告兩個變數。

  • _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()的小數部分。

image.png

重新整理你的數學

假設我們的滑鼠正懸停在專案3上。

讓我們假設我們希望我們的通用動畫取值在0到1之間。所以我們希望專案3的動畫為1,他的鄰居2和4的動畫為0.5,其他的鄰居不會被動畫化。

這種行為可以很容易地用圓的方程來表示。

image.png

一箇中心為(x0,y0)、半徑為r的圓的方程是 。

1.gif

我們希望我們的圓以滑鼠在x軸上的位置為中心,我們希望它在y軸上以0為中心。

因此,x0將採用_getOffset()的值,y0將等於0。

image.png

我們可以很容易地從這個方程中推匯出y的值。

2.gif

上面的方程描述了一箇中心為(x0,0)、半徑為r的圓的上半部分。

在這種情況下,y會根據x(Dock Item的位置)和x0(滑鼠的位置)在0到r之間取值。

由於我們需要在0和1之間的值來製作動畫,我們可以用y除以r。

3.gif

或者我們可以簡單地做。

4.gif

現在回到編碼。

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()),),
        ),
      ),
    ),
  );
複製程式碼

我們到了。

1.gif

上下文選單。右鍵點選!

如果您使用Flutter Web,請在主方法中新增這行程式碼,以禁用瀏覽器中預設的右鍵點選。

import 'dart:html';

void main() {
  window.document.onContextMenu.listen((evt) => evt.preventDefault());
  // ...
}
複製程式碼

stackoverflow.com/questions/6…

現在,我們將在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(() {});
  }
}
複製程式碼

結果:

2.gif

更多例子

旋轉動畫

3.gif

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()),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    ),
  );
}
複製程式碼

翻轉動畫

4.gif

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()),
                ),
              ),
            ),
          ],
        ),
      ),
    )),
  );
}
複製程式碼

彩色動畫

5.gif

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)),
            ),
          ),
        ),
      ),
    ),
  );
}
複製程式碼

github.com/achreffaidi…

下一步是什麼?

在這一系列的文章中,我將解釋我是如何在這個專案中實現一些複雜的小部件的。我希望它能幫助其他開發者做出很酷、很有用的專案。

你可以在下面找到所有的連結。一旦文章準備好了,我就會更新它們。


www.deepl.com 翻譯

相關文章