前言

使用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干爆了。

源码下载

总结

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

如何使用Flutter开发一款电影APP详解的更多相关文章

  1. 详解如何通过H5(浏览器/WebView/其他)唤起本地app

    这篇文章主要介绍了详解如何通过H5(浏览器/WebView/其他)唤起本地app的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. H5混合开发app如何升级的方法

    本篇文章主要介绍了H5混合开发app如何升级的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  3. html5唤起app的方法

    这篇文章主要介绍了html5唤起app的方法的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  4. xcode – 上传到App Store时进行身份验证

    只需为现有安装/文件夹创建备份,这很重要,因为在(新)安装期间,Transporter将删除以前的安装:现在运行以下命令来更新Transporter:希望这有助于某人.

  5. App store拒绝应用程序在iOs 10上支持IPV6网络

    我收到苹果公司的app拒绝邮件,下面是我们在连接到IPv6网络的Wi-Fi上运行iOS10.0.2的iPad和iPhone上查看了应用中的一个或多个错误.具体来说,应用程序在启动时仍保留在启动屏根据他们的要求,我已经在我的Mac上创建了NAT64网络,并为iPhone5S设备10.0.2os版本共享了互联网,App工作正常,但苹果称其不与IPv6合作任何人都可以确认我需要检查其他什么吗?

  6. ios – 我如何从iPhone中提取IPA以从App Store下载以便我可以在IPA中查看资产?

    我最喜欢的应用程序之一已从应用程序商店中删除,我想因为它没有在太长时间内更新.我有一台旧设备,但没有下载到我的新手机上.如何获得IPA以便我可以查看应用程序包并查看应用程序中的资产?

  7. ios – – [Not A Type _cfTypeID]:发送到解除分配的实例的消息

    我正在使用代码为图像提供不同的效果,如对比度,色调,饱和度等;并使用了appleglImageProcessing代码,我从我的视图跳转到glimgeProcessing,并将结果图像保存到appDelegate文件中的uiimage属性.从Eagle视图返回后,我使用viewDidAppear函数将我的图像视图更改为更新的图像我的代码是我的日志响应是尝试将图像设置为我的imageView时出现问

  8. 在iOS上制作透明视频

    我有两部mp4电影.一个具有实际视频,一个具有alpha值.我的目的是通过将第一部电影与第二部电影合并来获得透明外观,其中第二部电影将成为第一部电影的掩模.我知道如何做到这一点:使用CGImagealpha和混合工具.但我不知道如何获得视频的确切帧.谁能指出我正确的方向?或者有人有另一种想法吗?

  9. iOS扩展:是否需要增加其捆绑版本(CFBundleVersion)?

    我是否必须在我的扩展程序的Info.plist中增加CFBundLeversion以确保它覆盖现有的?或者,如果在主应用程序的Info.plist中这样做就足够了?

  10. ios – navigator.app undefined

    谢谢你的帮助.干杯,米格尔解决方法“navigator.app”对象仅适用于Android.幸运的是,在即将发布的PhoneGap2.3.0版本中你可以做到:做你想做的事.

随机推荐

  1. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Android单选按钮RadioButton的使用详解

    今天小编就为大家分享一篇关于Android单选按钮RadioButton的使用详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

  3. 解决android studio 打包发现generate signed apk 消失不见问题

    这篇文章主要介绍了解决android studio 打包发现generate signed apk 消失不见问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  4. Android 实现自定义圆形listview功能的实例代码

    这篇文章主要介绍了Android 实现自定义圆形listview功能的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. 详解Android studio 动态fragment的用法

    这篇文章主要介绍了Android studio 动态fragment的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. Android用RecyclerView实现图标拖拽排序以及增删管理

    这篇文章主要介绍了Android用RecyclerView实现图标拖拽排序以及增删管理的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

  7. Android notifyDataSetChanged() 动态更新ListView案例详解

    这篇文章主要介绍了Android notifyDataSetChanged() 动态更新ListView案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

  8. Android自定义View实现弹幕效果

    这篇文章主要为大家详细介绍了Android自定义View实现弹幕效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Android自定义View实现跟随手指移动

    这篇文章主要为大家详细介绍了Android自定义View实现跟随手指移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Android实现多点触摸操作

    这篇文章主要介绍了Android实现多点触摸操作,实现图片的放大、缩小和旋转等处理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部