如何使用Flutter开发一款电影APP详解

前言

使用Flutter开发一款App是一件非常愉快的事情,其出色的性能、跨多端以及数量众多的原生组件都是我们选择Flutter的理由!今天我们就来使用Flutter开发一款电影类的App,先看下App的截图。

从main.dart开始

在Flutter里main.dart是应用开始的地方:

import 'package:flutter/material.dart';
import 'package:movie/utils/router.dart' as router;

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

class MyApp extends StatelessWidget {
 // This widget is the root of your application.
 @override
 Widget build(BuildContext context) {
  return MaterialApp(
   debugShowCheckedModeBanner: false,
   title: '电影',
   theme: ThemeData(
    primarySwatch: Colors.blue,
   ),
   onGenerateRoute: router.generateRoute,
   initialRoute: '/',
  );
 }
}

一般的,在Flutter中管理路由有两种方式,一种是直接使用Navigator.of(context).push(),这种方式比较适合非常简单的应用,随着应用的不断发展,逻辑越来越多,推荐使用具名路由来管理应用,本文也是使用的这种方式。直接将路由挂在MaterialApp的onGenerateRoute字段上即可,具体的路由定义放在了单独的文件中进行管理utils/router.dart:

import 'package:flutter/material.dart';
import 'package:movie/screens/home.dart';
import 'package:movie/screens/detail.dart';
import 'package:movie/screens/videoPlayer.dart';

Route<dynamic> generateRoute(RouteSettings settings) {
 switch (settings.name) {
  case '/':
   return MaterialPageRoute(builder: (context) => Home());
  case 'detail':
   var arguments = settings.arguments;
   return MaterialPageRoute(
     builder: (context) => MovieDetail(id: arguments));
  case 'video':
   var arguments = settings.arguments;
   return MaterialPageRoute(
     builder: (context) => VideoPage(url: arguments));
  default:
   return MaterialPageRoute(builder: (context) => Home());
 }
}

真是像极了前端的路由定义,先将组件import进来,然后在各自的路由中return即可。

首页

在首页中使用TabBar来展示"正在热映"和"TOP250":

import 'package:flutter/material.dart';
import 'package:movie/screens/hot.dart';

class Home extends StatefulWidget {
 Home({Key key}) : super(key: key);

 _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
 TabController _tabController;

 @override
 void initState() {
  super.initState();
  _tabController = TabController(vsync: this, initialIndex: 0, length: 2);
 }

 @override
 Widget build(BuildContext context) {
  return Scaffold(
   appBar: AppBar(
    title: TabBar(
     controller: _tabController,
     tabs: <Widget>[
      Tab(text: '正在热映'),
      Tab(text: 'TOP250'),
     ],
    ),
   ),
   body: TabBarView(
    controller: _tabController,
    children: <Widget>[
     Hot(),
     Hot(history: true),
    ],
   ),
  );
 }
}

两个页面的布局是一样的,只有数据是不同的,所以我们复用这个页面Hot,传入history参数来代表是否为Top250页面

复用的Hot组件

  • 在这个组件中,通过history字段来区分成两个页面。
  • 在页面initState的生命周期中,请求数据,再进行相应的展示。
  • 下拉刷新的功能是使用的RefreshIndicator组件,在其onRefresh中进行下拉时的逻辑处理。
  • Flutter没有直接提供上拉加载的组件,但是也是很容易实现,通过ListView的controller来做判断即可:当前滚动的位置是否到达最大滚动位置_scrollController.position.pixels == _scrollController.position.maxScrollExtent
  • 为了获得良好的用户体验,Tab来回切换的时候,我们不希望页面重新渲染,Flutter提供了混入类AutomaticKeepAliveClientMixin,重载wantKeepAlive即可,下面是完整的代码:
import 'package:flutter/material.dart';
import 'package:movie/utils/api.dart' as api;
import 'package:movie/widgets/movieItem.dart';

class Hot extends StatefulWidget {
 final bool history;
 Hot({Key key, this.history = false}) : super(key: key);

 _HotState createState() => _HotState();
}

class _HotState extends State<Hot> with AutomaticKeepAliveClientMixin {
 List _movieList = [];
 int start = 0;
 int total = 0;
 ScrollController _scrollController = ScrollController();

 @override
 void initState() {
  super.initState();
  _scrollController.addListener(() {
   if (_scrollController.position.pixels ==
     _scrollController.position.maxScrollExtent) {
    getMore();
   }
  });
  this.query(init: true);
 }

 query({bool init = false}) async {
  Map res = await api.getMovieList(
    history: widget.history, start: init ? 0 : this.start);
  var start = res['start'];
  var total = res['total'];
  var subjects = res['subjects'];
  setState(() {
   if (init) {
    this._movieList = subjects;
   } else {
    this._movieList.addAll(subjects);
   }
   this.start = start + 10;
   this.total = total;
  });
 }

