flutter 实现点击下拉栏微信右上角弹出窗功能

先看效果实现

需求分析

这个是使用 PopupRoute这个路由类进行实现

大概原理就是利用PopupRpute这个类进行改造,然后自定义一个页面,页面内镶嵌一个动画类,用来实现缩放动画

大概分为三部分,PopupRoute改造,弹出页面设置,动画类设置。

为什么选择PopupRoute?

可以镶嵌在flutter本身的路由管理之中

也就是逻辑操作都是正常的页面管理,可以手动管理,也可以用路由返回直接关掉,不会影响原有页面和布局

第一步,改造PopupRoute类

import 'package:flutter/material.dart';

class Popup extends PopupRoute {
  final Duration _duration = Duration(milliseconds: 300);
  Widget child;

  Popup({@required this.child});

  @override
  Color get barrierColor => null;

  @override
  bool get barrierDismissible => true;

  @override
  String get barrierLabel => null;

  @override
  Widget buildPage(BuildContext context, Animation<double> animation,
      Animation<double> secondaryAnimation) {
    return child;
  }

  @override
  Duration get transitionDuration => _duration;
}

第二步,新建一个弹窗页面

页面分两部分

一个是页面的背景,一个是页面的内容

注意,弹窗动画的代码在下方

class Model extends StatefulWidget {
  final double left; //距离左边位置 弹窗的x轴定位
  final double top; //距离上面位置 弹窗的y轴定位
  final bool otherClose; //点击背景关闭页面
  final Widget child; //传入弹窗的样式
  final Function fun; // 把关闭的函数返回给父组件 参考vue的$emit
  final Offset offset; // 弹窗动画的起点

  Model({
    @required this.child,
    this.left = 0,
    this.top = 0,
    this.otherClose = false,
    this.fun,
    this.offset,
  });

  @override
  _ModelState createState() => _ModelState();
}

class _ModelState extends State<Model> {
  AnimationController animateController;

  @override
  Widget build(BuildContext context) {
    return Material(
      color: Colors.transparent,
      child: Stack(
        children: <Widget>[
          Positioned(
            child: GestureDetector(
              child: Container(
                width: MediaQuery.of(context).size.width,
                height: MediaQuery.of(context).size.height,
                color: Colors.transparent,
              ),
              onTap: () async {
                if (widget.otherClose) {
                } else {
                  closeModel();
                }
              },
            ),
          ),
          Positioned(
            /// 这个是弹窗动画 在下方,我把他分离 防止太长
            child: ZoomInOffset(
              duration: Duration(milliseconds: 180),
              offset: widget.offset,
              controller: (controller) {
                animateController = controller;
                widget.fun(closeModel);
              },
              child: widget.child,
            ),
            left: widget.left,
            top: widget.top,
          ),
        ],
      ),
    );
  }

  ///关闭页面动画
  Future closeModel() async {
    await animateController.reverse();
    Navigator.pop(context);
  }
}

动画代码

我是直接复制animate_do:^2.0.0 这个版本的ZoomIn的动画类

这个插件本身就是依赖flutter 自带的动画来完成的,很简洁,使用很方便,不过默认构造的时候没有动画的启动方向,默认是最中心。但是可以添加个参数,我把源码复制出来自己改造了一下。这个类在构造的时候有个controller 参数,类型的函数,带一个AnimationController的参数把控制器通过函数传递出去到Model类,可以在Model类里面进行控制动画开启和关闭后续我在Model类里面把动画关闭和返回退出PopupRoute层封装成一个函数 传递到Model里面的fun参数里面返回出去可以在最外部进行组件通信,进而控制这些子组件

import 'package:flutter/material.dart';

class ZoomInOffset extends StatefulWidget {
  final Key key;
  final Widget child;
  final Duration duration;
  final Duration delay;

  ///把控制器通过函数传递出去,可以在父组件进行控制
  final Function(AnimationController) controller;
  final bool manualTrigger;
  final bool animate;
  final double from;

  ///这是我自己写的 起点
  final Offset offset;

  ZoomInOffset(
      {this.key,
      this.child,
      this.duration = const Duration(milliseconds: 500),
      this.delay = const Duration(milliseconds: 0),
      this.controller,
      this.manualTrigger = false,
      this.animate = true,
      this.offset,
      this.from = 1.0})
      : super(key: key) {
    if (manualTrigger == true && controller == null) {
      throw FlutterError('If you want to use manualTrigger:true, \n\n'
          'Then you must provide the controller property, that is a callback like:\n\n'
          ' ( controller: AnimationController) => yourController = controller \n\n');
    }
  }

  @override
  _ZoomInState createState() => _ZoomInState();
}

