Flutter撸一个加载动画

Flutter正式版出了,做为一个Android开发,是时候跟随大部队进坑了。在写一个登录页面的时候登录是写完了,突然发现不知道怎么搞一个加载中的动画效果,毕竟Android中有ProgressDialog可用,然而Flutter中不知道用啥,那就自己写一个出来。

项目地址

目标

先上效果图:


目标.gif

是不是感觉跟ProgressDialog创建出来的一毛一样!!!

实现思路

使用对话框

首先想到的是用Flutter自带的SimpleDialog对话框,但是想到这玩意貌似要主动点击按钮关闭,这种方案不符合自己的要求。

根据情况返回不同布局

在加载的时候返回加载的布局,不加载的时候返回登陆页面布局,代码如下:

import 'package:flutter/material.dart';
import 'package:flutter_loading/Toast.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: '加载动画'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  bool _loading = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: _childLayout(),
    );
  }

  Widget _childLayout() {
    if (_loading) {
      return Center(
        child: Container(
          child: CircularProgressIndicator(),
        ),
      );
    } else {
      return Center(
        child: RaisedButton(
          onPressed: () => _onRefresh(),
          child: Text('显示加载动画'),
        ),
      );
    }
  }

  Future<Null> _onRefresh() async {
    setState(() {
      _loading = !_loading;
    });
    await Future.delayed(Duration(seconds: 3), () {
      setState(() {
        _loading = !_loading;
        Toast.show(context, "加载完成");
      });
    });
  }
}

重点在_childLayout()方法,当加载中的时候返回环形进度条,加载完成,返回实际显示的布局,代码效果如下:

GIF0.gif

看效果是好像实现,但是这种效果只适合普通数据列表页面的加载,要是登陆页面,你总不能这么搞吧,一点登录,页面布局都跑路了,只有一个圈圈有啥意思。这种方法也不行。

使用Stack层叠布局

在原本布局上面叠加一层半透明背景,显示一个进度条。这个想法好像可以。重点来了开始撸一波
层叠布局至少有两个控件,按照Flutter思想,一切皆控件。我们自定义一个控件叫ProgressDialog,我们这个控件接收两个必传参数:子布局child,是否显示加载进度:loading,这两个参数是必须的,所以自定义控件如下

import 'package:flutter/material.dart';

class ProgressDialog extends StatelessWidget {
  final bool loading;
  final Widget child;

  ProgressDialog({Key key, @required this.loading, @required this.child})
      : assert(child != null),
        assert(loading != null),
        super(key: key);

  @override
  Widget build(BuildContext context) {
    return null;
  }
}

构造函数写好了,那么开始写控件,Stack层叠布局必须返回两个以上的控件,所以先定义一个List<Widget>,用来放层叠的控件组,首先要把child控件加进去,再加一个加载中的动画。上代码:

import 'package:flutter/material.dart';

class ProgressDialog extends StatelessWidget {
  final bool loading;
  final Widget child;

  ProgressDialog({Key key, @required this.loading, @required this.child})
      : assert(child != null),
        assert(loading != null),
        super(key: key);

  @override
  Widget build(BuildContext context) {
    List<Widget> widgetList = [];
    widgetList.add(child);
    //如果正在加载,则显示加载添加加载中布局
    if (loading) {
      widgetList.add(Center(
        child: CircularProgressIndicator(),
      ));
    }
    return Stack(
      children: widgetList,
    );
  }
}

是不是感觉好像很简单的样子,惯例上图:

加载中1

看图效果好像很接近了,起码原先的布局没有被直接替换,但是感觉不美观,好吧加个透明背景效果,这里就用到控件Opacity,专门用来绘制透明效果。 上代码:

import 'package:flutter/material.dart';

class ProgressDialog extends StatelessWidget {
  final bool loading;
  final Widget child;

  ProgressDialog({Key key, @required this.loading, @required this.child})
      : assert(child != null),
        assert(loading != null),
        super(key: key);

  @override
  Widget build(BuildContext context) {
    List<Widget> widgetList = [];
    widgetList.add(child);
    //如果正在加载,则显示加载添加加载中布局
    if (loading) {
      //增加一层黑色背景透明度为0.8
      widgetList.add(
        Opacity(
            opacity: 0.8,
            child: ModalBarrier(
              color: Colors.black87,
            )),
      );
      //环形进度条
      widgetList.add(Center(
        child: CircularProgressIndicator(),
      ));
    }
    return Stack(
      children: widgetList,
    );
  }
}

老规矩,上图:


增加透明度.gif

看着样子是不是差不多,一般进度都可以用了吧,但是如果我要想在进度条下方显示文字怎么办?并且我看那个toast样子蛮好看的,还想要搞成那样的。好吧,那样的话加载进度条和提示内容得是同一层,用个垂直布局显示一个进度一个Text()应该能搞定:

import 'package:flutter/material.dart';