 Future<Null> _onRefresh() async {
  await this.query(init: true);
 }

 getMore() {
  if (start < total) {
   query();
  }
 }

 @override
 bool get wantKeepAlive => true;

 @override
 Widget build(BuildContext context) {
  super.build(context);
  return RefreshIndicator(
   onRefresh: _onRefresh,
   child: ListView.builder(
    controller: _scrollController,
    itemCount: this._movieList.length,
    itemBuilder: (BuildContext context, int index) =>
      MovieItem(data: this._movieList[index]),
   ),
  );
 }
}

电影的详情页面

点击单条电影时使用Navigator.pushNamed(context, 'detail', arguments: data['id']);即可跳转详情页,在详情页中通过id再请求接口获取详情:

import 'package:flutter/material.dart';
import 'package:movie/widgets/detail/detailTop.dart';
import 'package:movie/widgets/detail/rateing.dart';
import 'package:movie/widgets/detail/actors.dart';
import 'package:movie/widgets/detail/photos.dart';
import 'package:movie/widgets/detail/comments.dart';
import 'package:movie/utils/api.dart' as api;

class MovieDetail extends StatefulWidget {
 final id;
 MovieDetail({Key key, this.id}) : super(key: key);

 _MovieDetailState createState() => _MovieDetailState();
}

class _MovieDetailState extends State<MovieDetail> {
 var _data = {};

 @override
 void initState() {
  super.initState();
  this.init();
 }

 init() async {
  var res = await api.getMovieDetail(widget.id);
  setState(() {
   _data = res;
  });
 }

 @override
 Widget build(BuildContext context) {
  return Scaffold(
   body: _data.isEmpty
     ? Center(child: CircularProgressIndicator(),)
     : SafeArea(
       child: Container(
        height: MediaQuery.of(context).size.height,
        width: MediaQuery.of(context).size.width,
        child: ListView(
         scrollDirection: Axis.vertical,
         children: <Widget>[
          MovieDetailTop(data: _data),
          Rate(count: _data['ratings_count'], rating: _data['rating']),
          Container(padding: EdgeInsets.all(10),child: Text(_data['summary'])),
          Actors(directors: _data['directors'], casts: _data['casts']),
          Photos(photos: _data['photos'],),
          Comments(comments: _data['popular_comments']),
         ],
        ),
       ),
      ),
  );
 }
}

在详情页面中,我们封装了一些组件,这样能让项目更加容易阅读和维护,组件的具体实现就不详细介绍了,都是一些常用的原生组件,这些组件分别是:

  • widgets/detail/detailTop.dart 页面顶部的电影概述
  • widgets/detail/rateing.dart 评分组件
  • widgets/detail/actors.dart 演员表
  • widgets/detail/photos.dart 剧照
  • widgets/detail/comments.dart 评论组件

真实数据来自哪里?

应用中的数据都是从豆瓣开发者api中拉取的,分别是,正在热映in_theaters,top250top250和电影详情subject/id三个接口,请求这些接口是需要apikey的,为了大家能方便请求数据,我将apikey上传到了github上,还请大家温柔点,不要将这个apikey干爆了。

源码下载

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。

(0)

