[Flutter翻译]使用Flutter WEB实现桌面GUI(第2部分:Dock)

原文地址: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 翻译

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享