It is a great pleasure to develop an App with Flutter. Its excellent performance, versatility, and numerous native components are all reasons why we chose Flutter! Today we are going to use Flutter to develop a movie App. First take a look at the screenshots of Flutter.
From the main dart began
Dart is where the application starts in Flutter:
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: 'movie',
theme: ThemeData(
primarySwatch: Colors.blue,
),
onGenerateRoute: router.generateRoute,
initialRoute: '/',); }}Copy the code
Generally, there are two ways to manage routes in Flutter. One is to directly use navigator.of (context).push(). This method is suitable for very simple applications. The route definition is managed in a separate file utils/router.dart: 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:
returnMaterialPageRoute(builder: (context) => Home()); }}Copy the code
This is exactly like the route definition in the front end, where you import components and then return them in their respective routes.
Home page
Use TabBar on the front page to show “trending” and “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: 'It's on fire.'),
Tab(text: 'TOP250'),
],
),
),
body: TabBarView(
controller: _tabController,
children: <Widget>[
Hot(),
Hot(history: true),,),); }}Copy the code
The layout of the two pages is the same, only the data is different, so we reuse the page Hot and pass the history parameter to indicate whether the page is Top250
Hot components for reuse
- In this component, the two pages are distinguished by the history field.
- On the page
initState
Request data and display it accordingly. - The drop-down refresh function is to use the RefreshIndicator component in its
onRefresh
For the logical processing of the drop down. - Flutter does not directly provide pull-up loading components, but is also easy to implement through
ListView
Controller can make a judgment: whether the current scrolling position has reached the maximum scrolling position_scrollController.position.pixels == _scrollController.position.maxScrollExtent
- In order to obtain good user experience, the Tab to switch back and forth, we don’t want the page to render, provided with class AutomaticKeepAliveClientMixin Flutter, overloading
wantKeepAlive
Here is the complete code:
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]), ), ); }}Copy the code
Movie details page
Click on a single movie using navigator.pushnamed (context, ‘detail’, arguments: data[‘id’]); Can jump to the detail page, in the detail page through the ID request interface to obtain details:
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'[() [() [() [() [() [() }}Copy the code
In the details page, we have encapsulated some components to make the project easier to read and maintain. The specific implementation of the components is not described in detail, but some common native components. These components are:
widgets/detail/detailTop.dart
Movie overview at the top of the pagewidgets/detail/rateing.dart
Grading componentswidgets/detail/actors.dart
The castwidgets/detail/photos.dart
stillwidgets/detail/comments.dart
Comment on the component
Where do the real numbers come from?
The data in the application is pulled from the Douban developer API, which is in in_theaters, top250top250 and subject/ ID. Apikey is required to request these interfaces, so that the data can be easily requested. I uploaded the Apikey to Github. Please be gentle and don’t blow the apikey dry.
A link to the
Source repository blog address nuggets address