Flutter仿写单读App介绍与总结

2019 年 2 月 10 日 CocoaChina

前言:


  • Flutter在2018年里从Beta一直走到1.0版本,虽然暴露出1.0版本仍然有很多Bug、生态不完善、库少、某些功能实现不好等问题,但是关注它的人们依然热情很高,每天都有关于Flutter的新的东西产出,说明很多人还是很看好它的。

  • 国内阿里腾讯等也在积极布道,做了很多高质量的分享。也许大火需要时间,商用也需要胆量,但是现在学习了解一下,还是很有必要的。


关于项目:


这次选择仿写一个很轻量的项目单读App,主要通过项目来练习一下常用的组件布局网络请求json解析


项目准备:


本项目是通过Charles抓包工具来获取项目Api,通过Apple Configurator 2工具来获取仿写App的图片资源,关于两个工具的介绍和使用可以参考下以前的文章Swift高仿喜马拉雅APP之一Charles抓包、图片资源获取等


项目源码:


Flutter仿写单读App 完整源码


效果图:


点击查看动图


项目整体主要点介绍:


以项目进来看到的主页面为例介绍布局:


项目主页采用的是PageView,当前屏幕所能看到的即时PageView的单一页面,该页面的布局主要是使用Column里面children: <Widget>[] 包裹各种Widget,顶部图片是使用new Image.network()来获取显示网络图片,中间部分为包裹的Container、Text,各个组件之间间距可以使用Padding或者Expanded来零活控制,最底部选择使用Row包裹一下喜欢喝评论按钮等。



主页面的主要代码示例:


返回PageView:


 return Center(
      child: PageView.builder(
        itemCount: dataList.length,
        scrollDirection: Axis.vertical,
        itemBuilder: (context, index) {
          return calendarList(dataList[index],index,context);
        },
      ),
    );


网络请求和数据解析:


需要在工程的pubspec.yaml文件中dependencies: 中引入 dio: ^1.0.13 该库为网络请求库^x.x.x为版本号。在dependencies:中引入json_annotation:: ^2.0.0,在dev_dependencies:中引入 json_serializable: ^2.0.1build_runner: ^1.1.2,这三个为json解析库,配合使用就可以进行网络请求和json解析数据赋值了,引入完成后记得右上角的Packages get一下。


使用举例如下:


说明一下:

由于是抓包接口,抓包中含有加密的sign字段并没有破解,导致接口有很短的超时时间,所以该项目中使用了很多的json本地接口数据,也意味着没有分页,但是文字模块的接口是获取的网络接口,含有分页和下拉接在更多,所以该项目可以看到本地json数据和网络数据两种处理方式。


对于本地json文件的引入,可以直接拖动一个json到工程自己新建的文件夹,然后在pubspec.yaml文件中flutter:下面写上assets:代表引入资源文件,然后把具体路径写下就行了例如- json/HomeData.json不过需要注意写法和空格,同样项目中需要的图片资源也是拖进新的个文件夹内,然后在这里面做下引入Packages get一下,具体的可以查看本项目源码。


本地json数据load和解析示例:


 void loadData() async{
    String cssStr = await DefaultAssetBundle.of(context).loadString('json/HomeData.json');
    CommonModel model = CommonModel.fromJson(json.decode(cssStr));
    setState(() {
      List<dynamic>data = [];
      data.addAll(model.datas);
      dataList.addAll(data);//给数据源赋值
    });
  }


网络接口请求和解析示例:说明一下请求http接口的时候iOS需要添加信任Http认证,具体为iOS文件夹-->Runner-->info.plist添加代码



  /// 这里是请求接口数据,当滑动到最底端的时候会判断是否加载更多更改二个参数,post_id,page+1,然后把新请求的数据追加到以前的数据中
  void loadData(String post_id, int curPage,bool isMore) async{
    Dio dio = new Dio();
    Response response = await dio.get("http://203.195.230.211/index.php?m=Home&c=Api2&a=getTagList&tag=$tag&p=$curPage&client=iOS&show_sdv=1&page_id=$post_id&create_time=0&sign=24a904d18a786327741ca5613180ceda&time=1547092560&device_id=A39632E7-F689-405B-A3A3-0319D6095B54&version=1.6.2&client=iOS");
    CommonModel result = CommonModel.fromJson(response.data);
    setState(() {
      if(!isMore) {
        List<dynamic>data = [];
        data.addAll(result.datas);
        new_page_id = result.datas[data.length-1].id;
        dataList.addAll(data);//给数据源赋值
      }else{
        List<dynamic>data = [];
        data.addAll(result.datas);
        new_page_id = result.datas[data.length-1].id;
        dataList.addAll(data);
      }
    });
  }
}


