Flutter 第一个界面

news2024/11/24 0:10:36

第一个页面

app首页

入口函数

一个Flutter工程的入口函数与Dart命令行工程一样是main,不同的是在Flutter中执行runApp(ArticleApp()) 就能够在手机屏幕上展示这个Widget。

import 'package:flutter/material.dart';
void main() => runApp(new ArticleApp());

ArticleApp

我们要实现的文章列表页面UI就在ArticleApp中定义:

class ArticleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text(
            '文章',
            style: const TextStyle(color: Colors.white),
          ),
        ),
        body: new ArticlePage(),
      ),
    );
  }
}

build方法中返回的就是我们需要显示在屏幕上的widget。MaterialApp代表使用Material Design风格,这是一个封装了很多Android MD设计所必须要的组件的小部件。假设我们需要显示一个Text,而没有包裹在MaterialApp内:

class ArticleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //Center:摆放在中间
    return Center(
      child: Text("你好!"),
    );
  }
}

如果直接运行则会出现异常,因为Flutter不知道以什么顺序摆放文字(从左到右/从右到左)

因此我们不得不给Text指名textDirection属性:

class ArticleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text("你好!",
          style: const TextStyle(color: Colors.white),
          textDirection: TextDirection.ltr),
    );
  }
}

但是如果包含在MaterialApp当中我们就不需要指名类似textDirection这样的属性了,因为内部已经内置了一套风格,指明了这些必须属性。而Scaffold则实现了基本的 Material Design布局结构,在 Material 设计中定义的单个界面上的各种布局元素,在 Scaffold 中都支持。比如:AppBar、抽屉菜单、BottomNavigationBar等等。

ArticlePage

​ 在我们的布局中指定了Scaffold的body(主体)为ArticlePage,这是一个我们自定义的组合Widget。

class ArticlePage extends StatefulWidget {
  @override
  _ArticlePageState createState() => _ArticlePageState();
}

class _ArticlePageState extends State<ArticlePage> {
  ///滑动控制器
  ScrollController _controller = new ScrollController();

  ///控制小菊花的显示
  bool _isLoading = true;

  ///请求到的文章数据
  List articles = [];

  ///banner图
  List banners = [];

  ///总文章数有多少
  var listTotalSize = 0;

  ///分页加载,当前页码
  var curPage = 0;

  @override
  void initState() {
    super.initState();
    _controller.addListener(() {
      ///获得 SrollController 监听控件可以滚动的最大范围
      var maxScroll = _controller.position.maxScrollExtent;

      ///获得当前位置的像素值
      var pixels = _controller.position.pixels;

      ///当前滑动位置到达底部,同时还有更多数据
      if (maxScroll == pixels && articles.length < listTotalSize) {
        ///加载更多
        _getArticlelist();
      }
    });
    _pullToRefresh();
  }

  _getArticlelist([bool update = true]) async {
    /// 请求成功是map,失败是null
    var data = await Api.getArticleList(curPage);
    if (data != null) {
      var map = data['data'];
      var datas = map['datas'];

      ///文章总数
      listTotalSize = map["total"];

      if (curPage == 0) {
        articles.clear();
      }
      curPage++;
      articles.addAll(datas);

      ///更新ui
      if (update) {
        setState(() {});
      }
    }
  }

  _getBanner([bool update = true]) async {
    var data = await Api.getBanner();
    if (data != null) {
      banners.clear();
      banners.addAll(data['data']);
      if (update) {
        setState(() {});
      }
    }
  }

