美文网首页Flutter
Flutter自定义之悬浮框

Flutter自定义之悬浮框

作者: 一笑轮回吧 | 来源:发表于2021-05-21 15:10 被阅读0次

    目前Flutter越来越火,很多公司已经使用了Flutter来开发,所以作为一名Android开发者来说学习是迫在眉睫,那么今天我们要讲的是”悬浮框“,说到”悬浮框“有人就说了,这不很简单吗?对,确实在原生上来说太多的轮子供我们去使用了,我也写过关于Android悬浮框的轮子,所以说还是比较常用的这个功能。

    对于我来说,我也是刚学习Flutter不久,也是个小白,今天也是抱着一个学习的态度来和大家一起把这个Flutter悬浮框写出来,那么现在我们开始吧!

    效果图:

    float.gif

    四大法则

    1、效果功能分析。
    2、功能拆解。
    3、功能参数。
    4、功能代码实现。

    1、效果功能分析。

    • 支持悬浮窗跟随手指移动位置;
    • 支持随机位置及吸附屏幕两边;
    • 支持某个界面及全局悬浮框
    • 支持自定义悬浮窗child
    • 越界处理

    2、功能拆解

    • 自定义Widget(StatefulWidget)
    • 定位组件的初始位置(越界处理)
    • 手势移动Widget位置(越界处理)
    • 抬手计算左右边距并动画移动边缘位置
    • 全局悬浮框功能

    3、功能参数

    • 自定义child
    • 初始值位置offset
    • 是否移动到边缘
    • 移动边缘动画时间

    4、功能代码实现

      import 'package:flutter/material.dart';
    
    ///悬浮按钮Demo
    class FloatingView extends StatefulWidget {
      FloatingView({
        Key key,
        @required this.child,
        this.offset = Offset.infinite,
        this.backEdge = true,
        this.animTime = 500,
      }) : super(key: key);
    
      Widget child;
      Offset offset;
      bool backEdge;
      int animTime;
    
      @override
      State<StatefulWidget> createState() {
        return FloatingViewState();
      }
    }
    
    class FloatingViewState extends State<FloatingView>
        with SingleTickerProviderStateMixin {
      Offset offset = Offset.infinite;
      GlobalKey floatingKey = GlobalKey();
      AnimationController controller;
      Animation<double> animation;
      double animStartX;
      double animEndX;
      double width, height;
      double screenWidth, screenHeight;
    
      @override
      void initState() {
        super.initState();
        controller = new AnimationController(
          duration: Duration(milliseconds: widget.animTime),
          vsync: this,
        );
        WidgetsBinding.instance.addPostFrameCallback((_) {
          width = floatingKey.currentContext.size.width;
          height = floatingKey.currentContext.size.height;
          final size = MediaQuery.of(context).size;
          screenWidth = size.width;
          screenHeight = size.height;
          offset = overflow(widget.offset);
          setState(() {});
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Positioned(
          left: offset.dx,
          top: offset.dy,
          child: _floatingWindow(),
        );
      }
    
      Widget _floatingWindow() {
        return GestureDetector(
          behavior: HitTestBehavior.deferToChild,
          onPanDown: (details) {},
          onPanUpdate: (DragUpdateDetails details) {
            setState(() {
              offset = offset + details.delta;
              offset = overflow(offset);
            });
          },
          onPanEnd: (details) {
            if (widget.backEdge) {
              double oldX = offset.dx;
              if (offset.dx <= screenWidth / 2 - width / 2) {
                offset = Offset(0, offset.dy);
              } else {
                offset = Offset(screenWidth - width, offset.dy);
              }
              double newX = offset.dx;
              animation = new Tween(begin: oldX, end: newX).animate(controller)
                ..addListener(() {
                  setState(() {
                    offset = Offset(animation.value, offset.dy);
                  });
                });
              controller.reset();
              controller.forward();
            }
          },
          child: Material(
            color: Colors.transparent,
            key: floatingKey,
            child: widget.child,
          ),
        );
      }
    
      Offset overflow(Offset offset) {
        if (offset.dx <= 0) {
          offset = Offset(0, offset.dy);
        }
        if (offset.dx >= screenWidth - width) {
          offset = Offset(screenWidth - width, offset.dy);
        }
        if (offset.dy <= 0) {
          offset = Offset(offset.dx, 0);
        }
        if (offset.dy >= screenHeight - height) {
          offset = Offset(offset.dx, screenHeight - height);
        }
        return offset;
      }
    
      @override
      void dispose() {
        controller?.dispose();
        super.dispose();
      }
    }
    

    使用

    import 'package:flutter/material.dart';
    import 'package:flutter_float/flutter_float.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: MyHomePage(title: 'Flutter Demo Home Page'),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key key, this.title}) : super(key: key);
    
      final String title;
    
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      int _counter = 0;
    
      void _incrementCounter() {
        setState(() {
          _counter++;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Stack(
          children: [
            Scaffold(
              appBar: AppBar(
                title: Text(widget.title),
              ),
              body: Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    Text(
                      'You have pushed the button this many times:',
                    ),
                    Text(
                      '$_counter',
                      style: Theme.of(context).textTheme.display1,
                    ),
                  ],
                ),
              ),
              floatingActionButton: FloatingActionButton(
                onPressed: _incrementCounter,
                tooltip: 'Increment',
                child: Icon(Icons.add),
              ), // This trailing comma makes auto-formatting nicer for build methods.
            ),
    
            FloatingView(
              backEdge: true,
              offset: Offset(
                MediaQuery.of(context).size.width,
                MediaQuery.of(context).size.height * 3 / 4,
              ),
              //将要执行动画的子view
              child: CircleAvatar(
                backgroundColor: Colors.transparent,
                radius: 40,
                backgroundImage: NetworkImage(
                    "https://img1.baidu.com/it/u=2196634016,1990933817&fm=26&fmt=auto&gp=0.jpg"),
              ),
            ),
    
    
            FloatingView(
              backEdge: false,
              offset: Offset(0, 200),
              child: Container(
                padding: EdgeInsets.all(10),
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(20),
                  color: Colors.deepOrange,
                ),
                child: Text("我是可以移动的哦"),
              ),
            ),
          ],
        );
      }
    }
    
    
    小知识点

    github 链接:https://github.com/yixiaolunhui/flutter_float

    相关文章

      网友评论

        本文标题:Flutter自定义之悬浮框

        本文链接:https://www.haomeiwen.com/subject/entbrltx.html