/// State class, where the magic happens
class _ZoomInState extends State<ZoomInOffset>
    with SingleTickerProviderStateMixin {
  AnimationController controller;
  bool disposed = false;
  Animation<double> fade;
  Animation<double> opacity;

  @override
  void dispose() async {
    disposed = true;
    controller.dispose();
    super.dispose();
  }

  @override
  void initState() {
    super.initState();

    controller = AnimationController(duration: widget.duration, vsync: this);
    fade = Tween(begin: 0.0, end: widget.from)
        .animate(CurvedAnimation(curve: Curves.easeOut, parent: controller));

    opacity = Tween<double>(begin: 0.0, end: 1)
        .animate(CurvedAnimation(parent: controller, curve: Interval(0, 0.65)));

    if (!widget.manualTrigger && widget.animate) {
      Future.delayed(widget.delay, () {
        if (!disposed) {
          controller?.forward();
        }
      });
    }

    if (widget.controller is Function) {
      widget.controller(controller);
    }
  }

  @override
  Widget build(BuildContext context) {
    if (widget.animate && widget.delay.inMilliseconds == 0) {
      controller?.forward();
    }

    return AnimatedBuilder(
      animation: fade,
      builder: (BuildContext context, Widget child) {
        ///  这个transform有origin的可选构造参数,我们可以手动添加
        return Transform.scale(
          origin: widget.offset,
          scale: fade.value,
          child: Opacity(
            opacity: opacity.value,
            child: widget.child,
          ),
        );
      },
    );
  }
}

最后页面调用

我用stack类进行堆叠组件,堆叠出上面箭头

其实可以抽成一个方向设置不过太麻烦了我没写,毕竟能用就行