  ///下拉刷新
  Future<void> _pullToRefresh() async {
    curPage = 0;
    Iterable<Future> futures = [_getArticlelist(), _getBanner()];
    await Future.wait(futures);
    _isLoading = false;
    setState(() {});
    return null;
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        ///小菊花
        Offstage(
          offstage: !_isLoading, //是否隐藏
          child: new Center(child: CircularProgressIndicator()),
        ),

        ///内容
        Offstage(
          offstage: _isLoading,
          child: new RefreshIndicator(
              child: ListView.builder(
                itemCount: articles.length + 1,
                itemBuilder: (context, i) => _buildItem(i),
                controller: _controller,
              ),
              onRefresh: _pullToRefresh),
        )
      ],
    );
  }

  Widget _buildItem(int i) {
    if (i == 0) {
      return new Container(
        height: 180.0,
        child: _bannerView(),
      );
    }
    var itemData = articles[i - 1];
    return new ArticleItem(itemData);
  }

  Widget _bannerView() {
    var list = banners.map((item) {
      return Image.network(item['imagePath'], fit: BoxFit.cover);
    }).toList();
    return list.isNotEmpty
        ? BannerView(
            list,
            intervalDuration: const Duration(seconds: 3),
          )
        : null;
  }
}

这个Widget的代码比较多,它配置了我们见到的banner、与文章列表。代码中重写了State的生命周期方法initStatebuild。我们首先来观察build方法:

@override
  Widget build(BuildContext context) {
    //Stack:帧布局  
    return Stack(
      children: <Widget>[
        ///正在加载
        Offstage( //可以控制是否隐藏
          offstage: !_isLoading, //是否隐藏
          child: new Center(child: CircularProgressIndicator()),//圆形进度指示器(小菊花)
        ),

        ///内容
        Offstage(
          offstage: _isLoading,
          child: new RefreshIndicator( //下拉刷新
              child: ListView.builder(
                itemCount: articles.length + 1, //列表视图的个数
                itemBuilder: (context, i) => _buildItem(i),//类似adapter,item显示什么?返回widget
                controller: _controller,//滑动控制器
              ),
              onRefresh: _pullToRefresh),//刷新回调方法
        )
      ],
    );
  }

这段代码中各个部分都给到了注释,_buildItem_pullToRefresh方法分别用于条目视图的生成与新数据的获取。

_pullToRefresh

_pullToRefresh是传递给下拉刷新组件:RefreshIndicator的刷新回调方法参数,它需要返回一个Future<void>,同时我们初次进入页面也需要自动的去获取一次数据,所以我们还会在initState方法中主动的调用一次该方法。

Future<void> _pullToRefresh() async {
    curPage = 0;
    Iterable<Future> futures = [_getArticlelist(), _getBanner()];
    await Future.wait(futures);
    _isLoading = false;
    setState(() {});
    return null;
  }

在这个方法中,我们需要重新请求文章列表与banner图,因此借助Future.wait组合两个任务,在两个任务都完成后,再利用setState更新UI完成重绘。

_buildItem

获取到数据之后,接下来我们需要对这些数据进行展示

Widget _buildItem(int i) {
    if (i == 0) {
      return new Container(
        height: 180.0,
        child: _bannerView(),
      );
    }
    var itemData = articles[i - 1];
    return new ArticleItem(itemData);
  }

  Widget _bannerView() {
    ///banners是请求到的banner信息组,其中imagePath代表了图片地址
    ///map意为映射,对banners中的数据进行遍历并返回Iterable<?>迭代器,
    ///?则是在map的参数:一个匿名方法中返回的类型
    var list = banners.map((item) {
      return Image.network(item['imagePath'], fit: BoxFit.cover);
    }).toList();
    ///BannerView的条目不能为空
    return list.isNotEmpty
        ? BannerView(
            list,
            ///切换时间
            intervalDuration: const Duration(seconds: 3),
          )
        : null;
  }

_buildItem用于生成ListView当中的条目。注意在配置ListView时,我们给的itemCount为:articles.length + 1。articles就是请求到的文章信息数量,而+1则是为了显示banner。因此当i=0,显示第一个条目时候,我们返回了一个BannerView。这个BannerView其实是一个库(关于如何导入第三方库在最后)。而ArticleItem则又是我们自己定义的用于显示文章信息item的组合Widget。

ArticleItem

class ArticleItem extends StatelessWidget {
  final itemData;

  const ArticleItem(this.itemData);