对于数据模型的创建:


这里利用一个工具来自动生成modeljson2dart,如图所示:



具体用法是先在工程中创建一个例如HomeDataModel.dart文件,然后利用该工具生成json格式,拷贝到该文件下面,注意上图中的entity应该改为HomeDataModel,这个时候终端cd到该项目的根目录,即是ls可以看到pubspec.yaml的目录,运行flutter packages pub run build_runner build,过一会会自动生成HomeDataModel.g.dart的文件。


到这里首页部分的布局数据模型接口请求数据解析都好了,就可以赋值查看真是数据的页面了,这里以主页底部Row,即点赞喜欢部分赋值代码示例,具体的请看源码。


class _LikeWidgetState extends State<LikeWidget{
  bool _isLike = true;
  void _togoLike() {
    setState(() {
      /// 如果是喜欢置为未喜欢
      if (_isLike) {
        _isLike = false;
        /// 如果是未喜欢置为喜欢
      } else {
        _isLike = true;
      }
    });
  }
  @override
  Widget build(BuildContext context) {
    return Row(
      children:  [
         new Expanded(
            child: new Row(
              children:  [
                 new IconButton(
                    icon: Image.asset( 'assets/comment@2x.png'),
                    onPressed: (){
                      Navigator.of(context).push( new MaterialPageRoute(builder: (ctx) {
                         return  new CommentPageStateful(post_id: widget.data.id);
                      }));
                    }
                ),
                 new Text(
                  widget.data.comment,
                  style: TextStyle(
                    color: Colors.black,
                    fontSize:  16,
                  ),
                ),
                 new IconButton(
                    icon: (_isLike ?  new Image.asset( "assets/like@2x.png") :  new Image.asset( "assets/liked@2x.png")),
                    onPressed: (){
                      _togoLike();
                    }
                ),
                 new Text(
                   /// 这里是先通过int.parse(widget.data.good) string转int ,然后toString()再转成string
                  _isLike ?  int.parse(widget.data.good).toString() : ( int.parse(widget.data.good)+ 1).toString(),
                  style: TextStyle(
                    color: Colors.black,
                    fontSize:  16,
                  ),
                ),
              ],
            )
        ),
         new Container(
          padding: EdgeInsets.only(right:  15),
          child:  new Text(
             '阅读数 '+widget.data.view,
            style: TextStyle(
              color: Colors.black,
              fontSize:  16,
            ),
          ),
        )
      ],
    );
  }
}


自定义Drawer:


通过主页面可以看到,顶部AppBar左右两边按钮可以弹出左右Drawer,由于系统的Drawer是不能遮罩全屏的,所以这里选择自定义Widget实现全屏遮罩的Drawer


效果图:




自定义Drawer示例代码:


/// 自定义的左边栏抽屉
class LeftDrawer extends StatefulWidget {
  final double elevation;
  final Widget child;
  final String semanticLabel;
  final double widthPercent;
  ///add start
  final DrawerCallback callback;
  ///add end
  const LeftDrawer({
    Key key,
    this.elevation = 16.0,
    this.child,
    this.semanticLabel,
    this.widthPercent,
    ///add start
    this.callback,
    ///add end
  })
  : assert(widthPercent < 1.0 && widthPercent > 0.0),
        super(key: key)
;
  @override
  _LeftDrawerState createState() => _LeftDrawerState();
}

class _LeftDrawerState extends State<LeftDrawer{

  @override
  Widget build(BuildContext context) {
    assert(debugCheckHasMaterialLocalizations(context));
    String label = widget.semanticLabel;
    final double _width = MediaQuery.of(context).size.width * widget.widthPercent;
    return Semantics(
      scopesRoute: true,
      namesRoute: true,
      explicitChildNodes: true,
      label: label,
      child: ConstrainedBox(
        constraints: BoxConstraints.expand(width: _width),
        child: Material(
          color: Colors.black87,
          elevation: widget.elevation,
          child: Container(
          ),
        ),
      ),
    );
  }


对于左右Drawer中的文字按钮有个放大显示的动画,该字体动画使用的是 animated_text_kit: ^1.2.0,有好几种动画,具体可以参考源码,具体使用为导入import 'package:animated_text_kit/animated_text_kit.dart';


