黑马程序员Flutter入门教程之观影App开发

使用到的控件及属性记录

项目创建完成后会有main.dart文件,里面有main方法,调用了MyApp控件,在MyApp控件的build方法里MaterialApp控件的home属性中编写页面内容。
跨平台技术比较:
Flutter、ReactNative、Weex、Ionic
Ionic 实际是H5页面外面套了Android的壳子
ReactNative 实际上是桥接方案,通过JS代码封装的IOS以及Android的控件,不用直接使用原生控件。但底层还是使用原生控件进行的布局
Flutter 比RN实现得更为底层,没有使用桥接,直接通过原生的底层框架实现页面效果,渲染效率更强
StatelessWidget  纯展示控件,不需要私有数据的控件
StatefulWidget  需要访问网络,数据需要动态变化的控件
BottomNavigationBar 底部的TabBar,如使用需要将其父节点用DefaultTabController包裹
DefaultTabController  使用需指定TabBar的个数
Scaffold   封装了页面美化效果的控件,一般都做整个页面的相对父节点使用。里面封装了AppBar,Drawer,TabBar,TabBarView等控件
Scaffold   的bottomNavigationBar属性一般传入TabBar,如果控件有些属性无法设置,则在外面包裹一个Container再看是否可以设置
Scaffold   的appBar属性一般传入AppBar ,通过actions属性添加行为按钮点击
ListView   设置固定的垂直列表,使用ListTile即可
Padding值的设置,全部设置EdgeInsets.all(0);单独设置EdgeInsets.only(0)
UserAccountsDrawerHeader控件  侧边栏经常会使用到
CircleAvatar   圆形图片控件,backgroundImage属性中设置网络图片或是本地图片
如果一个控件不能设置颜色、背景等,则设置一个装饰器decoration属性,传入一个BoxDecoration控件,看是否能设置
GestureDetector   给某个控件设置点击事件,onTap点击事件属性,child点击的控件
Divider   分割线控件
构造传参的固定写法:MovieList({Key key, @required this.tabFlag}) : super(key: key);
在状态管理类要想获取到构造传参数据需要使用widget. 去获取
可变状态控件以及其状态管理类也有固定的写法
如果可变状态控件的管理类,有状态需要保存,则在管理类中with AutomaticKeepAliveClientMixin
ListView控件和ListVIew.builder()的区别:ListView只能展示代码写死的内容,不能动态更改,ListView.builder()可以动态更改
ListView.builder()    itemCount属性表示item的个数,itemBuilder属性表示每一个item项
页面路由跳转:Navigator.push(context, MaterialPageRoute(builder: (BuildContext context)
Text控件  文字控件
Image控件  图片控件
Row   横向控件,行的意思
Column  纵向控件,列的意思。crossAxisAlignment横向属性设置,mainAxisAlignment纵向属性设置
Dio网络请求:async  await dio.get();   setState(() {} 在setState方法中进行私有数据的赋值,否则不会更新页面
需要使用类以及第三方库,需要手动在dart文件中导包

示例代码

// main.dart
// 导入控件
import 'package:flutter/material.dart';

// 手动引入另一个dart文件
import "./body.dart";

// 入口函数 运行MyApp这个控件
void main() => runApp(MyApp());

// MyApp控件继承自无状态控件
class MyApp extends StatelessWidget {
  // 每一个控件都要重写 build函数
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: '快播二代',
        theme: ThemeData(
          primarySwatch: Colors.yellow,
        ),
        // flutter里面每一个控件都是一个类,里面可以省略new
        home: new MyTest());
  }
}

class MyTest extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Scaffold中有一些封装好的控件可供调用
    // 使用TabBar 需要同步的使用TabControler
    return DefaultTabController(
        length: 4,
        child: Scaffold(
          // 顶边栏控件
          appBar: AppBar(
              title: Text("电影列表"),
              // 标题居中属性
              centerTitle: true,
              // 右侧行为按钮
              actions: <Widget>[
                IconButton(
                    icon: Icon(Icons.search),
                    // 点击事件
                    onPressed: () {})
              ]),
          // 侧边栏控件
          drawer: Drawer(
              child: ListView(
                  // 取消listview的内padding
                  padding: EdgeInsets.all(0),
                  // listview中的每一个item布局
                  children: <Widget>[
                // 第一个item是一个有头像和背景图片的布局,使用UserAccountsDrawerHeader控件
                UserAccountsDrawerHeader(
                    accountName:
                        Text("ZHR", style: TextStyle(color: Colors.white)),
                    accountEmail: Text("developerzhr@gmail.com",
                        style: TextStyle(color: Colors.white)),
                    currentAccountPicture: CircleAvatar(
                        backgroundImage: NetworkImage(
                            "https://img.zcool.cn/community/01a7f35d9587a1a8012060be0d6d90.jpg@1280w_1l_2o_100sh.jpg")),
                    // 美化当前控件的
                    decoration: BoxDecoration(
                        image: DecorationImage(
                            image: NetworkImage(
                                "https://img.zcool.cn/community/01c4155d9587a2a801211d53c7142f.jpg@1280w_1l_2o_100sh.jpg"),
                            // cover 是fitcenter
                            // fill 是fitxy
                            fit: BoxFit.cover))),
                GestureDetector(
                    onTap: () {
                      print("点击用户反馈");
                    },
                    child: ListTile(
                        title: Text("用户反馈"), trailing: Icon(Icons.feedback))),
                GestureDetector(
                    onTap: () {
                      print("点击系统设置");
                    },
                    child: ListTile(
                        title: Text("系统设置"), trailing: Icon(Icons.settings))),
                GestureDetector(
                    onTap: () {
                      print("点击我要发布");
                    },
                    child: ListTile(
                        title: Text("我要发布"), trailing: Icon(Icons.send))),
                Divider(),
                GestureDetector(
                    onTap: () {
                      print("点击立即退出");
                    },
                    child: ListTile(
                        title: Text("立即退出"), trailing: Icon(Icons.exit_to_app)))
              ])),
          // 底部的TabBar 如果要修改TabBar的属性就需要在TabBar外包一个Container
          bottomNavigationBar: Container(
              color: Colors.yellow,
              // 一般的TabBar高度都为50
              height: 50,
              child: TabBar(
                  // 设置TabBar的高度及字体大小
                  labelStyle: TextStyle(height: 0, fontSize: 10),
                  tabs: <Widget>[
                    Tab(text: "正在热映", icon: Icon(Icons.movie_filter)),
                    Tab(text: "即将上映", icon: Icon(Icons.movie_creation)),
                    Tab(text: "Top250", icon: Icon(Icons.aspect_ratio)),
                    Tab(text: "本地电影", icon: Icon(Icons.local_movies))
                  ])),
          // 内容控件,一般都设置为StatefulWidget
          body: TabBarView(
            children: <Widget>[
              // 构造方法传参
              MovieList(tabFlag: "in_theaters"),
              MovieList(tabFlag: "top250"),
              MovieList(tabFlag: "in_theaters"),
              MovieList(tabFlag: "top250")
            ],
          ),
        ));
  }
}
------------------------------------------------------------------
// body.dart
import 'package:flutter/material.dart';
import './detail.dart';