  @override
  Widget build(BuildContext context) {
    ///时间与作者
    Row author = new Row( //水平线性布局
      children: <Widget>[
        //expanded 最后摆我,相当于linearlayout的weight权重
        new Expanded(
            child: Text.rich(TextSpan(children: [
          TextSpan(text: "作者: "),
          TextSpan(
              text: itemData['author'],
              style: new TextStyle(color: Theme.of(context).primaryColor))
        ]))),
        new Text(itemData['niceDate'])//时间
      ],
    );

    ///标题
    Text title = new Text(
      itemData['title'],
      style: new TextStyle(fontSize: 16.0, color: Colors.black),
      textAlign: TextAlign.left,
    );

    ///章节名
    Text chapterName = new Text(itemData['chapterName'],
        style: new TextStyle(color: Theme.of(context).primaryColor));

    Column column = new Column( //垂直线性布局
      crossAxisAlignment: CrossAxisAlignment.start, //子控件左对齐
      children: <Widget>[
        new Padding(
          padding: EdgeInsets.all(10.0),
          child: author,
        ),
        new Padding(
          padding: EdgeInsets.fromLTRB(10.0, 5.0, 10.0, 5.0),
          child: title,
        ),
        new Padding(
          padding: EdgeInsets.fromLTRB(10.0, 5.0, 10.0, 10.0),
          child: chapterName,
        ),
      ],
    );

    return new Card(
      ///阴影效果
      elevation: 4.0,
      child: column,
    );
  }
}

Expanded

可以按比例“扩伸”Row、Column所占用的空间。

const Expanded({
  int flex = 1, 
  @required Widget child,
})

flex为弹性系数,和Android中的LinearLayoutweight比重效果一致。

class _LayoutWidgetState extends State<LayoutWidget> {
  @override
  Widget build(BuildContext context) {
    return Row(
      //将Row 分成 2+3+1分,
      children: <Widget>[
        Expanded(flex:2,child: Container(child: Text('1'), color: Colors.red)),
        Expanded(flex:3,child: Container(child: Text('1'), color: Colors.blue)),
        Expanded(flex:1,child: Container(child: Text('1'), color: Colors.yellow)),
      ],
    );
  }
}

网络请求

​ 一个app中,网络请求是最基本的功能,我们需要使用网络请求数据用于显示或者进行不同的逻辑处理。在我们的案例中,同样需要请求文章数据与banner数据。在Dart SDK中的io库其实提供了HttpClient 进行网络请求。大家都知道,Java中也提供了HttpConnection,但是我们更喜欢使用更加方便的OkHttp,所以一般开发中,我们可能使用一些更加方便的网络库,比如http、dio等等。进入https://pub.dartlang.org/ 输入库名就能够搜索到相关的库。这次我们使用dio来完成网络的请求:

class HttpManager {
  Dio _dio;
  static HttpManager _instance;

  factory HttpManager.getInstance() {
    if (null == _instance) {
      _instance = new HttpManager._internal();
    }
    return _instance;
  }

  //以 _ 开头的函数、变量无法在库外使用
  HttpManager._internal() {
    ///基础配置
    BaseOptions options = new BaseOptions(
      baseUrl: Api.baseUrl, //基础地址
      connectTimeout: 5000, //连接服务器超时时间,单位是毫秒
      receiveTimeout: 3000, //读取超时
    );
    _dio = new Dio(options);
  }

  request(url, {String method = "get"}) async {
    try {
      ///默认使用get请求
      Options option = new Options(method: method);
      Response response = await _dio.request(url, options: option);
      ///一般来说,提供的是json字符串,response.data得到的就是这个json对应的map
      return response.data;
    } catch (e) {
      return null;
    }
  }
}

导入库

​ 在Flutter工程中存在一个pubspec.yaml文件。此文件类似build.gradle,在这个文件中进行我们整个工程的一些配置,其中就包括了库的导入。配置完成之后,点击右上角的Packages get就能自动下载依赖。

附上:

Dart packagesPub is the package manager for the Dart programming language, containing reusable libraries & packages for Flutter and general Dart programs.https://pub.flutter-io.cn/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/422270.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenAI不能访问有什么方法解救呢?试试这方法吧