  Widget _AnimatedText(String str){
    return SizedBox(
      child: ScaleAnimatedTextKit(
        duration: Duration(milliseconds: 4000),
        isRepeatingAnimation: false,
        text:[str],
        textStyle: TextStyle(
            fontSize: 35,
            color: Colors.white
        ),
      ),
    );
  }


关于加载WebView:


Flutter中使用WebView,可以选择使用flutter_webview_plugin: ^0.3.0+2插件,使用也很简单引入头文件import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';


Widget _WebView(String url) {
  return WebviewScaffold(url: url);
}


使用该插件可以显示一般WebView页面,但是我工程中的WebView页面顶部AppBar,上面还是有按钮的,如下图所示,点击按钮要跳转到评论界面,但是使用该插件因为返回的WebviewScaffold,所以当在WebView页面跳转其他页面的时候,该页面会一直在其他页面的最上层,虽然可以使用widget.flutterWebviewPlugin.close();来进行关闭该页面,但是意味着返回来的时候就看不到WebView页面内容了,我本想去作者github寻求答案,看到有人已经提了issue但是作者给的标签是待解决。有人知道这个问题怎么解决请赐教。



而如果只是单纯的加载WebView页面,不再进行二次跳转的情况下,还可以使用 url_launcher: ^4.0.1插件,该插件为系统插件不仅可以显示WebView页面具体使用请自行查阅资料,使用后的页面样式是固定的,具体使用参考如下引入import 'package:url_launcher/url_launcher.dart';


 void _launchURL(String url, BuildContext context) async {
    if (await canLaunch(url)) {
      await launch(url);
    } else {
      throw 'Could not launch $url';
    }
  }

  onTap: (){
    _launchURL(data.html5, context);
  },


url_launcher显示的WebView页面样式:



说明一下:


本项目中两种加载WebView页面的方式均有使用,文字模块的二级页面的Detail页面使用的是flutter_webview_plugin,其余详情页面使用的均是url_launcher显示的WebView页面。


***追加说明:


经过群友提示发现了更好的一种webview加载方式,使用 webview_flutter: ^0.2.0插件,目前该项目中WebView页面已经全部换成该方法,使用该插件可以自定义AppBar,并且跳转页面也不会有问题,以上两种webview加载方法可以自行选择。
具体使用为在pubspec.yaml中引入webview_flutter: ^0.2.0并且Packages get一下,在需要用到的页面import 'package:webview_flutter/webview_flutter.dart'; import 'dart:async';


需要特别注意的是iOS端需要在info.plist中添加设置



使用源码示例:


class WebViewExample extends StatelessWidget {
  final Completer  _controller =
  Completer ();

