欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2022-04-09 13:50:07
前言 使用flutter开发一款app是一件非常愉快的事情,其出色的性能、跨多端以及数量众多的原生组件都是我们选择flutter的理由!今天我们就来使用flutter开发...

前言

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

如何使用Flutter开发一款电影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干爆了。

总结

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