#animation #scroll #flutter #appbar
#Анимация #прокрутка #флаттер #панель приложений
Вопрос:
У меня возникли проблемы с анимацией панели приложений, которую я использую SilverAppBar
в своем приложении. Итак, проблема в том, что когда я нахожусь в середине своего списка и прокручиваю вверх, панель приложений не появляется, но она появляется только тогда, когда прокрутка достигает вершины списка элементов. Я уже протестировал snap
параметр и выдал его true
, но результат не тот, который я ожидаю. У меня есть идеи о создании пользовательской анимации для этого, но я не слишком опытен во Flutter, а также, если есть способ добавить параметры или другой виджет, который будет работать в моей ситуации, это было бы здорово.
Фактический код демонстрации, которую я использую:
Widget _search() => Container(
color: Colors.grey[400],
child: SafeArea(
child: Container(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
enabled: false,
style: TextStyle(fontSize: 16, color: Colors.white),
decoration: InputDecoration(
prefix: SizedBox(width: 12),
hintText: "Search",
contentPadding:
EdgeInsets.symmetric(horizontal: 32.0, vertical: 14.0),
border: InputBorder.none,
),
),
),
)),
);
Container _buildBody() {
return Container(
child: new GridView.count(
crossAxisCount: 2,
children: List.generate(100, (index) {
return Center(
child: Text(
'Item $index',
style: Theme.of(context).textTheme.headline,
),
);
}),
));
}
@override
Widget build(BuildContext context) {
return new Scaffold(
resizeToAvoidBottomPadding: false,
body: new NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
new SliverAppBar(
title: Text("Demo",
style: TextStyle(
color: Colors.white,
)),
pinned: false,
floating: true,
forceElevated: innerBoxIsScrolled,
),
];
},
body: new Column(children: <Widget>[
_search(),
new Expanded(child: _buildBody())
])));
}
Результат, который я имею сейчас:
Изображение 1
Результат, который я получил после указания true
параметра snap
: Изображение 2
Множество приложений, таких как WhatsApp, Facebook, LinkedIn… есть эта анимирующая панель приложений. Чтобы подробнее объяснить, чего именно я ожидаю от этой анимирующей панели приложений, я добавил пример Google Play Store, показывающий требуемую анимацию: Пример Play Store
Ответ №1:
У меня была аналогичная проблема с CustomScrollView и SliverAppBar, используя индикатор обновления, в итоге я создал свою собственную панель приложений.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class HomeView extends StatefulWidget {
@override
HomeState createState() => HomeState();
}
class HomeState extends State<HomeView> with SingleTickerProviderStateMixin {
bool _isAppbar = true;
ScrollController _scrollController = new ScrollController();
@override
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.userScrollDirection ==
ScrollDirection.reverse) {
appBarStatus(false);
}
if (_scrollController.position.userScrollDirection ==
ScrollDirection.forward) {
appBarStatus(true);
}
});
}
void appBarStatus(bool status) {
setState(() {
_isAppbar = status;
});
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(kToolbarHeight),
child: AnimatedContainer(
height: _isAppbar ? 55.0 : 0.0,
duration: Duration(milliseconds: 200),
child: CustomAppBar(),
),
),
body: ListView.builder(
controller: _scrollController,
itemCount: 20,
itemBuilder: (BuildContext context, int index) {
return container();
},
),
),
);
}
}
Widget container() {
return Container(
height: 80.0,
color: Colors.pink,
margin: EdgeInsets.all(8.0),
width: 100,
child: Center(
child: Text(
'Container',
style: TextStyle(
fontSize: 18.0,
),
)),
);
}
class CustomAppBar extends StatefulWidget {
@override
AppBarView createState() => new AppBarView();
}
class AppBarView extends State<CustomAppBar> {
@override
Widget build(BuildContext context) {
return AppBar(
backgroundColor: Colors.white,
leading: InkWell(
onTap: () => {},
child: new Padding(
padding: const EdgeInsets.all(8.0),
child: CircleAvatar(
backgroundColor: Colors.white,
child: ClipOval(
child: Image.network(
'https://images.squarespace-cdn.com/content/5aee389b3c3a531e6245ae76/1530965251082-9L40PL9QH6PATNQ93LUK/linkedinPortraits_DwayneBrown08.jpg?format=1000wamp;content-type=image/jpeg'),
),
),
),
),
actions: <Widget>[
IconButton(
alignment: Alignment.centerLeft,
icon: Icon(
Icons.search,
color: Colors.black,
),
onPressed: () {},
),
],
title: Container(
alignment: Alignment.centerLeft,
child: Text("Custom Appbar", style: TextStyle(color: Colors.black),)
),
);
}
}
Ответ №2:
Чтобы заставить эту функциональность работать, вам нужно будет использовать CustomScrollView
виджет вместо NestedScrollView
. Документация Google
Вот рабочий пример:
class MyHomeState extends State<MyHome> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
const SliverAppBar(
pinned: false,
snap: false,
floating: true,
flexibleSpace: FlexibleSpaceBar(
title: Text('Demo'),
),
),
SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200.0,
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
childAspectRatio: 4.0,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
alignment: Alignment.center,
color: Colors.teal[100 * (index % 9)],
child: Text('grid item $index'),
);
},
childCount: 50,
),
),
],
)
);
}
}
Комментарии:
1. Спасибо за ответ. Анимация, которую я хочу, работает отлично, но при этом
CustomScrollView
поле поиска переходит к списку элементов, когда оно достигает вершины. Я хочу, чтобы поле поиска зависало вверху, когда панель приложений исчезает, как, например, в Google Play Store, или просто как в моем реальном примере. Я отредактировал код в вопросе, чтобы добавить две функции_search()
и_buildBody()
.
Ответ №3:
Я смог сделать плавающую панель приложений с панелью вкладок похожей на WhatsApp, используя SliverAppBar с NestedScrollView. Не забудьте добавить floatHeaderSlivers: true в NestedScrollView. Ссылка на пример кода
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: CustomSliverAppbar(),
);
}
}
class CustomSliverAppbar extends StatefulWidget {
@override
_CustomSliverAppbarState createState() => _CustomSliverAppbarState();
}
class _CustomSliverAppbarState extends State<CustomSliverAppbar>
with SingleTickerProviderStateMixin {
TabController _tabController;
@override
void initState() {
_tabController = TabController(
initialIndex: 0,
length: 2,
vsync: this,
);
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: NestedScrollView(
floatHeaderSlivers: true,
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
title: Text(
"WhatsApp type sliver appbar",
),
centerTitle: true,
pinned: true,
floating: true,
bottom: TabBar(
indicatorColor: Colors.black,
labelPadding: const EdgeInsets.only(
bottom: 16,
),
controller: _tabController,
tabs: [
Text("TAB A"),
Text("TAB B"),
]),
),
];
},
body: TabBarView(
controller: _tabController,
children: [
TabA(),
const Center(
child: Text('Display Tab 2',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
),
],
),
),
);
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
}
class TabA extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scrollbar(
child: ListView.separated(
separatorBuilder: (context, child) => Divider(
height: 1,
),
padding: EdgeInsets.all(0.0),
itemCount: 30,
itemBuilder: (context, i) {
return Container(
height: 100,
width: double.infinity,
color: Colors.primaries[Random().nextInt(Colors.primaries.length)],
);
},
),
);
}
}
Комментарии:
1. Спасибо, это действительно полезно и сэкономило время. floatHeaderSlivers: ключевым моментом здесь является true.