RN And FlutterFlutterFlutter跨平台移动开发

从原生开发到Flutter教程(二)新闻列表布局

2018-12-14  本文已影响149人  KavinZhou

上篇文章从原生开发到Flutter教程(一)认识Flutter我们已经大概了解了Flutter的魅力并搭建好了开发环境,终于到了大展身手的时候了。
接下来我们来做一个App,是央视新闻客户端。
带着这个例子,功能挺齐全,相信大家学完这套教程,应对日常的开发应该就不会有大的问题了。但是除了学会写项目,笔者觉得,更重要的是,我们通过这个例子,一起来领略一下Google出品的沥血之作其中的奥妙,体会Google工程师对于一些问题的解决方案的理念,如UI构建、数据流传输、用户交互、数据异步处理等等。话不多说,Let's Get Started.

项目初始化

注意:我的开发工具是VSCode。

项目的初始化很简单,Shift + Cmd + p,选择Flutter: New Project,然后写上项目名称(比如cctv_news),再选择一个放置文件夹即可。
接下来,VSCode会自动初始化项目,等待大概10s即可完成。

了解文件夹构成

项目初始化好后,会看到一堆文件夹和文件,如果之前很少接触Flutter,对这些文件可能会比较陌生。其实很简单,下面我来简述一下文件夹构成。

Flutter文件目录

编码开始之前

万丈高楼平地起,如果想快速上手写Flutter项目,下面几个概念一定要先熟悉一下,磨刀不误砍柴工。

1、Widget

Flutter中,万物皆Widget
如果你了解React、VUE等,这个概念不难理解。如果你从iOS过来的,Widget很像UIView,但是绝对不能等同。Flutter中的Widget非常轻量,他们本身不是什么控件,也不会被直接绘制出什么,他们只是UI的描述,即"声明和构建UI的方法"。一定要理解这个概念,否则后面你会产生类似"App为什么继承自Widget"这样的困惑。

StatelessWidget和StatefulWidget

Widget分为两种,StatelessWidgetStatefulWidget
我们自定义控件大多继承自两者之一。他们的区别是,前者没有state状态的概念,而后者有。

下面以StatefulWidget为例,它有两个类组成,即widget本身和他的状态state。看下面代码实例:

小提示:stl和stf是创建StatelessWidgetStatefulWidget的快捷键。

class Counter extends StatefulWidget {
  Counter({Key key, this.title}) : super(key: key);
  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
    int counter = 0;
    void increaseCount() {
        setState(() {
            this.counter++;
        }
    }

    Widget build(context) {
        return RaisedButton(
            onPressed: increaseCount,
            child: new Text('Tap to Increase'),
        );
    }
}

2、Material 和 Cupertino Widgets

上篇文章也谈到了这两个概念。Flutter之所以可以快速构建精美的页面,离不开这两个内建widget库。前者是安卓原生风格,后者是仿苹果风格。

3、常见控件

下面列一下常见的控件,为后面铺垫。

简单分析页面

先来简单分析一下央视新闻首页。


新闻首页分析

上面的内容比较多,我们先做最简单的iOS中的TableView视图,里面有多个Cell构成。
下面我们开始正式进入代码阶段。
(PS:由于笔者是主栈iOS开发的,所以一些名词术语暂以最熟悉的iOS平台的术语为准)

上文也提到了,我们以后大部分开发时间,都是在lib文件夹下。我们打开这个文件夹,发现里面有个main.dart文件,这是程序的入口文件,稍微懂点编程的都知道其作用。打开这个文件,发现已经存在示例代码,这是Flutter官方写的范例,你可以运行一下看看效果。当然,我们后面开始写代码之前,最好把他们清空,完全从0开始。

开始写代码

实现main.dart

import 'package:flutter/material.dart';

注意,当函数体只有一行代码时,我们可以用胖箭头=>代替花括号,语法更简洁。

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

main.dart完整代码如下:

import 'package:flutter/material.dart';

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

class MainApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'CCTV NRES',
      home: Scaffold(
        appBar: AppBar(
          title: Text('CCTV News'),
        ),
        body: Text('欢迎来到Flutter~'),
      ),
    );
  }
}

运行后会发现,'欢迎来到Flutter~'文字居于左上角,我们现在想将这段文字屏幕居中,跟我们之前原生开始的布局逻辑不同,Flutter是通过Widget来完成布局,关于布局的知识我们会在后面详细讲,这里只需要知道,要想居中控件,我们可以用Center控件包裹一下即可。
修改body属性如下:body: Center(child: Text('Test')),,输入r热加载一下,即可看到文字展示在中间了。

创建卡片视图(TableViewCell)

有了上面的铺垫,我们就可以正式开始写App了。先从最简单的入手,先实现一下新闻详情的列表的Cell的样式。

NewsCell
如上图,Cell里面的内容,是由两大部分构成,Row和Column。再次强调一下,Flutter的布局理念跟原生的Layout的概念完全不一样。形象点比喻就是,Flutter的布局就像装集装箱,先将一堆东西按照想要的规则放在一个盒子里,在将这个盒子按照想要的规则放在更大的盒子里面。好了,我们开始写代码,先创建home文件夹和HomeNewsCell.dart文件,如下图:
-lib/home
-lib/home/HomeNewsCell.dart