最近发现国内不挂代理是不能访问到openAI的接口的&#xff0c;为了解决这个问题&#xff0c;我一直在github上需在解决方案&#xff0c;今天终于被我找到一个大神开源了一个解决方案。下面就来看看如何做吧。 整个项目的代码很简单只有几行代码&#xff1a; {"rewrites&q…

几种在Python中List添加、删除元素的方法

嗨害大家好鸭&#xff01;我是爱摸鱼的芝士❤ 一、python中List添加元素的几种方法 List 是 Python 中常用的数据类型&#xff0c; 它一个有序集合&#xff0c; 即其中的元素始终保持着初始时的定义的顺序 &#xff08;除非你对它们进行排序或其他修改操作&#xff09;。 …

进程互斥的实现方式

1.进程互斥的软件实现方法 1.单标志法 算法思想&#xff1a;两个进程在访问完临界区后会把使用临界区的权限转让给另一个进程&#xff0c;也就是说每个进程进入临界区的权限只能被另一个进程赋予 局限性 2.双标志先检查法 算法思想&#xff1a;设置一个布尔数组flag[]&#xff…

python 笔记:PyTrack(将GPS数据和OpenStreetMap数据进行整合)【官网例子解读】

论文笔记&#xff1a;PyTrack: A Map-Matching-Based Python Toolbox for Vehicle Trajectory Reconstruction_UQI-LIUWJ的博客-CSDN博客4 0 包的安装 官网的两种方式我都试过&#xff0c;装是能装成功&#xff0c;但是python import PyTrack包的时候还是显示找不到Pytrack …

Altova MapForce 2023 Crack

Altova MapForce 2023 Crack 数据映射项目中的注释-除了支持对数据映射项目的单个连接进行注释外&#xff0c;现在还可以向源组件和目标组件添加注释&#xff0c;以帮助记录映射的作用和实现方式。 支持XML输出中的standalone“yes”声明-在独立文档声明中&#xff0c;值“yes”…

Chat-GLM 详细部署(GPU显存>=12GB)

建议配置: ( Windows OS 11 部署 )CPU-i7 13700F ~ 13700KF RAM: 16GB DDR4 GPU: RTX3080(12G) 安装 conda: 1. 下载安装 miniconda3 &#xff1a; https://docs.conda.io/en/latest/miniconda.html conda是一个包和环境管理工具&#xff0c;它不仅能管理包&#xff0c;还能隔…

Linux嵌入式学习之Ubuntu入门(四)Makefile

系列文章目录 一、Linux嵌入式学习之Ubuntu入门&#xff08;一&#xff09;基本命令、软件安装及文件结构 二、Linux嵌入式学习之Ubuntu入门&#xff08;二&#xff09;磁盘文件介绍及分区、格式化等 三、Linux嵌入式学习之Ubuntu入门&#xff08;三&#xff09;用户、用户组…

go语言切片做函数参数传递+append()函数扩容

go语言切片函数参数传递append()函数扩容 给你二叉树的根节点 root 和一个整数目标和 targetSum &#xff0c;找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。 二叉树递归go代码&#xff1a; var ans [][]int func pathSum(root *TreeNode, targetSum int) ( [][…

Longitudinal Change Detection on Chest X-rays Using Geometric Correlation Maps

文章来源&#xff1a;[MICCAI2019] Keywords&#xff1a;Chest X-ray&#xff1b;Longitudinal analysis&#xff1b;Change detection&#xff1b;Geometric correlation 一、本文提出的问题以及解决方案 在胸部X-ray图像的诊断中&#xff0c;医生会考虑与先前检查相比病变的…

8.网络爬虫—正则表达式RE实战

8.网络爬虫—正则表达式RE实战正则表达式&#xff08;Regular Expression&#xff09;re.Ire.Are.Sre.Mre.Xre.Lre.U美某杰实战写入csv文件&#xff1a;前言&#xff1a; &#x1f3d8;️&#x1f3d8;️个人简介&#xff1a;以山河作礼。 &#x1f396;️&#x1f396;️:Pyth…

