- Оригинальный адрес:Flutter Challenge: YouTube (Picture-In-Picture)
- Оригинальный автор:Deven Joshi
- Перевод с:Программа перевода самородков
- Постоянная ссылка на эту статью:GitHub.com/rare earth/gold-no…
- Переводчик:MeFelixWang
Вызов Flutter Попробуйте воссоздать пользовательский интерфейс или дизайн определенного приложения во Flutter.
В этом задании будет предпринята попытка реализовать домашнюю страницу YouTube и страницу сведений о видео (страницу, на которой фактически воспроизводится видео), включая анимацию.
Эта задача будет немного сложнее, чем мои предыдущие задачи, но результаты лучше.
Начинать
Приложения YouTube включают:
а) Главная страница включает в себя:
- В AppBar есть три действия
- Пользователь подписывается на видео
- Нижняя панель навигации
б) Страница сведений о видео содержит:
- Уменьшающийся основной проигрыватель, который позволяет пользователям просматривать информацию о своей подписке (PIP)
- Рекомендация пользователя на основе текущего видео
создать проект
Давайте создадим проект 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 для обнаружения свайпов по экрану, чтобы мы могли соответствующим образом сжимать и расширять его. Давайте создадим анимацию.
Добавить анимацию на страницу сведений о видео
Когда мы анимируем, нам нужно иметь дело с двумя вещами:
- Переместите видео из правого верхнего угла в правый нижний угол.
- Измените размер видео и уменьшите его.
Для этого мы используем два 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
Вот видео приложения:
- Ссылка на видео на ютубе:youtu.be/dTpZ1BtNy4w
iOS-видео финального приложения
Вот ссылка на GitHub для проекта:GitHub.com/hit even98/вы…
Спасибо, что читали этот флаттер. Не стесняйтесь написать мне о любых приложениях, которые вы хотели бы воссоздать на трепетании. Пожалуйста, дайте звезду, если вам это нравится, увидимся в следующий раз.
не пропустите:
Если вы обнаружите ошибки в переводе или в других областях, требующих доработки, добро пожаловать наПрограмма перевода самородковВы также можете получить соответствующие бонусные баллы за доработку перевода и PR. начало статьиПостоянная ссылка на эту статьюЭто ссылка MarkDown этой статьи на GitHub.
Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,внешний интерфейс,задняя часть,блокчейн,товар,дизайн,искусственный интеллектЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.