进入HomeNewsCell.dart文件中开始写Cell视图。这里我会分析得细一些,后面文章我们就快一些了。下面这样一层一层分析:

Column > Row > Column
注意,这里有几个需要注意的地方:

assets:
 - images/news_image.jpg

其实整个布局比较基础,但是大家要通过这个简单的布局理解Flutter的布局思想,理解透了思想,再复杂的布局,也可以拆解成简单的单元。代码如下:

import 'package:flutter/material.dart';

class HomeNewsCell extends StatelessWidget {
  Widget get _cellContentView {
    return Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        Expanded(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Text(
                '继山东编导艺考联考被曝疑似出现泄题和作弊的情况。江西编导艺考联考也被曝疑似出现泄题和作弊的情况。',
                style: TextStyle(
                  fontSize: 15.0,
                  color: Color(0xff111111),
                ),
                maxLines: 3,
                overflow: TextOverflow.ellipsis,
              ),
              Container(
                width: 50.0,
                height: 20.0,
                margin: EdgeInsets.only(top: 6.0),
                child: ButtonTheme(
                  buttonColor: Color(0xff1C64CF),
                  shape: StadiumBorder(),
                  child: RaisedButton(
                    onPressed: () => print('test'),
                    padding: EdgeInsets.all(2.0),
                    child: Text(
                      '听新闻',
                      style: TextStyle(
                          color: Colors.white,
                          fontSize: 11.0,
                          fontWeight: FontWeight.w300),
                    ),
                  ),
                ),
              ),
            ],
          ),
        ),
        SizedBox(
          width: 10.0,
        ),
        Container(
          height: 85.0,
          width: 115.0,
          margin: EdgeInsets.only(top: 3.0),
          decoration: BoxDecoration(
            color: Colors.green,
            borderRadius: BorderRadius.circular(5.0),
            image: DecorationImage(
              image: AssetImage('images/news_image.jpg'),
              fit: BoxFit.cover,
            ),
          ),
        ),
      ],
    );
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 115.0,
      child: Column(
        children: <Widget>[
          // 内容视图
          Container(
            padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 6.0),
            child: _cellContentView,
          ),
          // 分割线
          Container(
            margin: EdgeInsets.only(top: 4.0),
            color: Color(0xffeaeaea),
            constraints: BoxConstraints.expand(height: 4.0),
          )
        ],
      ),
    );
  }
}

展示HomeNewsCell样式

上面已经写好了Cell的布局,我们现在改写一下main.dart,加载看一下Cell的样式。很简单,先将HomeNewsCell.dart引入进来后,改写body属性成Column,即可完成渲染。

...
body: Column(
  children: <Widget>[
    HomeNewsCell(),
    HomeNewsCell(),
    ],
),
...

flutter run 看一下效果,是不是很惊喜,这么快时间,就写好了横跨iOS/Android平台的代码,效果还不错,如下图:

新闻列表

使用ListView渲染列表

上面我们是把Cell放在了Column里面,但是在实际开发场景中,我们需要放在ListView里面,这样就可以多了很多如下拉刷新、滑动加载、阻尼效果等等的功能。下面我们继续改造main.dart,加入ListView
同上,改写body属性如下:

body: ListView.builder(
  itemCount: 100,
  itemBuilder: (context, index) {
    return HomeNewsCell();
  },
)),

使用ListView.builder很简单,itemCount是cell的个数,相当于iOS中TableViewnumberOfRowsInSectionitemBuilder是一个回调函数,需要外界告知需要渲染的Cell的样式,即相当于iOS中的cellForRowAtIndexPath。还有一些其他的属性,我们后面再介绍。
好了,改造完成后,flutter run一下,可以滑动了,效果还不错,见下图:

新闻滑动列表

总结

本节教程,抛砖引玉,完成了基础的新闻列表页的布局及展示,我们也了解到了Flutter的布局跟原生布局的思想的差异,其实,跟原生布局比起来,Flutter的这种布局方式刚一开始可能会觉得有些笨拙,但是写顺手之后会发现,这种堆积木、集装箱式的布局构建方式,另外配合上Flutter的数据绑定、响应式编程,这种方式写起来更得心应手,水到渠成。
当然,从上面列表页的小例子我们也不难很快就能发现其中的缺憾,这种UI构建方式,稍不注意,就很容易造成代码冗长,各种括号,各种回车等。颇有在写标记语言的的韵味,当然,注意好组件抽象封装隔离,就能在一定程度上很好避免上述问题。

下篇教程,我们会搭建Tab主UI框架,自定义组件,另外,本篇教程使用的是假数据,下篇教程,我们会请求网络真实数据,来体验一下,Dart作为优秀的现代化语言,异步任务处理是怎么样的一番景象。

上一篇下一篇

猜你喜欢

热点阅读