[Перевод] YouTube-канал Challenge Flutter (картинка в картинке)

GitHub Программа перевода самородков Android iOS Flutter

Вызов Flutter Попробуйте воссоздать пользовательский интерфейс или дизайн определенного приложения во Flutter.

В этом задании будет предпринята попытка реализовать домашнюю страницу YouTube и страницу сведений о видео (страницу, на которой фактически воспроизводится видео), включая анимацию.

Эта задача будет немного сложнее, чем мои предыдущие задачи, но результаты лучше.

Начинать

Приложения YouTube включают:

а) Главная страница включает в себя:

  1. В AppBar есть три действия
  2. Пользователь подписывается на видео
  3. Нижняя панель навигации

б) Страница сведений о видео содержит:

  1. Уменьшающийся основной проигрыватель, который позволяет пользователям просматривать информацию о своей подписке (PIP)
  2. Рекомендация пользователя на основе текущего видео

создать проект

Давайте создадим проект Flutter с именем youtube_flutter и удалим весь код по умолчанию, оставив только пустую страницу с панелью приложений по умолчанию.

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(""),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
          ],
        ),
      ),
    );
  }
}

Сделать панель приложений

На AppBar слева есть логотип и название YouTube, а справа — три действия: запись, поиск и открытие профиля.

Воссоздайте AppBar:

appBar: new AppBar(
  backgroundColor: Colors.white,
  title: Row(
    mainAxisSize: MainAxisSize.min,
    children: <Widget>[
      Icon(FontAwesomeIcons.youtube, color: Colors.red,),
      Padding(
        padding: const EdgeInsets.only(left: 8.0),
        child: Text("YouTube", style: TextStyle(color: Colors.black, letterSpacing: -1.0, fontWeight: FontWeight.w700),),
      ),
    ],
  ),
  actions: <Widget>[
    Padding(
      padding: const EdgeInsets.symmetric(horizontal: 12.0),
      child: Icon(Icons.videocam, color: Colors.black54,),
    ),
    Padding(
      padding: const EdgeInsets.symmetric(horizontal: 12.0),
      child: Icon(Icons.search, color: Colors.black54,),
    ),
    Padding(
      padding: const EdgeInsets.symmetric(horizontal: 12.0),
      child: Icon(Icons.account_circle, color: Colors.black54,),
    ),
  ],
),

Вот как выглядит воссозданный AppBar:

Примечание. Для логотипа YouTube я использовалDart pubFontFlutterУдивительная иконка.

Затем создайте нижнюю панель навигации,

Создать нижнюю панель навигации

Нижняя навигация состоит из 5 элементов, и ее очень легко воссоздать во Flutter. Мы используем параметр bottomNavigationBar Scaffold.

bottomNavigationBar: BottomNavigationBar(items: [
  BottomNavigationBarItem(icon: Icon(Icons.home, color: Colors.black54,), title: Text("Home", style: TextStyle(color: Colors.black54),),),
  BottomNavigationBarItem(icon: Icon(FontAwesomeIcons.fire, color: Colors.black54,), title: Text("Home", style: TextStyle(color: Colors.black54),),),
  BottomNavigationBarItem(icon: Icon(Icons.subscriptions, color: Colors.black54,), title: Text("Home", style: TextStyle(color: Colors.black54),),),
  BottomNavigationBarItem(icon: Icon(Icons.email, color: Colors.black54,), title: Text("Home", style: TextStyle(color: Colors.black54),),),
  BottomNavigationBarItem(icon: Icon(Icons.folder, color: Colors.black54,), title: Text("Home", style: TextStyle(color: Colors.black54),),),
], type: BottomNavigationBarType.fixed,),

Примечание. Для более чем 4 элементов нам необходимо указать фиксированный тип BottomNavigationBarType, поскольку тип по умолчанию смещается, чтобы избежать скученности.

оказаться:

Воссоздана нижняя панель навигации YouTube.

Пользователь подписывается на видео

Видео по подписке пользователя — это подробный список рекомендуемых видео. Посмотрим на элементы списка:

