Разработать приложение для фильма с трепетом

Flutter

Использование Flutter для разработки приложений — очень приятное занятие, отличная производительность, мультитерминальность и большое количество нативных компонентов — причины, по которым мы выбираем Flutter! Сегодня мы собираемся использовать 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());
  }
}

Это действительно похоже на определение маршрутизации внешнего интерфейса: сначала импортируйте компоненты, а затем возвращайте их в соответствующих маршрутах.

титульная страница

Используйте 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Высушенные.

Ссылки по теме

Репозиторий исходного кода адрес блога Наггетс адрес