// 导入网络请求框架
import "package:dio/dio.dart";

// ctrl + alt + L 格式化代码
Dio dio = new Dio();

// 创建一个电影列表控件 是可变状态控件 需要创建一个状态管理类
class MovieList extends StatefulWidget {
  final String tabFlag;

  // 构造函数的固定写法
//  MovieList({Key key}): super(key : key);
  // 带传参的构造函数写法 @required 表示此参数为必传项
  MovieList({Key key, @required this.tabFlag}) : super(key: key);

  // 固定格式和写法
  @override
  _MovieListState createState() {
    return _MovieListState();
  }
}

// 状态管理的的固定格式和写法 如果想要有保存当前状态能力用with keepalive
class _MovieListState extends State<MovieList> with AutomaticKeepAliveClientMixin{

  // 默认显示第一页
  int page = 1;
  // 每一页显示十条数据
  int pageSize = 10;
  // 电影列表数据
  var movieList = [];

  @override
  bool get wantKeepAlive => true;

  // 控件被创建的时候会执行initState此生命周期方法
  @override
  void initState() {
    super.initState();
    // 请求网络获取电影列表数据
    getMovieList();
  }

  @override
  Widget build(BuildContext context) {
    // 如果在状态管理类要使用控件的传参 需要使用widget.来调用私有数据使用
//    return Text("tabFlag:${widget.tabFlag},---${movieList.length}");
    // ListView只能展示代码写死的内容,不能动态更改,ListView.builder可以动态更改
    return ListView.builder(
      itemCount: movieList.length,
      itemBuilder: (BuildContext context, int index) {
        var movieItem = movieList[index];
        // This function has a return type of 'Widget', but doesn't end with a return statement
        // 如果有些属性添加不了,就想着在外面套一层Container
        return GestureDetector(
            onTap: () {
//              print("测试点击事件");
              // 给点击事件添加路由跳转地址
              Navigator.push(context,
                  MaterialPageRoute(builder: (BuildContext context) {
//                return Text("测试点击事件");
                return MovieDetail(
                    id: movieItem["id"], title: movieItem["title"]);
              }));
            },
            child: Container(
                padding: EdgeInsets.all(10),
                decoration: BoxDecoration(
                    border: Border(bottom: BorderSide(color: Colors.black12))),
                // 返回一个行展示数据
                child: Row(
                  children: <Widget>[
                    Image(
                        image: NetworkImage(movieItem["images"]["small"]),
                        width: 130,
                        height: 180,
                        fit: BoxFit.cover),
                    Container(
                        padding: EdgeInsets.only(left: 10),
                        height: 180,
                        child: Column(
                          // 横向属性设置 居左对齐
                          crossAxisAlignment: CrossAxisAlignment.start,
                          // 纵向属性设置 居中对齐
                          mainAxisAlignment: MainAxisAlignment.spaceAround,
                          children: <Widget>[
                            Text("电影名称:${movieItem["title"]}"),
                            Text("上映年份:${movieItem["year"]}年"),
                            // 集合中参数要添加的内容用join插入
                            Text("电影类型:${movieItem["genres"].join("*")}"),
                            Text("豆瓣评分:${movieItem["rating"]["average"]}"),
                            Row(children: <Widget>[
                              Text("主要演员:"),
                              CircleAvatar(
                                  backgroundImage: NetworkImage(
                                      movieItem["casts"][0]["avatars"]
                                          ["small"]))
                            ])
                          ],
                        ))
                  ],
                )));
      },
    );
  }

