Использование Flutter для разработки приложений — очень приятное занятие, отличная производительность, мультитерминальность и большое количество нативных компонентов — причины, по которым мы выбираем Flutter! Сегодня мы собираемся использовать Flutter для разработки приложения для фильмов.Давайте сначала посмотрим на скриншоты приложения.
из 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());
}
}
Это действительно похоже на определение маршрутизации внешнего интерфейса: сначала импортируйте компоненты, а затем возвращайте их в соответствующих маршрутах.
титульная страница
Используйте 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.
Многоразовые горячие компоненты
- В этом компоненте поле истории различает две страницы.
- на странице
initState
В жизненном цикле запросите данные, а затем отобразите их соответствующим образом. - Функция раскрывающегося обновления заключается в использовании компонента RefreshIndicator в его
onRefresh
Логическая обработка при вытягивании выполняется в . - Flutter напрямую не предоставляет компоненты загрузки подтягиваний, но его также очень легко реализовать через
ListView
Контроллер можно использовать для оценки того, достигла ли текущая позиция прокрутки максимальную позицию прокрутки._scrollController.position.pixels == _scrollController.position.maxScrollExtent
- Чтобы получить хороший пользовательский опыт, когда вкладка переключается туда и обратно, мы не хотим, чтобы страница повторно отображалась.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-интерфейса разработчика Douban.in_theaters
, топ250top250
и детали фильмаsubject/id
Три интерфейса, требуется запрос этих интерфейсовapikey
Да, для всеобщего удобства при запросе данных я будуapikey
Загружено на github, пожалуйста, не ставьте этоapikey
Высушенные.