class ProgressDialog extends StatelessWidget {
  final bool loading;
  final Widget child;

  ProgressDialog({Key key, @required this.loading, @required this.child})
      : assert(child != null),
        assert(loading != null),
        super(key: key);

  @override
  Widget build(BuildContext context) {
    List<Widget> widgetList = [];
    widgetList.add(child);
    //如果正在加载,则显示加载添加加载中布局
    if (loading) {
      //增加一层黑色背景透明度为0.8
      widgetList.add(
        Opacity(
            opacity: 0.8,
            child: ModalBarrier(
              color: Colors.black87,
            )),
      );
      //环形进度条
      widgetList.add(Center(
        child: Container(
          padding: const EdgeInsets.all(20.0),
          decoration: BoxDecoration(
              //黑色背景
              color: Colors.black87,
              //圆角边框
              borderRadius: BorderRadius.circular(10.0)),
          child: Column(
            //控件里面内容主轴负轴剧中显示
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            //主轴高度最小
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              CircularProgressIndicator(),
              Text(
                '加载中...',
                style: TextStyle(color: Colors.white),
              )
            ],
          ),
        ),
      ));
    }
    return Stack(
      children: widgetList,
    );
  }
}

用一个垂直布局Column包裹进度条和提示内容,完美解决,已经接近目标了,图来-->

增加提醒内容.gif

目标完成,最后润色:提示字体要让用户自定义,加载动画也得可以自定义,那么最终代码如下:

import 'package:flutter/material.dart';

class ProgressDialog extends StatelessWidget {
  //子布局
  final Widget child;

  //加载中是否显示
  final bool loading;

  //进度提醒内容
  final String msg;

  //加载中动画
  final Widget progress;

  //背景透明度
  final double alpha;

  //字体颜色
  final Color textColor;

  ProgressDialog(
      {Key key,
      @required this.loading,
      this.msg,
      this.progress = const CircularProgressIndicator(),
      this.alpha = 0.6,
      this.textColor = Colors.white,
      @required this.child})
      : assert(child != null),
        assert(loading != null),
        super(key: key);

  @override
  Widget build(BuildContext context) {
    List<Widget> widgetList = [];
    widgetList.add(child);
    if (loading) {
      Widget layoutProgress;
      if (msg == null) {
        layoutProgress = Center(
          child: progress,
        );
      } else {
        layoutProgress = Center(
          child: Container(
            padding: const EdgeInsets.all(20.0),
            decoration: BoxDecoration(
                color: Colors.black87,
                borderRadius: BorderRadius.circular(4.0)),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              mainAxisSize: MainAxisSize.min,
              children: <Widget>[
                progress,
                Container(
                  padding: const EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 0),
                  child: Text(
                    msg,
                    style: TextStyle(color: textColor, fontSize: 16.0),
                  ),
                )
              ],
            ),
          ),
        );
      }
      widgetList.add(Opacity(
        opacity: alpha,
        child: new ModalBarrier(color: Colors.black87),
      ));
      widgetList.add(layoutProgress);
    }
    return Stack(
      children: widgetList,
    );
  }
}

最后附上在工程中调用的例子代码:

import 'package:flutter/material.dart';
import 'package:flutter_loading/Toast.dart';
import 'package:flutter_loading/view_loading.dart';


void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: '加载动画'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  bool _loading = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: ProgressDialog(
        loading: _loading,
        msg: '正在加载...',
        child: Center(
          child: RaisedButton(
            onPressed: () => _onRefresh(),
            child: Text('显示加载动画'),
          ),
        ),
      ),
    );
  }

  Future<Null> _onRefresh() async {
    setState(() {
      _loading = !_loading;
    });
    await Future.delayed(Duration(seconds: 3), () {
      setState(() {
        _loading = !_loading;
        Toast.show(context, "加载完成");
      });
    });
  }
}

对于加载动画,只要把progress属性改为自定义的属性即可,比如这位大佬写的加载动画:
flutter自定义进度动画,我们用他的加载中动画:
只需在上述代码中加一行(当然前提是你得去github上git到他自定义的代码):

loading: _loading,
//自定义动画
progress: MyProgress(size: new Size(100.0, 20.0),color: Colors.white,),
msg: '正在加载...',

效果如下:

自定义动画.gif
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容

  • 内容 抽屉菜单 ListView WebView SwitchButton 按钮 点赞按钮 进度条 TabLayo...
    小狼W阅读 1,614评论 0 10
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,105评论 4 62
  • 曾经有一段时间我很沮丧,焦虑不安,因为我觉得我的工作辛苦挣的不多,我的老公妈宝自私不讲理,我的公婆出尔反尔买...
    人生需要靠自己阅读 309评论 0 1
  • jrj01阅读 130评论 0 0
  • 白马涧边水悠悠, 壮志未酬泪空流。 吴王阖闾今安在? 意气风发持吴钩。 作于16/2/25 改于16/6/23
    四合道人阅读 243评论 1 0