  // 获取电影列表数据的方法
  getMovieList() async {
    // 模版字符串 两种表现方式${.}和$
    // 偏移量计算公式 (page - 1) * pageSize
    int offSet = (page - 1) * pageSize;
    // 请求网络需要异步 sync和await搭配使用
    var response = await dio.get(
        // http://www.liulongbin.top:3005/api/v2/movie/top250?start=0&count=1
        "http://www.liulongbin.top:3005/api/v2/movie/${widget.tabFlag}?start=$offSet&count=$pageSize");
    // 服务器返回的真正数据
    var data = response.data;
    print("data:${data.toString()}");
    // 网络上请求的数据都需要在setState方法中进行私有数据的赋值,否则页面不会更新
    setState(() {
      // 数据实体中的键需要通过[]进行匹配调用
      // 把subjects 数据赋值给了movieList 其他地方使用直接取出来就可以了
      movieList = data["subjects"];
      pageSize = data["total"];
    });
  }
}
------------------------------------------------------------------
// detail.dart
import 'package:flutter/material.dart';

class MovieDetail extends StatefulWidget {
  final String id;
  final String title;

  // 构造传参,传入点击的item对应的电影id和名称,根据这两个参数去请求不同的详情内容
  MovieDetail({Key key, @required this.id, @required this.title})
      : super(key: key);

  @override
  _MovieDetailState createState() {
    return _MovieDetailState();
  }
}

class _MovieDetailState extends State<MovieDetail> {
  @override
  Widget build(BuildContext context) {
//    return Text("测试点击事件${widget.id},,${widget.title}");
    return Scaffold(
        appBar: AppBar(title: Text(widget.title)),
        body: Center(
            child: Text(
          "电影id:${widget.id}",
          style: TextStyle(fontSize: 22),
        )));
  }
}

上面Demo 点击此处下载

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DreamBackTo

感谢各位金主大大(* _ *)

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值