I want to show a modalsheet like this
above the BottomsNavigationBar like so. I have tried this: But then my whole bottomNavigationBar menu becomes unclickable.
My code for this is:
Widget build(BuildContext context) {
final theme = Theme.of(context);
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
return WillPopScope(
onWillPop: () => _willPopCallback(context),
child: Scaffold(
key: _scaffoldKey,...
bottomNavigationBar: BottomNavigationBar(
onTap: (v) {
_scaffoldKey.currentState!.showBottomSheet<Null>(
(BuildContext context){
return GridView.count....
}
}).....
Then this is my original code:
#override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return WillPopScope(
onWillPop: () => _willPopCallback(context),
child: Scaffold(
body: PageView(
controller: _controller,
physics:const NeverScrollableScrollPhysics(),
onPageChanged: (v) => setState(() => _selectedIndex = v),
children: BottomNavigationList.pageList(context),
),
bottomNavigationBar: BottomNavigationBar(
onTap: (v) {
setState(() {
if (v == 3) {
showModalBottomSheet(
...
builder: (BuildContext context){
return GridView.count...
but then it is going ontop of the BottomNavigationBar. Like this:
Is there any way I can have it clipped on top of the BottomNavigationBar like in the first image or a FAB
UPDATE: I have tried the suggested implementation and got this:
Maybe let me try to rephrase: so the first image has 3 rows, the most bottom row is the bottomNavigationBar. When and if you click on it when you are on that selectedIndex of the bottomNav, the other two rows have to show, WITHOUT obscuring the bottomNav. #Yeasin, in your solution there, the purple row has to show when the hamburger menu is pressed, and hide when pressed again that is why I had used the showModalBottomSheet and also tried the showBottomSheet
You can use Stack Or Column with boolean to handle view.
Using Column
class _CustomViewState extends State<CustomView> {
bool _showBottomSheet = false;
#override
Widget build(BuildContext context) {
return Scaffold(body: LayoutBuilder(
builder: (context, constraints) {
return Column(
children: [
Expanded(
child: CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: ElevatedButton(
onPressed: () {
setState(() {
_showBottomSheet = !_showBottomSheet;
});
},
child: Text(
"show btmSheet",
)),
),
],
),
),
if (_showBottomSheet)
SizedBox(
//get single gridWith * mainAxisCount
height: constraints.maxWidth / 4 * 2, //based on your view
child: GridView.count(
crossAxisCount: 4,
physics: NeverScrollableScrollPhysics(),
children: [
...List.generate(
8,
(index) => Container(
color: Colors.pink,
child: Text("$index"),
),
)
],
),
),
Container(
width: constraints.maxWidth,
color: Colors.deepPurple,
height: kToolbarHeight,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
...List.generate(
4,
(index) => ElevatedButton(
onPressed: () {
print("tapped on $index");
},
child: Text("$index"),
),
)
],
),
)
],
);
},
));
}
}
Using Stack
class CustomView extends StatefulWidget {
CustomView({Key? key}) : super(key: key);
#override
_CustomViewState createState() => _CustomViewState();
}
class _CustomViewState extends State<CustomView> {
bool _showBottomSheet = false;
#override
Widget build(BuildContext context) {
return Scaffold(body: LayoutBuilder(
builder: (context, constraints) {
return Stack(
children: [
Align(
alignment: Alignment.center, // based on UI,
child: CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: ElevatedButton(
onPressed: () {
setState(() {
_showBottomSheet = !_showBottomSheet;
});
},
child: Text(
"show btmSheet",
)),
)
],
),
),
Align(
alignment: Alignment.bottomCenter,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (_showBottomSheet)
SizedBox(
height: 100,
child: GridView.count(
crossAxisCount: 4,
physics: NeverScrollableScrollPhysics(),
children: [
...List.generate(
8,
(index) => Container(
color: Colors.pink,
))
],
),
),
Container(
width: constraints.maxWidth,
color: Colors.deepPurple,
height: kToolbarHeight,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
...List.generate(
4,
(index) => ElevatedButton(
onPressed: () {
print("tapped on $index");
},
child: Text("$index"),
),
)
],
),
)
],
),
)
],
);
},
));
}
}
Related
I have a scrolableView with x amount of ExpansionTiles, I need to place the 'Test' button on the bottom of the page, only if a number of tiles are fitting the screen or if they expand within the page, else to push the button off the screen.
import 'package:flutter/material.dart';
class MyHomePage extends StatelessWidget {
MyHomePage({Key? key}) : super(key: key);
List datas = [1, 2, 3, 4, 5, 6];
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Column(
children: [
ListView.builder(
physics: NeverScrollableScrollPhysics(),
itemCount: datas.length,
shrinkWrap: true,
itemBuilder: (context, i) {
return ExpansionTile(
title: Text(
datas[i].toString(),
),
children: List.generate(
datas.length,
(index) {
return Text(datas[index].toString());
},
));
},
),
ElevatedButton(onPressed: () {}, child: Text('Test'))
],
),
),
),
);
}
}
The UX is a little tricky. It will be easy and get better performance using CustomScrollView.
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: CustomScrollView(
slivers: [
SliverList(
delegate: SliverChildListDelegate.fixed(
[
...datas
.map((e) => ExpansionTile(
title: Text(
e.toString(),
),
children: List.generate(
datas.length,
(index) {
return Text(datas[index].toString());
},
)))
.toList(),
],
),
),
SliverFillRemaining(
hasScrollBody: false,
child: Align(
alignment: Alignment.bottomCenter,
child: ElevatedButton(
onPressed: () {},
child: const Text('Test'),
),
),
)
],
)),
);
}
The important thing is using SliverFillRemaining with hasScrollBody: false along with Align child.
More about CustomScrollView.
im working through a scrollable page view somewhat like tic tok and each time i leave the screen the video continuously play in the background, which i dont want, cuz this code ids from a youtube tutorial i dont know how to work around the problem, so please help me check problem for a viable solution
import 'package:app/packageManager/package.dart';
import 'dart:io';
class Reels extends StatefulWidget {
const Reels({Key? key}) : super(key: key);
#override
State<Reels> createState() => _ReelsState();
}
vidPicker(ImageSource src, BuildContext context) async{
final vid = await ImagePicker().pickVideo(source: src);
if(vid != null){
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => VideoUploader(
videoPath: vid.path, videoName:vid.name,
videoFile:File(vid.path)
)));
}
} //To Pick a Video
showDialogueBox(BuildContext, context){
return showDialog(context: context, builder: (context) => SimpleDialog(
children: [
SimpleDialogOption(
onPressed: () => vidPicker(ImageSource.gallery, context),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: const [
Icon(Icons.image),
Text(" Gallery ")
],
),
),
),
SimpleDialogOption(
onPressed: () => Navigator.of(context).pop(),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: const [
Icon(Icons.cancel),
Text(" Cancel ")
],
),
),
)
],
));
} //TO Show Option Like Gallery and Cancel
class _ReelsState extends State<Reels> {
#override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
appBar: AppBar(
automaticallyImplyLeading: false,
elevation: 0,
flexibleSpace: SizedBox(
height: 200,
child: Padding(
padding: const EdgeInsets.all(15.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
IconButton(onPressed: () => showDialogueBox(BuildContext,context),
icon: const Icon(Icons.add),
iconSize: 35,
color: Colors.teal[300],
)
],
),
),
),
backgroundColor: Colors.transparent,
),
body: StreamBuilder(
stream: fireStore.collection("videoReels").snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
// TODO: Put Progress Bar Here
if (!snapshot.hasData) return const Text("Loading...");
return PageView.builder(
itemCount: snapshot.data!.docs.length,
scrollDirection: Axis.vertical,
controller: PageController(viewportFraction: 1, initialPage: 0),
itemBuilder: (context, index){
DocumentSnapshot dataSnapshot = snapshot.data!.docs[index];
return Stack(
alignment: Alignment.bottomCenter,
children: [
VideoPlayerContent(videoUrl: dataSnapshot["videoUrl"]),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(
flex: 3,
child: Container(
height: MediaQuery.of(context).size.height/6,
padding: const EdgeInsets.all(10.0),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
children: [
InkWell(
onTap: () => {},
child: const Icon(Icons.thumb_up, color: Colors.blueAccent,),
),
const SizedBox(width: 30,),
InkWell(
onTap: () => {},
child: const Icon(Icons.favorite, color: Colors.red,),
)
],
),
const SizedBox(width: 20,),
InkWell(
onTap: () => {},
child: Text(" Add Comment here... ", style: TextStyle( color: Colors.grey[500] ),),
)
],
),
const SizedBox(height: 30,),
// Second Row
Padding(
padding: const EdgeInsets.fromLTRB(30.0, 4.0,30.0,4.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
InkWell(
onTap: () => { } ,
child:ClipRRect(
borderRadius: BorderRadius.circular(50.0),
child: CircleAvatar(backgroundImage: NetworkImage(dataSnapshot['avatar']), radius: 25,),
),
),
Text((dataSnapshot["videoUrl"])),
ElevatedButton(onPressed: ()=> {}, child: Text("Subscribe"))
],
),
)
],
),
),
),
// TODO: Remove this later
],
)
],
);
},
);
}
),
);
}
}
import 'package:app/packageManager/package.dart';
class VideoPlayerContent extends StatefulWidget {
final videoUrl;
const VideoPlayerContent({Key? key, required this.videoUrl}) : super(key: key);
#override
State<VideoPlayerContent> createState() => _VideoPlayerContentState();
}
class _VideoPlayerContentState extends State<VideoPlayerContent> {
late VideoPlayerController _videoController;
late Future _initializeVideoPlayer;
#override
void initState(){
_videoController = VideoPlayerController.network(widget.videoUrl);
_initializeVideoPlayer = _videoController.initialize();
_videoController.play();
_videoController.setVolume(1);
_videoController.setLooping(true);
super.initState();
}
#override
void dispose (){
_videoController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: FutureBuilder(
future: _initializeVideoPlayer,
builder: (context, snapshot){
if (snapshot.connectionState == ConnectionState.done){
return VideoPlayer(_videoController);
}else{
return Container(
color: Colors.black,
child: const Center(
child: CircularProgressIndicator(
value: 0.8,
valueColor: AlwaysStoppedAnimation<Color>(Colors.purpleAccent),
),
)
);
}
},
),
);
}
}
Have you tried WidgetsBindingObserver and VisibilityDetector?
If you have not, then it might be handy to you.
For WidgetBindingObserver, simply use it as mixin like this:
class _VideoPlayerContentState extends State<VideoPlayerContent> with WidgetsBindingObserver{
#override
void initState(){
WidgetsBinding.instance?.addObserver(this);
super.initState();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.resumed:
// TODO: Handle this case.
break;
case AppLifecycleState.inactive:
// Do like this in other lifecylestate if required !
_videoController.pause();
// TODO: Handle this case.
break;
case AppLifecycleState.paused:
// TODO: Handle this case.
break;
case AppLifecycleState.detached:
// TODO: Handle this case.
break;
}
super.didChangeAppLifecycleState(state);
}
// rest of the code .....
#override
void dispose(){
WidgetsBinding.instance?.removeObserver(this);
super.dispose();
}
}
Using visibility detector:
simply wrap the video player with this widget like:
VisibilityDetector(
// Must provide key
key: ValueKey<String>('give any string value to represent key'),
onVisibilityChanged: (visibilityInfo) {
// 0 ---> visible, 1 --> not visible
if(visibilityInfo.visibleFraction == 0){
_videoController.pause();
// might need setState over here
}
},
child : VideoPlayer(_videoController)
);
The first section is an automatic page that I want to make in a dialog box with Flutter.
This is the code I used to try it out, but I couldn't get to what I wanted
Please help me with this by showing the pageView and auto indicator in the dialog
import 'package:flutter/material.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final PageController _controller = PageController(initialPage: 0);
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
child: const Text("Show Dialog"),
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) => AlertDialog(
title: const Text('Warning'),
content: PageView(
controller: _controller,
children: [
Container(width: double.infinity, height: double.infinity, color: Colors.yellow),
Container(width: double.infinity, height: double.infinity, color: Colors.red),
Container(width: double.infinity, height: double.infinity, color: Colors.black),
],
),
actionsAlignment: MainAxisAlignment.center,
actions: <Widget>[
Column(
children: [
ElevatedButton(onPressed: () {}, child: const Text("CONTINUE")),
OutlinedButton(onPressed: () {}, child: const Text("NO THANKS"))
],
),
],
),
);
},
),
),
);
}
}
We need to provide size on PageView. Based on your attached image, I am using LayoutBuilder to get the constraints and providing 30% height of the dialog. Use constraints to provide size.
showDialog(
context: context,
builder: (BuildContext context) =>
LayoutBuilder(builder: (context, constraints) {
debugPrint("${constraints.toString()}");
return AlertDialog(
title: const Text('Warning'),
content: Column(
children: [
SizedBox(
height: constraints.maxHeight * .3,
width: constraints.maxWidth,
child: PageView(
controller: _controller,
children: [
Container(color: Colors.yellow),
Container(color: Colors.red),
Container(color: Colors.black),
],
),
),
],
),
actionsAlignment: MainAxisAlignment.center,
actions: <Widget>[
Column(
children: [
ElevatedButton(
onPressed: () {},
child: const Text("CONTINUE")),
OutlinedButton(
onPressed: () {},
child: const Text("NO THANKS"))
],
),
],
);
}));
If you find the content get overflow after adding many widget wrap top column with SingleChildScrollView
showDialog(
context: context,
builder: (BuildContext context) =>
LayoutBuilder(builder: (context, constraints) {
debugPrint("${constraints.toString()}");
return AlertDialog(
title: const Text('Warning'),
content: SingleChildScrollView(
child: Column(
More about LayoutBuilder
I am trying to navigate to a route and this exception happen. Have look at my code and I don't think I have a FAB and Hero inside this route. Is it because when you tap on each List item, it will show a dialog with Gridview on it? Someone care to explain to me how can this exception happened? It doesn't produce any error on user thought, just throw an exception.
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Daftar Dokter")),
body: ListView.separated(
separatorBuilder: (BuildContext context, int i) => Divider(color: Colors.grey[400]),
itemCount: widget.data.length,
itemBuilder: (context, index) {
Doctor doctor = widget.data[index];
return InkWell(
onTap: (){
_buildDialog(context, scheduleService, doctor.doctorId);
},
child: Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: SizedBox(
width: 50,
height: 50,
child: Placeholder(),
),
),
Flexible(
child: SizedBox(
child: ListTile(
title: Text(doctor.name),
subtitle: Text(doctor.specializationName),
),
)
)
],
),
);
}
)
);
}
}
Hope it will solve your issue:
onItem Builder:
itemBuilder: (context, index) {
return Hero(
tag: "itemTag $index",
child: Material(
child: InkWell(
onTap: () {
// _buildDialog(context, scheduleService, doctor.doctorId);
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => Wisgets(
tag: "itemTag $index",
)));
},
child: Row(
children: <Widget>[
Text("Item $index"),
],
),
),
),
);
},
Child Widget for row
class Wisgets extends StatelessWidget {
final String tag;
const Wisgets({Key? key, required this.tag}) : super(key: key);
#override
Widget build(BuildContext context) {
return Hero(
tag: tag,
child: Scaffold(
appBar: AppBar(
title: Text("Child"),
),
body: Column(
children: [
Text("got $tag"),
],
),
),
);
}
}
I would like to include buttons between AppBar and ListView. In the example below, the buttons scroll along with the text. I tried to include the SingleChildScrollView within a Column, but was unsuccessful.
I read that the Column widget does not support scrolling. I already searched a lot, but I didn't find an example similar to what I need.
Can someone help me?
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('A Idade do Lobo'),
elevation: 0.0,
backgroundColor: COLOR_MAIN,
),
body: NotificationListener(
onNotification: (notif) {
if (_hasScroll) {
if (notif is ScrollEndNotification && scrollOn) {
Timer(Duration(seconds: 1), () {
_scroll();
setState(() {
_controlButton();
});
});
}
}
return true;
},
child: SingleChildScrollView(
controller: _scrollController,
child: new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Center(
child: new Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new RaisedButton(
onPressed: _showScrollPickerDialog,
child: Text('Rolagem ${_scrollSpeed}'),
),
new RaisedButton(
onPressed: _showTomPickerDialog,
child: Text('TOM ${_tom}'),
),
],
),
),
new Flexible(
fit: FlexFit.loose,
child: new ListView.builder(
shrinkWrap: true,
itemCount: _songDetails.length,
itemBuilder: (context, index) {
return new Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: new EdgeInsets.all(5.0),
child: new RichText(
text: TextSpan(children: [
new TextSpan(
text: '${_songDetails[index].line}',
style: _getStyle(
_songDetails[index].type,
),
),
]),
),
),
],
);
},
),
),
],
),
),
),
floatingActionButton: _controlButton(),
);
}
}
You can use bottom properly of AppBar to achieve desire UI.
Following example clear your idea.
import 'package:flutter/material.dart';
class DeleteWidget extends StatefulWidget {
const DeleteWidget({Key key}) : super(key: key);
#override
_DeleteWidgetState createState() => _DeleteWidgetState();
}
class _DeleteWidgetState extends State<DeleteWidget> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("your title"),
bottom: PreferredSize(
preferredSize: Size(MediaQuery.of(context).size.width, 40),
child: Center(
child: new Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new RaisedButton(
onPressed: () {},
child: Text('Rolagem '),
),
new RaisedButton(
onPressed: () {},
child: Text('TOM '),
),
],
),
),
),
),
body: Container(
child: ListView.builder(
itemBuilder: (context, int index) {
return Text(index.toString());
},
itemCount: 100,
),
));
}
}