一个高颜值Flutter版WanAndroid客户端

5,387 阅读6分钟

1. 前言

项目地址: https://github.com/xfhy/WanAndroid-Flutter

前段时间抽了点业余时间学了点Flutter入门,打算写个简单项目练练手.说实话,只有真正动手写东西才能真正切身感受到Flutter的魅力,刚开始学的时候写布局特别难受,各种嵌套,很烦. 后面多写一点儿之后感觉也还是勉强可以接受,各种Widget操作起来也还是容易.

Flutter目前我认为是最合理的跨平台方案,只需要native提供画布,Flutter直接自己在上面画Widget,就单凭这一点就和RN截然不同.RN还需要JS和Java进行中转,多了一层,肯定会慢很多,而且还不能自己画.

再来说说开发和发布,支持JIT和AOT.平时开发使用JIT,可以快速热加载,及时将代码变动传递给flutter app,这其实可以大大提升开发效率.及时查看UI和逻辑是否正确,不像原生开发还需要重新编译,花费很多时间.然后Flutter在发布(release包)的时候,采用AOT,运行时直接指向Native(arm)代码,高效.

可能有时候Flutter需要和native通信,比如使用相机之类的,这时会使用到channel技术,但是这个是C++层次的,性能好.

所以,了解一下Flutter.

2. 下载试玩

扫描二维码下载

3. 技术点

  • 封装 上拉加载,下拉刷新
  • dio进行网络请求,统一封装get,post
  • 封装banner
  • Future
  • 路由,跳转界面
  • 事件总线 event_bus
  • toast
  • SharedPreference
  • ....

4. 项目截图

image1 image2 image3 image4 image6 image6

5. 遇到的问题

5.1 引入第三方库

  1. 首先去官网package搜索.
  2. 找到相应插件,点进详情,切换到Installing tab,然后在pubspec.yaml中引入该插件.
  3. 在本项目控制台,输入flutter pub get. 即引入三方库完成.

5.2 loading

当页面正在loading时,需要一个Widget来占位,不然Widget为空要报错.

5.3 运行在iOS上

  1. 首先得安装Xcode(7.8G)
  2. 然后安装 cocoapods sudo gem install cocoapods
  3. 然后在ios工程下,执行pod install,引入那些依赖
  4. 然后用AS打开ios项目里面的Info.plist,点击右上角的用Xcode打开.
  5. 编辑Podfile,将顶部的platform :ios, '9.0' 注释放开
  6. 运行到模拟器上.

5.4 如何快速解析json

Flutter不支持运行时反射,所以没有像Gson这样自动解析JSON的库来降低解析成本.在Flutter中解析JSON需要完全手动进行操作,麻烦.

可以在AS上装FlutterJsonBeanFactory这个插件,然后右键New->JsonToDartBeanAction,输入文件名和json数据.即可自动生成bean对象,和它所对应的解析代码.

原理是它生成了一个JsonConvert,然后这里面可以根据运行时type去选择应该解析哪一个类对象. 然后bean类在声明的时候是混入了JsonConvert的,可以直接使用JsonConvert里面的方法,完美.

5.5 Flutter ScrollView (滚动视图)

ScrollView是一个带有滚动的视图组件,它本身由三部分组成

  • Scrollable - 它监听各种用户手势并实现滚动的交互设计。
  • Viewport - 它通过在滚动视图内仅显示一部分小部件来实现滚动的可视化设计。
  • Slider - 它们是可以组合以创建各种滚动效果的小部件,如列表,网格和扩展标题。

Scroll是一个抽象类,通常使用CustomScrollView

CustomScrollView(
    shrinkWrap: true,
    // 内容
    slivers: <Widget>[
        new SliverPadding(
            padding: const EdgeInsets.all(20.0),
            sliver: new SliverList(
                delegate: new SliverChildListDelegate(
                    <Widget>[
                        const Text('A'),
                        const Text('B'),
                        const Text('C'),
                        const Text('D'),
                    ],
                ),
            ),
        ),
    ],
)

5.6 处理Text超出问题

可以放Row或Column中,用Expanded包起来,然后用maxLines控制行数,用overflow: TextOverflow.ellipsis,控制超出部分的展示.

5.7 让一个ListView支持下拉刷新

非常简单, 使用官方自带的RefreshIndicator即可,将listview放child,然后实现一个_pullToRefresh下拉刷新时调用的方法(做下拉刷新的逻辑).

RefreshIndicator(
      child: listView,
      onRefresh: _pullToRefresh,
    );

Future<void> _pullToRefresh() {
    loadData();
    //这里Feature不能返回 null
    return Future(() => LogUtil.d("lalala"));
  }

5.8 获取屏幕宽度,高度

MediaQuery.of(context).size.width,
MediaQuery.of(context).size.height

5.9 封装通用标题栏

标题栏,每个界面都需要,所以封装一个,取需.

///get通用状态栏
static AppBar getCommonAppBar(BuildContext context, String title, {double fontSize, List<Widget> actions}) {
if (title == null) {
  title = "";
}
return AppBar(
  leading: IconButton(
    icon: Icon(
      Icons.arrow_back,
      color: Colors.white,
    ),
    //点击返回
    onPressed: () {
      if (context != null) {
        Navigator.pop(context);
      }
    },
  ),
  title: Text(
    title,
    style: TextStyle(
      color: Colors.white,
      fontSize: fontSize == null ? 18.0 : fontSize,
    ),
  ),
  //标题栏居中
  centerTitle: true,
  //右边的action 按钮
  actions: actions == null ? <Widget>[] : actions,
);
}

5.10 格式化String

dart中格式化String,需要引入三方库sprintf,使用方式如下:

sprintf("lg/collect/%s/json", [15615]);

5.11 获取Android/iOS本地目录

需要引入三方库path_provider,用于查找文件系统上的常用位置,支持Android和iOS.免得去写一原生代码,这个三方库帮我们封装好了.

Directory tempDir = await getTemporaryDirectory();
String tempPath = tempDir.path;

Directory appDocDir = await getApplicationDocumentsDirectory();
String appDocPath = appDocDir.path;

5.12 展示一个Dialog

以下方法是dart的material包下面的方法.

//展示对话框
showDialog(
        context: context,
        barrierDismissible: false,
        builder: (_) {
          return SpinKitFadingCircle(
            color: AppColors.colorPrimary,
          );
        });

//取消对话框
Navigator.of(context).pop();

5.13 间距的简单方式

可以用Padding和margin来实现.其实还有一种方式,可以在Column和Row中快速增加一段间距,利用SizedBox,类似Android中的Space

SizedBox(width: 10.0),

5.14 收起软键盘

有时候需要在点击某些按钮时收起软键盘

FocusScope.of(context).requestFocus(FocusNode());

5.15 让ListView的item点击时有水波纹效果

用InkWell把Item包起来