如何使用Flutter開(kāi)發(fā)一款電影APP詳解
前言
使用Flutter開(kāi)發(fā)一款A(yù)pp是一件非常愉快的事情,其出色的性能、跨多端以及數(shù)量眾多的原生組件都是我們選擇Flutter的理由!今天我們就來(lái)使用Flutter開(kāi)發(fā)一款電影類(lèi)的App,先看下App的截圖。

從main.dart開(kāi)始
在Flutter里main.dart是應(yīng)用開(kāi)始的地方:
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(),這種方式比較適合非常簡(jiǎn)單的應(yīng)用,隨著應(yīng)用的不斷發(fā)展,邏輯越來(lái)越多,推薦使用具名路由來(lái)管理應(yīng)用,本文也是使用的這種方式。直接將路由掛在MaterialApp的onGenerateRoute字段上即可,具體的路由定義放在了單獨(dú)的文件中進(jìn)行管理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進(jìn)來(lái),然后在各自的路由中return即可。
首頁(yè)
在首頁(yè)中使用TabBar來(lái)展示"正在熱映"和"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),
],
),
);
}
}
兩個(gè)頁(yè)面的布局是一樣的,只有數(shù)據(jù)是不同的,所以我們復(fù)用這個(gè)頁(yè)面Hot,傳入history參數(shù)來(lái)代表是否為T(mén)op250頁(yè)面
復(fù)用的Hot組件
- 在這個(gè)組件中,通過(guò)history字段來(lái)區(qū)分成兩個(gè)頁(yè)面。
- 在頁(yè)面initState的生命周期中,請(qǐng)求數(shù)據(jù),再進(jìn)行相應(yīng)的展示。
- 下拉刷新的功能是使用的RefreshIndicator組件,在其onRefresh中進(jìn)行下拉時(shí)的邏輯處理。
- Flutter沒(méi)有直接提供上拉加載的組件,但是也是很容易實(shí)現(xiàn),通過(guò)ListView的controller來(lái)做判斷即可:當(dāng)前滾動(dòng)的位置是否到達(dá)最大滾動(dòng)位置_scrollController.position.pixels == _scrollController.position.maxScrollExtent
- 為了獲得良好的用戶(hù)體驗(yàn),Tab來(lái)回切換的時(shí)候,我們不希望頁(yè)面重新渲染,F(xiàn)lutter提供了混入類(lèi)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]),
),
);
}
}
電影的詳情頁(yè)面
點(diǎn)擊單條電影時(shí)使用Navigator.pushNamed(context, 'detail', arguments: data['id']);即可跳轉(zhuǎn)詳情頁(yè),在詳情頁(yè)中通過(guò)id再請(qǐng)求接口獲取詳情:
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']),
],
),
),
),
);
}
}
在詳情頁(yè)面中,我們封裝了一些組件,這樣能讓項(xiàng)目更加容易閱讀和維護(hù),組件的具體實(shí)現(xiàn)就不詳細(xì)介紹了,都是一些常用的原生組件,這些組件分別是:
- widgets/detail/detailTop.dart 頁(yè)面頂部的電影概述
- widgets/detail/rateing.dart 評(píng)分組件
- widgets/detail/actors.dart 演員表
- widgets/detail/photos.dart 劇照
- widgets/detail/comments.dart 評(píng)論組件
真實(shí)數(shù)據(jù)來(lái)自哪里?
應(yīng)用中的數(shù)據(jù)都是從豆瓣開(kāi)發(fā)者api中拉取的,分別是,正在熱映in_theaters,top250top250和電影詳情subject/id三個(gè)接口,請(qǐng)求這些接口是需要apikey的,為了大家能方便請(qǐng)求數(shù)據(jù),我將apikey上傳到了github上,還請(qǐng)大家溫柔點(diǎn),不要將這個(gè)apikey干爆了。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。
- Android如何在原生App中嵌入Flutter
- Flutter沉浸式狀態(tài)欄/AppBar導(dǎo)航欄/仿咸魚(yú)底部凸起導(dǎo)航欄效果
- Flutter以?xún)煞N方式實(shí)現(xiàn)App主題切換的代碼
- flutter 怎么實(shí)現(xiàn)app整體灰色效果
- Flutter 實(shí)現(xiàn)整個(gè)App變?yōu)榛疑姆椒ㄊ纠?/a>
- Flutter 滾動(dòng)監(jiān)聽(tīng)及實(shí)戰(zhàn)appBar滾動(dòng)漸變的實(shí)現(xiàn)
- Flutter實(shí)現(xiàn)App功能引導(dǎo)頁(yè)
- flutter BottomAppBar實(shí)現(xiàn)不規(guī)則底部導(dǎo)航欄
- Flutter下載更新App的方法示例
- Flutter 如何設(shè)置App的主色調(diào)與字體
相關(guān)文章
如何安裝系統(tǒng)認(rèn)證簽名過(guò)的APK
如果你的App因?yàn)闄?quán)限原因需要設(shè)置 android:sharedUserId="android.uid.system" 那么IDE編譯出的包通常是無(wú)法直接安裝的,查看控制臺(tái)會(huì)發(fā)現(xiàn)報(bào) INSTALL_FAILED_SHARED_USER_INCOMPATIBLE錯(cuò)誤。這是必須的,隨隨便便一個(gè)App聲明一下就可以和系統(tǒng)用戶(hù)共享ID,豈不亂套了?2014-07-07
Android開(kāi)發(fā)人臉識(shí)別登錄功能
這篇文章主要介紹了Android開(kāi)發(fā)人臉識(shí)別登錄功能,這個(gè)很多公司都在使用,非常流行,今天小編給大家從頭到尾做一個(gè)案例分享到腳本之家平臺(tái),需要的朋友參考下吧2019-11-11
Android編程添加快捷方式(Short)到手機(jī)桌面的方法(含添加,刪除及查詢(xún))
這篇文章主要介紹了Android編程添加快捷方式(Short)到手機(jī)桌面的方法,含有針對(duì)桌面快捷方式的添加,刪除及查詢(xún)的操作實(shí)現(xiàn)技巧,需要的朋友可以參考下2016-01-01
Android ListView用EditText實(shí)現(xiàn)搜索功能效果
本篇文章主要介紹了Android ListView用EditText實(shí)現(xiàn)搜索功能效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-03-03
Android使用Websocket實(shí)現(xiàn)聊天室
這篇文章主要為大家詳細(xì)介紹了Android使用Websocket實(shí)現(xiàn)聊天室,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03
Android開(kāi)發(fā)之自定義View(視圖)用法詳解
這篇文章主要介紹了Android開(kāi)發(fā)之自定義View(視圖)用法,結(jié)合實(shí)例形式分析了Android自定義視圖的實(shí)現(xiàn)方法與使用注意事項(xiàng),需要的朋友可以參考下2016-10-10
解決Kotlin 類(lèi)在實(shí)現(xiàn)多個(gè)接口,覆寫(xiě)多個(gè)接口中相同方法沖突的問(wèn)題
這篇文章主要介紹了解決Kotlin 類(lèi)在實(shí)現(xiàn)多個(gè)接口,覆寫(xiě)多個(gè)接口中相同方法沖突的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-03-03
Android Bluetooth藍(lán)牙技術(shù)初體驗(yàn)
這篇文章主要介紹了Android Bluetooth藍(lán)牙技術(shù)初體驗(yàn)的相關(guān)資料,需要的朋友可以參考下2016-02-02