   @override
   Widget build(BuildContext context) {
     return Scaffold(
      appBar: AppBar(
        title:  const Text('Flutter WebView example'),
      ),
      body: WebView(
        initialUrl: 'https://www.baidu.com',
        javascriptMode: JavascriptMode.unrestricted,
        onWebViewCreated: (WebViewController webViewController)
 
{
          _controller.complete(webViewController);
        },
      ),
    );
  }
}



评论页面:
对于刚刚的使用flutter_webview_pluginWebView页面右上角点击评论按钮,即可跳转评论页面,评论页面因为需要显示HeaderView,所以选择了sticky_headers: ^0.1.7库,使用起来效果很好也很方便,对于布局和详细代码请查看源码,sticky_headers主要代码示例如下:


child: ListView.builder(
          itemCount: newsList.length+hotsList.length+1,
          itemBuilder: (context, index) {
            if (index == 0) {
              return StickyHeader(header: CommentHeader('喜欢', diggList.length, index),
              content: DiggList(diggList, diggList.length, context),
              );
            }else if (hotsList.length >0 && index < hotsList.length+1) {
              return StickyHeader(header: CommentHeader('最热评论', hotsList.length, index-1),
                content: HotsList(hotsList[index-1], index, context),
              );
            }else {
              return StickyHeader(header: CommentHeader('最新评论', newsList.length, index-hotsList.length-1),
                content: NewsList(newsList[index-hotsList.length-1], index, context),
              );
            }
          },
        ),


评论页面效果图:



谈论页面:


谈论页面顶部含有一个TabBar,左右滑动可以切换单读问页面和读者论页面。


代码示例如下:


class TalkStateful  extends StatefulWidget {
  @override
  _TalkPageState createState() => new _TalkPageState();
}

class _TalkPageState extends State<TalkStatefulwith SingleTickerProviderStateMixin {
  TabController _tabController;
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _tabController = new TabController(vsync: this, initialIndex: 0, length: 2);
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.black87,
        title: new Text('谈论', style: TextStyle(color: Colors.white,fontSize: 24),),
        bottom: new TabBar(
            isScrollable: false,//是否可滑动
            unselectedLabelColor: Colors.white,//未选中按钮颜色
            labelColor: Colors.white,//选中按钮颜色
            labelStyle: TextStyle(fontSize: 16),//文字样式
            indicatorSize: TabBarIndicatorSize.label,//滑动的宽度是根据内容来适应,还是与整块那么大(label表示根据内容来适应)
            indicatorWeight: 2.0,//滑块高度
            indicatorColor: Colors.black87,//滑动颜色
            indicatorPadding: EdgeInsets.only(bottom: 1),//与底部距离为1
            controller: _tabController,
            tabs:   [
               new Tab(
                text:  '单读问',
              ),
               new Tab(
                text:  '读者论',
              )
            ]
        ),
      ),
      body:  new TabBarView(
          controller: _tabController,
          children:   [
             new DanDuWenPage(),
             new DuZheLunPage(),
          ]
      ),
    );
  }
}



谈论页面效果图如下:




关于本项目就介绍到这里吧,想了解更多请看源码部分,如果有什么不懂得或者疑问可以相互交流。


应用Icon和启动页LaunchImage设置:


iOS设置工程主文件夹例:flutter_dandu--> ios-->Runner-->Assets.xcassets-->AppIcon And LaunchImage



Android设置工程主文件夹例:flutter_dandu-->android-->app-->src--main-->res



总结:


刚接触Flutter使用Dart语言,肯定是会别扭的,嵌套过多阅读困难,但是了解之后发现也还行,总体来说现在谈Flutter的将来还有点早,但是前景还是很不错的,有时间的或者客户端前端从业者可以考虑入门学习一下。熟练之后写东西确实很快,跨平台是大势所趋,就算不是Flutter也会有其他的出现。最重要的是FlutterGoogle手机设备新系统FuchsiaUI框架和过渡性产品,应该还是挺看好的。


学习资源:


对于大佬肯定是啃源码和官方文档了,但是对于小菜鸡来说还是找找译文和其他大佬教程吧。这里整理了一些不错的学习资源链接:
系列基础知识教程:
1、Flutter中文网:https://flutterchina.club
2、技术胖:Flutter基础视频免费教程
大佬技术文章:
1.
juejin.im/user/59be05…
2.juejin.im/post/5c1f35…
3.juejin.im/user/5ad016…
4.juejin.im/post/5bc450…
5.www.jianshu.com/u/5c5872cc9…
Flutter圈子和标签及知乎:
Flutter圈子:https://www.jianshu.com/c/ebc9d2e84214
Flutter标签:https://juejin.im/tag/Flutter
Flutter知乎:https://zhuanlan.zhihu.com/flutter
Flutter布道者:
闲鱼技术团队
Flutter开源示例:
1.
github.com/2d-inc/Hist…
2.flutterawesome.com
3.github.com/Solido/awes…
Flutter优秀开源项目:
1、Github客户端App
2、阿里拍卖团队Flutter开发者帮助App
3、开源中国App
4、仿网易云音乐App
5、仿书旗小说App
6、仿好奇心日报App
7、仿单读App


作者:Domo

链接:

https://juejin.im/post/5c4016c5f265da61483bdaea


本公众号转载内容已尽可能注明出处,如未能核实来源或转发内容图片有权利瑕疵的,请及时联系本公众号进行修改或删除【联系方式QQ : 3442093904  邮箱:support@cocoachina.com】。文章内容为作者独立观点,不代表本公众号立场。版权归原作者所有,如申请授权请联系作者,因文章侵权本公众号不承担任何法律及连带责任。

---END---

登录查看更多
0

相关内容

