Постоянный ящик AppBar через все страницы Flutter
Я пытаюсь создать единый ящик, доступный на всех страницах моего приложения. Как заставить его сохраняться на всех этих страницах без необходимости воссоздавать мой пользовательский виджет Drawer в каждом отдельном файле дротика?
Ответы
Ответ 1
Для этого есть несколько разных вариантов. Самое основное - это, надеюсь, то, что вы уже сделали, но я все равно перечислил:
1: Создайте класс для вашего ящика
Ваш виджет должен быть собственным виджетами с государственным статусом или безстоящим. Таким образом, вам просто нужно создавать экземпляр каждого раза.
class MyDrawer extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Drawer(...);
}
}
А затем, когда вы используете его на каждой странице:
Scaffold(
drawer: MyDrawer(...),
...
)
Надеюсь, вы уже это делаете; если нет, то ты должен быть. Функция построения класса не должна быть слишком большой или может привести к плохой производительности и сложнее поддерживать код; разделение вещей на логические единицы поможет вам в долгосрочной перспективе.
2: Создайте класс для вашего леса
Если включить один и тот же ящик в эшафот для каждой страницы, все еще слишком много кода, вы можете вместо этого использовать класс, который инкапсулирует ваш эшафот. Он будет по существу принимать входные данные для каждого из входов, которые вы фактически используете.
class MyScaffold extends StatelessWidget {
final Widget body;
MyScaffold({this.body});
@override
Widget build(BuildContext context) {
return Scaffold(
body: body,
drawer: MyDrawer(...),
);
}
}
А затем вместо использования Scaffold в вашем коде, используйте MyScaffold (но, пожалуйста, назовите его что-то лучше = D).
3: Многоуровневый эшафот
Я только включаю этот способ сделать это полным, и я не рекомендую его. При этом есть определенные вещи, которые вы не можете заставить работать в нормальном рабочем потоке флаттера, который вы могли бы сделать, выполнив это - например, если вы хотите создать пользовательскую анимацию, когда пользователь нажимает на разные элементы в ящике.
В принципе, то, что вы бы сделали в этом случае, - это иметь Scaffold вне вашего MaterialApp или Navigator (что, я считаю, также означает, что вам придется иметь другой навигатор вне этого, но я не уверен на 100%). У вас был бы эшафот, который за пределами вашей навигации показывает ящик, в то время как другой (на каждой странице в навигации) будет делать все, что вам нужно для этого. Там несколько предостережений - вам нужно будет убедиться, что вы получите правильный эшафот (то есть Scaffold.of(context)
сам по себе не сократит его - вам нужно будет получить контекст первого эшафота и использовать его для поиска на более высоком уровне), и вам, вероятно, нужно будет передать GlobalKey (леса нижнего уровня) в ящик, чтобы он мог реально изменять страницы внутри него.
Как я уже сказал, я не рекомендую этот подход, поэтому я не собираюсь вдаваться в подробности, а не оставлять его как упражнение для читателя, если они хотят спуститься в эту кроличью нору!
Ответ 2
rmtmckenzie является очень правильным.
Хотя, если вам интересно о решении с несколькими эшафорами, это может быть более элегантным, чем вы думаете.
Чтобы разделить ящик между всеми страницами, мы могли бы добавить builder
в наш экземпляр MaterialApp
. Это создаст экземпляр Scaffold
под Navigator
но прежде всего маршруты.
MaterialApp(
title: 'Flutter Demo',
builder: (context, child) {
return Scaffold(
drawer: MyDrawer(),
body: child,
);
},
home: MyHome()
)
Внутри вашей страницы вы можете создать экземпляр другого Scaffold
без ограничений, как обычно.
Затем вы можете показать общий ящик, выполнив следующее в любом виджете в MaterialApp
:
final ScaffoldState scaffoldState = context.rootAncestorStateOfType(TypeMatcher<ScaffoldState>());
scaffoldState.openDrawer();
Код, который вы можете извлечь в хороший помощник:
class RootScaffold {
static openDrawer(BuildContext context) {
final ScaffoldState scaffoldState =
context.rootAncestorStateOfType(TypeMatcher<ScaffoldState>());
scaffoldState.openDrawer();
}
}
Затем повторное использование с использованием RootScaffold.openDrawer(context)
Ответ 3
В дополнение к @Rémi Rousselet ответ
MaterialApp(
title: 'Flutter Demo',
builder: (context, child) {
return Scaffold(
drawer: MyDrawer(),
body: child,
);
},
home: MyHome()
)
Для навигации в корневом ящике, если вы используете Navigator.of(context) // push or pop
, который выдаст ошибку, и для этого вы должны использовать дочерний виджет для перехода на разные страницы
Вот так
(child.key as GlobalKey<NavigatorState>).currentState // push or pop
Демонстрационный проект в Github
Ответ 4
если кто-то ищет модные вещи во время навигации, посмотрите здесь. В качестве ящика для своего проекта я использую пакет flutter_inner_drawer.
Я создал класс с состоянием с именем CustomDrawer.
class CustomDrawer extends StatefulWidget {
final Widget scaffold;
final GlobalKey<InnerDrawerState> innerDrawerKey;
CustomDrawer({
Key key,
this.scaffold,
this.innerDrawerKey,
}) : super(key: key);
@override
_CustomDrawerState createState() => _CustomDrawerState();
}
class _CustomDrawerState extends State<CustomDrawer> {
MainPageIcons assets = MainPageIcons();//From my actual code dont care it
final vars = GlobalVars.shared; //From my actual code dont care it
@override
Widget build(BuildContext context) {
return InnerDrawer(
key: widget.innerDrawerKey,
onTapClose: true, // default false
tapScaffoldEnabled: true,
swipe: true, // default true
colorTransition: Colors.teal, // default Color.black54
//innerDrawerCallback: (a) => print(a ),// return bool
leftOffset: 0.2, // default 0.4
leftScale: 1,// default 1
boxShadow: [
BoxShadow(color: Colors.teal,blurRadius: 20.0, // has the effect of softening the shadow
spreadRadius: 10.0, // has the effect of extending the shadow
offset: Offset(
10.0, // horizontal, move right 10
10.0, // vertical, move down 10
),)],
borderRadius: 20, // default 0
leftAnimationType: InnerDrawerAnimation.quadratic, // default static
//when a pointer that is in contact with the screen and moves to the right or left
onDragUpdate: (double val, InnerDrawerDirection direction) =>
setState(() => _dragUpdate = val),
//innerDrawerCallback: (a) => print(a),
// innerDrawerCallback: (a) => print(a), // return true (open) or false (close)
leftChild: menus(), // required if rightChild is not set
scaffold:widget.scaffold
);
}
double _dragUpdate = 0;
Widget menus(){
return
Material(
child: Stack(
children: <Widget>[
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomLeft,
colors: [
ColorTween(
begin: Colors.blueAccent,
end: Colors.blueGrey[400].withRed(100),
).lerp(_dragUpdate),
ColorTween(
begin: Colors.green,
end: Colors.blueGrey[800].withGreen(80),
).lerp(_dragUpdate),
],
),
),
child: Stack(
children: <Widget>[
Padding(
padding: EdgeInsets.only(left: 30),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Column(
children: <Widget>[
Container(
margin: EdgeInsets.only(left: 10, bottom: 15),
width: 80,
child: ClipRRect(
child: Image.network(
"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSrWfWLnxIT5TnuE-JViLzLuro9IID2d7QEc2sRPTRoGWpgJV75",
),
borderRadius: BorderRadius.circular(60),
),
),
Text(
"User",
style: TextStyle(color: Colors.white, fontSize: 18),
)
],
//mainAxisAlignment: MainAxisAlignment.center,
),
Padding(
padding: EdgeInsets.all(10),
),
ListTile(
onTap: ()=>navigate(Profile.tag),
title: Text(
"Profile",
style: TextStyle(color: Colors.white, fontSize: 14),
),
leading: Icon(
Icons.dashboard,
color: Colors.white,
size: 22,
),
),
ListTile(
title: Text(
"Camera",
style: TextStyle(fontSize: 14,color:Colors.white),
),
leading: Icon(
Icons.camera,
size: 22,
color: Colors.white,
),
onTap: ()=>navigate(Camera.tag)
),
ListTile(
title: Text(
"Pharmacies",
style: TextStyle(fontSize: 14,color:Colors.white),
),
leading: Icon(
Icons.add_to_photos,
size: 22,
color: Colors.white,
),
onTap: ()=>navigate(Pharmacies.tag)
),
],
),
),
Positioned(
bottom: 20,
child: Container(
alignment: Alignment.bottomLeft,
margin: EdgeInsets.only(top: 50),
padding: EdgeInsets.symmetric(vertical: 15, horizontal: 25),
width: double.maxFinite,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Icon(
Icons.all_out,
size: 18,
color: Colors.grey,
),
Text(
" LogOut",
style: TextStyle(
fontSize: 16,
color: Colors.grey,
),
),
],
),
),
)
],
),
),
_dragUpdate < 1
? BackdropFilter(
filter: ImageFilter.blur(
sigmaX: (10 - _dragUpdate * 10),
sigmaY: (10 - _dragUpdate * 10)),
child: Container(
decoration: BoxDecoration(
color: Colors.black.withOpacity(0),
),
),
)
: null,
].where((a) => a != null).toList(),
));
}
navigate(String route) async{
await navigatorKey.currentState.pushNamed(route).then((_){
Timer(Duration(milliseconds: 500),()=>widget.innerDrawerKey.currentState.toggle() );
});
}
}
Я скопировал пример из упаковки и не особо трогал оригинал. добавлена только функция для переключения после поворота назад.
navigate(String route) async{
await navigatorKey.currentState.pushNamed(route).then((_){
Timer(Duration(milliseconds: 500),()=>widget.innerDrawerKey.currentState.toggle() );
});
}
для навигации по всем страницам, добавленным в GlobalKey, чтобы их можно было найти в любом классе
final GlobalKey<NavigatorState> navigatorKey = GlobalKey(debugLabel: "Main Navigator");
inner_drawer также нужен глобальный ключ для переключения состояния, но если вы создаете только один при переходе между страницами, это приводит к дублированию ошибки глобального ключа. чтобы избежать, я создал глобальную переменную с именем innerKeys
Map<String,GlobalKey<InnerDrawerState>>innerKeys={
'main':GlobalKey<InnerDrawerState>(),
'profile':GlobalKey<InnerDrawerState>(),
'pharmacies':GlobalKey<InnerDrawerState>(),
};
наконец я добавил этот CustomDrawer на каждую страницу
@override
Widget build(BuildContext context) {
return CustomDrawer(
innerDrawerKey: vars.innerKeys['profile'],
scaffold:Scaffold(
appBar: CustomAppBar(
title: 'Profile',
actions: <Widget>[
],),
body: Stack(
children: <Widget>[
Background(),
])));
}
Я надеюсь, что это поможет кому-то.
ПРИМЕЧАНИЕ: пожалуйста, проверьте оригинальную упаковку флаттера, если что-то обновлено. Имейте в виду, что этот пример не идеален и должен позаботиться о том, чтобы, если много навигации по этому ящику, то дерево виджетов будет иметь много страниц, и это повлияет на производительность. Любое предложение по настройке будет оценено.