相关推荐

  • Flutter下载更新App的方法示例

    1. 说明 iOS 和Android 更新是完全不一样的. iOS 只能跳转到 AppStore,比较好实现 Android则需要下载apk包,由于Android机型较多,这里我们用 dart 连接第三方(这里)的原生 android 下载库. 更新界面和下载更新分开处理的. iOS 没得下载进度这一说,Android 则有. 2. 代码 2.1 iOS 直接采用url_launcher就可以了 if (Platform.isIOS) { final url = "https://itunes.

  • Flutter实现App功能引导页

    App功能介绍页,主要是由介绍app功能的几张图片和当前页指示符组成,如下效果 我们来一步一步实现上面的界面,左右滑动切换显示功能页,这个可以通过PageView来实现,底部的指示符半透明覆盖在PageView上,开发过Android同学知道可以用Framelayout布局来实现,Flutter上也有类似的控件Stack,我们先完成骨架代码 // An highlighted block void main() => runApp(App()); class App extends Statel

  • 如何使用Flutter开发一款电影APP详解

    前言 使用Flutter开发一款App是一件非常愉快的事情,其出色的性能.跨多端以及数量众多的原生组件都是我们选择Flutter的理由!今天我们就来使用Flutter开发一款电影类的App,先看下App的截图. 从main.dart开始 在Flutter里main.dart是应用开始的地方: import 'package:flutter/material.dart'; import 'package:movie/utils/router.dart' as router; void main()

  • 一款高颜值且免费的 SQL 开发工具之Beekeeper Studio详解

    目录 Beekeeper Studio 简介 Beekeeper Studio 安装 Beekeeper Studio 使用教程 连接数据库 文件关联 SQL 编辑器 表格浏览器 快捷键 SQLTools 工具 今天给大家介绍一款简单易用而且美观的免费 SQL 客户端:Beekeeper Studio. Beekeeper Studio 简介 Beekeeper Studio 是一款免费开源的 SQL 开发和数据库管理工具,具有美观高效.简单易用的特点.Beekeeper Studio 基于 V

  • Flutter自动路由插件auto_route使用详解

    目录 一.简介 二.基本使用 2.1 安装插件 2.2 定义路由表 2.3 生成路由 2.4 路由跳转 2.5 处理返回结果 三.路由导航 3.1 嵌套导航 3.2 Tab 导航 3.3 PageView 3.4 声明式导航 四.高级用法 4.1 路由控制器 4.2 Paths 4.2.1 Path Parameters 4.2.2 Inherited Path Parameters 4.2.3 Query Parameters 4.2.4 Redirecting Paths 4.3 路由守护

  • Flutter 中 Dart的Mixin示例详解

    原文在这里.写的不错,推荐各位看原文. 这里补充一下Mixin的定义: 只要一个类是继承自Object的而且没有定义构造方法,那么这个类可以是一个Mixin了.当然,如果你想让mixin的定义更加的清晰,可以使用mixin关键字开头来定义.具体请参考这里 原文截图体会一下风格. 正文 在经典的面向对象编程语言里一定会有常规的类,抽象类和接口.当然,Dart也有它自己的接口,不过那是另外的文章要说的.有的时候阴影里潜伏者另外的野兽:Mixin!这是做什么的,如何使用?我们来一起发现. 没有mixi

  • 微信小程序开发图片拖拽实例详解

    微信小程序开发图片拖拽实例详解 1.编写页面结构:moveimg.wxml <view class="container"> <view class="cnt"> <image class="image-style" src="../uploads/foods.jpg" style="left:{{ballleft}}px;width:{{screenWidth}}px" bi

  • 使用JavaScript开发跨平台的桌面应用详解

    任何可以使用JavaScript来编写的应用,最终会由JavaScript编写.--Atwood定律 Atwood's Law是Jeff Atwood在2007年提出的:"any application that can be written in JavaScript, will eventually be written in JavaScript.".据说,这只是当时开的一个玩笑.不过,这个玩笑似乎渐渐变成了现实.从各种华丽的网页框架,到功能强大的库,到了现在的机器学习,服务器开

  • Android 开发订单流程view实例详解

     Android 开发订单流程view实例详解 先看看最终效果图: 怎么样,效果还是很不错的吧?群里有人说切四张图的.recycleview的.各种的都有啊,但是最简单的就是通过自定义view来实现了-接下来让我们来实现下这个(订单流程view). 首先我们定义好我们的自定义属性: attrs.xml <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleabl

  • 基于vue-cli3多页面开发apicloud应用的教程详解第1/2页

    之前开发项APP项目直接用APICloud+原生js的方式进行编写,整个项目下来发现开发慢,页面代码多且复杂,维护起来相对困难,而且文件大打包之后的APP会比较大,apicloud的框架也不好用,支持部分es67(像let.const.import等es6新特性不支持写的太难受了) 采用vue-cli+APIcloud的方式写解决以上痛点,开发灵活,并且打包之后体积更小速度更快 环境依赖 vue webpack vue-cli3 nodeJS 基本流程 项目开发最好准备两个项目,一个打包APP,

  • 原生微信小程序开发中 redux 的使用详解

    前提 复杂场景中有不少数据需要在多个不同页面间来回使用和修改.但是小程序页面直接的数据通信方式十分的简单.通常情况需要自己维护一个全局的对象来存放共有数据.但是,简单的维护一个共有数据实体,会随着业务逻辑的不断复杂化而变的过分庞大,并且数据的修改往往无法很好的溯源.加之公共数据实体中数据的修改和页面的UI之间没有太好的同步手段,往往需要在页面和对应的数据实体中同时都维护一份相同的数据,操作十分的不方便. 之前使用过Taro以react+redux的结构来开发微信小程序,依托redux整体上可以解

  • .NET 6开发之实现缓存过程详解

    目录 需求 目标 原理与思路 实现 使用原生ResponseCaching实现缓存 使用Marvin.Cache.Headers实现更多缓存功能 一点扩展 总结 参考资料 需求 有的时候为了减少客户端请求相同资源的逻辑重复执行,我们会考虑使用一些缓存的方式,在.NET 6中,我们可以借助框架提供的中间件来实现请求资源的缓存. 目标 实现请求结果的缓存. 原理与思路 对于在.NET6中实现缓存,我们可以使用响应缓存中间件ResponseCaching来实现,同时可以使用Marvin.Cache.H

随机推荐