JSON( Java Script Object Notation)是一种轻量级的资料交换语言,以文字为基础,且易于让人阅读。尽管 JSON 是在 JavaScript 的一個子集,但 JSON 是独立于语言的文本格式,並且采用了类似于 C 语言家族的一些习惯。
【2020新书】实战R语言4,323页pdf
专知会员服务
98+阅读 · 2020年7月1日
【实用书】Python技术手册,第三版767页pdf
专知会员服务
229+阅读 · 2020年5月21日
【实用书】Python爬虫Web抓取数据,第二版,306页pdf
专知会员服务
115+阅读 · 2020年5月10日
100+篇《自监督学习(Self-Supervised Learning)》论文最新合集
专知会员服务
161+阅读 · 2020年3月18日
TensorFlow Lite指南实战《TensorFlow Lite A primer》,附48页PPT
专知会员服务
68+阅读 · 2020年1月17日
【干货】大数据入门指南:Hadoop、Hive、Spark、 Storm等
专知会员服务
94+阅读 · 2019年12月4日
【电子书】C++ Primer Plus 第6版,附PDF
专知会员服务
83+阅读 · 2019年11月25日
【电子书】Flutter实战305页PDF免费下载
专知会员服务
20+阅读 · 2019年11月7日
【初学者系列】tensorboard学习笔记
专知
7+阅读 · 2019年10月4日
Cayley图数据库的可视化(Visualize)
Python开发者
5+阅读 · 2019年9月9日
用Now轻松部署无服务器Node应用程序
前端之巅
16+阅读 · 2019年6月19日
美团:基于跨平台框架Flutter的动态化平台建设
前端之巅
14+阅读 · 2019年6月17日
硬核实践经验 - 企鹅辅导 RN 迁移及优化总结
IMWeb前端社区
5+阅读 · 2019年5月6日
7 款实用到哭的App,只说一遍
高效率工具搜罗
84+阅读 · 2019年4月30日
从webview到flutter:详解iOS中的Web开发
前端之巅
5+阅读 · 2019年3月24日
学术型ipad pro配置分享
专知
29+阅读 · 2018年12月31日
WebAssembly在QQ邮箱中的一次实践
IMWeb前端社区
13+阅读 · 2018年12月19日
Generative Adversarial Networks: A Survey and Taxonomy
A Comprehensive Survey on Graph Neural Networks
Arxiv
13+阅读 · 2019年3月10日
Arxiv
5+阅读 · 2018年10月11日
VIP会员
相关VIP内容
【2020新书】实战R语言4,323页pdf
专知会员服务
98+阅读 · 2020年7月1日
【实用书】Python技术手册,第三版767页pdf
专知会员服务
229+阅读 · 2020年5月21日
【实用书】Python爬虫Web抓取数据,第二版,306页pdf
专知会员服务
115+阅读 · 2020年5月10日
100+篇《自监督学习(Self-Supervised Learning)》论文最新合集
专知会员服务
161+阅读 · 2020年3月18日
TensorFlow Lite指南实战《TensorFlow Lite A primer》,附48页PPT
专知会员服务
68+阅读 · 2020年1月17日
【干货】大数据入门指南:Hadoop、Hive、Spark、 Storm等
专知会员服务
94+阅读 · 2019年12月4日
【电子书】C++ Primer Plus 第6版,附PDF
专知会员服务
83+阅读 · 2019年11月25日
【电子书】Flutter实战305页PDF免费下载
专知会员服务
20+阅读 · 2019年11月7日
相关资讯
【初学者系列】tensorboard学习笔记
专知
7+阅读 · 2019年10月4日
Cayley图数据库的可视化(Visualize)
Python开发者
5+阅读 · 2019年9月9日
用Now轻松部署无服务器Node应用程序
前端之巅
16+阅读 · 2019年6月19日
美团:基于跨平台框架Flutter的动态化平台建设
前端之巅
14+阅读 · 2019年6月17日
硬核实践经验 - 企鹅辅导 RN 迁移及优化总结
IMWeb前端社区
5+阅读 · 2019年5月6日
7 款实用到哭的App,只说一遍
高效率工具搜罗
84+阅读 · 2019年4月30日
从webview到flutter:详解iOS中的Web开发
前端之巅
5+阅读 · 2019年3月24日
学术型ipad pro配置分享
专知
29+阅读 · 2018年12月31日
WebAssembly在QQ邮箱中的一次实践
IMWeb前端社区
13+阅读 · 2018年12月19日
Top
微信扫码咨询专知VIP会员