Элемент списка состоит из столбца с изображением и столбца с информацией о видео. Строка состоит из изображения, столбца, содержащего заголовок, издатель и кнопки меню.

Чтобы создать список во Flutter, мы можем использовать ListView.builder(). Воссоздайте элемент списка следующим образом:

ListView.builder(
  itemCount: 3,
  itemBuilder: (context, position) {
    return Column(
      children: <Widget>[
        Row(
          children: <Widget>[
            Expanded(child: Image.asset(videos[position].imagePath, fit: BoxFit.cover,)),
          ],
        ),
        Padding(
          padding: const EdgeInsets.all(12.0),
          child: Row(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Expanded(child: Icon(Icons.account_circle, size: 40.0,), flex: 2,),
              Expanded(
                child: Column(
                  children: <Widget>[
                    Padding(
                      padding: const EdgeInsets.only(bottom: 4.0),
                      child: Text(videos[position].title, style: TextStyle(fontSize: 18.0),),
                    ),
                    Text(videos[position].publisher, style: TextStyle(color: Colors.black54),)
                  ],
                  crossAxisAlignment: CrossAxisAlignment.start,
                ),
                flex: 9,
              ),
              Expanded(child: Icon(Icons.more_vert), flex: 1,),
            ],
          ),
        )
      ],
    );
  },
),

Видео здесь — это просто список с информацией о видео, такой как название и издатель.

Вот как выглядит воссозданная домашняя страница:

Наша воссозданная домашняя страница

Теперь мы перейдем к более сложной части — странице сведений о видео.

Создайте страницу сведений о видео

Страница сведений о видео — это страница, которая фактически показывает видео на YouTube. Изюминкой страницы является то, что мы можем уменьшить масштаб видео и продолжить воспроизведение в правом нижнем углу экрана. В этой статье мы сосредоточимся на уменьшении масштаба анимации, а не на воспроизведении видео.

Обратите внимание, что это не специальная страница, а наложение поверх существующего экрана. Поэтому мы будем использовать компонент Stack для наложения экрана.

Итак, сзади будет наша домашняя страница, а вверху будет страница с видео.

Создайте плавающий видеоплеер (картинка в картинке)

Чтобы создать плавающий видеоплеер, который расширяется на весь экран, мы используем LayoutBuilder, чтобы он идеально вписался в экран.

Прежде чем продолжить, давайте определим некоторые значения, размер видеоплеера при сжатии и расширении. Вместо того, чтобы задавать ширину расширенного проигрывателя, мы получаем ее из компоновщика макетов.

var currentAlignment = Alignment.topCenter;

var minVideoHeight = 100.0;
var minVideoWidth = 150.0;

var maxVideoHeight = 200.0;

// 这是一个任意的值,当构建布局时会改变。
var maxVideoWidth = 250.0;

var currentVideoHeight = 200.0;
var currentVideoWidth = 200.0;

bool isInSmallMode = false;

Здесь «маленький режим» относится к уменьшению масштаба видеоплеера.

LayoutBuilder, который создает страницу сведений о видео, можно записать так:

LayoutBuilder(
  builder: (context, constraints) {

    maxVideoWidth = constraints.biggest.width;

    if(!isInSmallMode) {
      currentVideoWidth = maxVideoWidth;
    }

    return Column(
      crossAxisAlignment: CrossAxisAlignment.end,
      children: <Widget>[
        Expanded(
          child: Align(
            child: Padding(
              padding: EdgeInsets.all(isInSmallMode? 8.0 : 0.0),
              child: GestureDetector(
                child: Container(
                  width: currentVideoWidth,
                  height: currentVideoHeight,
                  child: Image.asset(
                    videos[videoIndexSelected].imagePath,
                    fit: BoxFit.cover,),
                  color: Colors.blue,
                ),
                onVerticalDragEnd: (details) {
                  if(details.velocity.pixelsPerSecond.dy > 0) {
                    setState(() {
                      isInSmallMode = true;
                    });
                  }else if (details.velocity.pixelsPerSecond.dy < 0){
                    setState(() {
                    });
                  }
                },
              ),
            ),
            alignment: currentAlignment,
          ),
          flex: 3,
        ),
        currentAlignment == Alignment.topCenter ?
        Expanded(
          flex: 6,
          child: Container(
            child: Column(
              children: <Widget>[
                Row(),
                Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Card(
                    child: Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: Text("Video Recommendation"),
                    ),
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Card(
                    child: Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: Text("Video Recommendation"),
                    ),
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Card(
                    child: Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: Text("Video Recommendation"),
                    ),
                  ),
                )
              ],
            ),
            color: Colors.white,
          ),
        )
            :Container(),
        Row(),
      ],
    );
  },
)