MongoDB 聚合管道的文档操作($sort,$skip,$limit,$sample,$unwind)

目前为止&#xff0c;我们已经介绍了一部分聚合管道中的管道参数&#xff1a; $match&#xff1a;文档过滤 $group&#xff1a;文档分组&#xff0c;并介绍了分组中的常用操作&#xff1a;$addToSet&#xff0c;$avg&#xff0c;$sum&#xff0c;$min&#xff0c;$max等。 $add…

COCO数据集相关知识介绍

&#x1f468;‍&#x1f4bb;个人简介&#xff1a; 深度学习图像领域工作者 &#x1f389;总结链接&#xff1a; 链接中主要是个人工作的总结&#xff0c;每个链接都是一些常用demo&#xff0c;代码直接复制运行即可。包括&#xff1a; &am…

SpringTx 源码解析 - @Transactional 声明式事务执行原理

一、Spring Transactional 声明式事务执行原理 Transactional 是 Spring 框架中用于声明事务的注解&#xff0c;可以标注在方法或类上。当标注在类上时&#xff0c;表示该类的所有public方法都将支持事务。当标注在方法上时&#xff0c;表示该方法将在一个事务内执行。 Trans…

BGP对等体建邻配置

BGP对等体大体分为EBGP对等体和IBGP对等体。而BGP对等体的建邻主要分为两种&#xff1a;1、使用物理接口建邻 2、使用环回借口建邻&#xff0c;针对不同的BGP对等体选用不同的建邻方式。 EBGP的建邻主要使用的是物理接口建邻 IBGP的建邻主要使用的是环回接口建邻 这两种建邻方…

VBA的面向接口编程

工作中有时候会用到VBA&#xff08;Visual Basic for Applications&#xff09;&#xff0c;不是很多&#xff0c;也没有专门去学习VBA&#xff0c;用的时候遇到问题就上网去查资料&#xff0c;解决问题了就放下了。 今天被同事问到VBA中类的用法&#xff0c;我从来没有用过&am…

论文解读:基于 OpenMLDB 的流式特征计算优化

近期&#xff0c;数据库领域的顶级学术会议 ICDE 2023 在迪斯尼主题公园的故乡 - 美国的安纳海姆&#xff08;Anaheim&#xff09;举办。由 OpenMLDB 开源社区和新加坡科技设计大学&#xff08;Singapore University of Technology and Design&#xff09;联合完成的研究工作在…

Vue2-黑马(三)

目录&#xff1a; &#xff08;1&#xff09;vue2-axios &#xff08;2&#xff09;axios-发送请求 &#xff08;3&#xff09;vue2-axios-请求体格式 &#xff08;4&#xff09;vue2-axios-默认配置 &#xff08;1&#xff09;vue2-axios 已经配置了代理&#xff0c;可以…

项目部署---shell脚本自动部署项目

通过shell脚本自动部署项目 操作步骤&#xff1a; 在Linux中安装Git在Linux中安装maven编写shell脚本&#xff08;拉取代码、编译、打包、启动&#xff09;为用户授予执行shell脚本的权限执行shell脚本 执行过程&#xff1a;Linux服务器&#xff08;编译、打包、启动&#x…

每天一道大厂SQL题【Day21】华泰证券真题实战(三)

每天一道大厂SQL题【Day21】华泰证券真题实战(三) 大家好&#xff0c;我是Maynor。相信大家和我一样&#xff0c;都有一个大厂梦&#xff0c;作为一名资深大数据选手&#xff0c;深知SQL重要性&#xff0c;接下来我准备用100天时间&#xff0c;基于大数据岗面试中的经典SQL题&…

爱智EdgerOS之深入解析爱智云原生产品ECSM

一、云原生简介 近些年来&#xff0c;云原生逐渐被业界认可和接受&#xff0c;在国内&#xff0c;包括政府、金融、通信、能源在内的众多领域的大型机构和企业都实现了不同程度的云化&#xff0c;那么什么是云原生呢&#xff1f;云原生计算基金会提供了官方的定义&#xff1a;…