Skip to content

Commit

Permalink
Update README.md
Browse files Browse the repository at this point in the history
  • Loading branch information
xch1029 committed Jul 19, 2019
1 parent ce23d34 commit b0eec23
Showing 1 changed file with 252 additions and 12 deletions.
264 changes: 252 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,259 @@
# movie
> 使用Flutter开发一款App是一件非常愉快的事情,其出色的性能、跨多端以及数量众多的原生组件都是我们选择Flutter的理由!今天我们就来使用Flutter开发一款电影类的App,先看下App的截图。
A new Flutter project.
![App截图](http://qiniu.tbmao.com/flutterfluttermovie.jpg)

## App截图
![](http://qiniu.tbmao.com/flutterfluttermovie.jpg)
### 从main.dart开始
在Flutter里main.dart是应用开始的地方:
``` dart
import 'package:flutter/material.dart';
import 'package:movie/utils/router.dart' as router;
## Getting Started
void main() => runApp(MyApp());
This project is a starting point for a Flutter application.
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`:
``` dart
import 'package:flutter/material.dart';
import 'package:movie/screens/home.dart';
import 'package:movie/screens/detail.dart';
import 'package:movie/screens/videoPlayer.dart';
A few resources to get you started if this is your first Flutter project:
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即可。

- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
### 首页
在首页中使用TabBar来展示"正在热映"和"TOP250":
``` dart
import 'package:flutter/material.dart';
import 'package:movie/screens/hot.dart';
For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
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`即可,下面是完整的代码:
``` dart
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`再请求接口获取详情:
``` dart
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`,top250`top250`和电影详情`subject/id`三个接口,请求这些接口是需要`apikey`的,为了大家能方便请求数据,我将`apikey`上传到了github上,还请大家温柔点,不要将这个`apikey`干爆了。

### 相关链接
[源码仓库](https://github.com/xch1029/movie)
[博客地址](http://jser.tech/2019/07/19/flutter-movie)
[掘金地址](https://juejin.im/post/5d31ea42f265da1bbe5e3ea7)

0 comments on commit b0eec23

Please sign in to comment.