Обратите внимание, как мы получаем максимальную ширину экрана и используем ее вместо использования нашего первого произвольного значения в качестве максимальной ширины экрана.

Мы подключаем GestureDetector для обнаружения свайпов по экрану, чтобы мы могли соответствующим образом сжимать и расширять его. Давайте создадим анимацию.

Добавить анимацию на страницу сведений о видео

Когда мы анимируем, нам нужно иметь дело с двумя вещами:

  1. Переместите видео из правого верхнего угла в правый нижний угол.
  2. Измените размер видео и уменьшите его.

Для этого мы используем два Tween, AlignmentTween и Tween, и создаем две независимые анимации, которые выполняются одновременно.

AnimationController alignmentAnimationController;
Animation alignmentAnimation;

AnimationController videoViewController;
Animation videoViewAnimation;

var currentAlignment = Alignment.topCenter;
@override
void initState() {
  super.initState();

  alignmentAnimationController = AnimationController(vsync: this, duration: Duration(seconds: 1))
    ..addListener(() {
      setState(() {
        currentAlignment = alignmentAnimation.value;
      });
    });
  alignmentAnimation = AlignmentTween(begin: Alignment.topCenter, end: Alignment.bottomRight).animate(CurvedAnimation(parent: alignmentAnimationController, curve: Curves.fastOutSlowIn));

  videoViewController = AnimationController(vsync: this, duration: Duration(seconds: 1))
    ..addListener(() {
      setState(() {
        currentVideoWidth = (maxVideoWidth*videoViewAnimation.value) + (minVideoWidth*(1.0-videoViewAnimation.value));
        currentVideoHeight = (maxVideoHeight*videoViewAnimation.value) + (minVideoHeight*(1.0-videoViewAnimation.value));
      });
    });
  videoViewAnimation = Tween<double>(begin: 1.0, end: 0.0).animate(videoViewController);

}

Когда наш видеоплеер скользят вверх или вниз, он заставит эти анимации.

onVerticalDragEnd: (details) {
  if(details.velocity.pixelsPerSecond.dy > 0) {
    setState(() {
      isInSmallMode = true;
      alignmentAnimationController.forward();
      videoViewController.forward();
    });
  }else if (details.velocity.pixelsPerSecond.dy < 0){
    setState(() {
      alignmentAnimationController.reverse();
      videoViewController.reverse().then((value) {
        setState(() {
          isInSmallMode = false;
        });
      });
    });
  }
},

Вот окончательный результат кода:

Окончательное воссозданное приложение YouTube

Вот видео приложения:

iOS-видео финального приложения

Вот ссылка на GitHub для проекта:GitHub.com/hit even98/вы…

Спасибо, что читали этот флаттер. Не стесняйтесь написать мне о любых приложениях, которые вы хотели бы воссоздать на трепетании. Пожалуйста, дайте звезду, если вам это нравится, увидимся в следующий раз.

не пропустите:

Flutter Challenge: Whatsapp

Flutter Challenge: Twitter

Если вы обнаружите ошибки в переводе или в других областях, требующих доработки, добро пожаловать наПрограмма перевода самородковВы также можете получить соответствующие бонусные баллы за доработку перевода и PR. начало статьиПостоянная ссылка на эту статьюЭто ссылка MarkDown этой статьи на GitHub.


Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,внешний интерфейс,задняя часть,блокчейн,товар,дизайн,искусственный интеллектЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.