import 'package:flutter/material.dart';
import 'package:one/widget/Model.dart';
import 'package:one/widget/Popup.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  ///给获取详细信息的widget设置一个key
  GlobalKey iconkey = GlobalKey();

  ///获取位置,给后续弹窗设置位置
  Offset iconOffset;

  ///获取size 后续计算弹出位置
  Size iconSize;

  ///接受弹窗类构造成功传递来的关闭参数
  Function closeModel;

  @override
  Widget build(BuildContext context) {
    ///等待widget初始化完成
    WidgetsBinding.instance.addPostFrameCallback((duration) {
      ///通过key获取到widget的位置
      RenderBox box = iconkey.currentContext.findRenderObject();

      ///获取widget的高宽
      iconSize = box.size;

      ///获取位置
      iconOffset = box.localToGlobal(Offset.zero);
    });

    return MaterialApp(
      home: Builder(
        builder: (context) => Scaffold(
          appBar: AppBar(
            actions: [
              IconButton(
                key: iconkey,
                icon: Icon(
                  Icons.favorite,
                  color: Colors.red,
                ),
                onPressed: () {
                  showModel(context);
                },
              ),
            ],
          ),
          body: Column(
            children: [],
          ),
        ),
      ),
    );
  }

  ///播放动画
  void showModel(BuildContext context) {
    /// 设置传入弹窗的高宽
    double _width = 130;
    double _height = 230;

    Navigator.push(
      context,
      Popup(
        child: Model(
          left: iconOffset.dx - _width + iconSize.width / 1.2,
          top: iconOffset.dy + iconSize.height / 1.3,
          offset: Offset(_width / 2, -_height / 2),
          child: Container(
            width: _width,
            height: _height,
            child: buildMenu(),
          ),
          fun: (close) {
            closeModel = close;
          },
        ),
      ),
    );
  }

  ///构造传入的widget
  Widget buildMenu() {
    ///构造List
    List _list = [1, 2, 3, 4, 5];

    return Container(
      height: 160,
      width: 230,
      child: Stack(
        children: [
          Positioned(
            right: 4,
            top: 17,
            child: Container(
              width: 20,
              height: 20,
              transform: Matrix4.rotationZ(45 * 3.14 / 180),
              decoration: BoxDecoration(
                color: Color.fromRGBO(46, 53, 61, 1),
                borderRadius: BorderRadius.circular(5),
              ),
            ),
          ),

          ///菜单内容
          Positioned(
            bottom: 0,
            child: Container(
              padding: EdgeInsets.only(
                top: 20,
                bottom: 20,
                left: 10,
                right: 10,
              ),
              width: 130,
              height: 200,
              decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(10),
                color: Color.fromRGBO(46, 53, 61, 1),
              ),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: _list
                    .map<Widget>((e) => InkWell(
                          child: Container(
                            width: double.infinity,
                            alignment: Alignment.center,
                            child: Text(
                              '这应该是选项${e.toString()}',
                              style: TextStyle(
                                color: Colors.white70,
                                fontSize: 14,
                              ),
                            ),
                          ),
                          onTap: () async {
                            print('这是点击了选项${e.toString()}');
                            await Future.delayed(Duration(milliseconds: 500))
                                .then((value) => print('开始'));
                            await closeModel();
                            print('结束');
                          },
                        ))
                    .toList(),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

然后就能实现我们的弹窗动画了,如果想要其他效果的动画,可以手动替换动画类,或者自己手写个新的最后我自己的项目修饰效果,还有demo的代码代码 仓库地址:https://github.com/mannaoz/one

到此这篇关于flutter 实现点击下拉栏微信右上角弹出窗功能的文章就介绍到这了,更多相关flutter弹出窗内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

时间: 2021-05-19

flutter仿微信底部图标渐变功能的实现代码

先给大家展示下效果图,感觉不错请参考实例代码. 实现思路 在flutter中,如果想实现上面的页面切换效果,必然会想到pageView.pageView的controller可以监听到pageView的滚动事件,也可以获取pageView滚动的位置,所以我们在滚动事件中根据位置去改变对应的图标颜色就可以实现了. 改变图标颜色 图标是从微信中提取出来的,都是webp格式的图片.要改变图片颜色可以使用ImageIcon这个组件. ImageIcon会把一张图片变成单色图片,所以只要图片没有多色的要求

Flutter实现仿微信底部菜单栏功能

import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget{ @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: MyHomePage(), ), ); } } class MyHomePage extends Sta

Android仿微信底部菜单栏功能显示未读消息数量

底部菜单栏很重要,我看了一下很多应用软件都是用了底部菜单栏,这里使用了tabhost做了一种通用的(就是可以像微信那样显示未读消息数量的,虽然之前也做过但是layout下的xml写的太臃肿,这里去掉了很多不必要的层,个人看起来还是不错的,所以贴出来方便以后使用). 先看一下做出来之后的效果: 以后使用的时候就可以换成自己项目的图片和字体了,主框架不用变哈哈, 首先是要布局layout下xml文件 main.xml: <?xml version="1.0" encoding=&qu

Android仿微信底部菜单栏效果

前言 在市面上,大多数的APP都需要通过底部菜单栏来将程序的功能进行分类整理,通常都是分为3-5个大模块,从而正确有效地引导用户去使用我们的APP.实现底部菜单栏的方法也有很多种. 1.仿微信底部菜单栏(ViewPager+ImagerView+TextView) ......(其他方式后续会补充) 效果预览 首先来个开胃菜,看看实现效果: 先贴出项目所需的资源文件,这些可随个人自由更改颜色和文字 colors.xml <color name="bg_line_light_gray&quo

Android仿微信底部按钮滑动变色

Android仿微信底部按钮滑动变色,这里只针对使用Fragment为Tab页的滑动操作,进行简单的变色讲解. 首先说下OnPageChangeListener这个监听 //这个监听有三个方法 public abstract void onPageScrollStateChanged (int state) public abstract void onPageScrolled (int position, float positionOffset, int positionOffsetPixe

Android 仿微信底部渐变Tab效果

先来看一下效果图 除了第三个的发现Tab有所差别外,其他的基本还原了微信的底部Tab渐变效果 每个Tab都是一个自定义View,根据ImageView的tint属性来实现颜色渐变效果,tint属性的使用可以看我的上一篇文章 我将自定义View命名为ShadeView,包含四个自定义属性 意思分别为图标.背景色.底部文本.底部文本大小 <declare-styleable name="ShadeView"> <attr name="icon" for

iOS高仿微信表情输入功能代码分享

最近项目需求,要实现一个类似微信的的表情输入,于是把微信的表情扒拉出来,实现了一把.可以从这里下载源码.看起来表情输入没有多少东西,不外乎就是用NSTextAttachment来实现图文混排,结果在实现的过程中遇到了很多小问题,接下来会一一介绍遇到过的坑.先上一张效果图: 一.实现表情选择View(WKExpressionView) 具体的实现就不细说了,主要功能就是点击表情时,将对应表情的图片名称通知给delegate. 二.实现表情textView(WKExpressionTextView)

Android仿微信语音聊天功能

本文实例讲述了Android仿微信语音聊天功能代码.分享给大家供大家参考.具体如下: 项目效果如下: 具体代码如下: AudioManager.java package com.xuliugen.weichat; import java.io.File; import java.io.IOException; import java.util.UUID; import android.media.MediaRecorder; public class AudioManager { private

Android开发之微信底部菜单栏实现的几种方法汇总

 实现方式 实现的方式有很多种 这里总结最常见的几种方式,以后再添加其他的. viewPager + RadioGroup viewPager + FragmentTabHost viewpager +TabLayout viewPager+RadioGroup 感觉这是最简单的一个了,我也就不贴代码 说说我理解的思路吧 通过给pager 和RadioGroup 添加监听,监听两个控件的变化 实现联动 当viewPager的显示pager改变就会触发监听 ,在监听中选中对应的RadioButto

android 中viewpager+fragment仿微信底部TAG完美渐变

viewpager+fragment仿微信底部TAG完美渐变,在图片渐变的同时字的颜色也在变,注意,是渐变哦! 效果图: activity_main.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:zhy="http://schemas.android.com/apk/res/com.Sing.weixin" xmlns:tools="

利用百度地图Android sdk高仿微信发送位置功能及遇到的问题

接触了百度地图开发平台半个月了,这2天试着模仿了微信给好友发送位置功能,对百度地图的操作能力又上了一个台阶 我在实现这个功能的时候,遇到一些困难,可能也是别人将会遇到的困难,特在此列出 1.在微信发送功能中,不管用户如何拖拽地图,总有个覆盖物固定了MapView中央,怎么实现? 其实这很容易实现,只要MapView的布局文件中,将一个ImageView覆盖在MapView的中央,就能够实现不管用户如何拖拽地图,覆盖物(ImageView)总固定总MapView中央 2.如何获取MapView中央