I have implemented TabBar without TabBarView. I am using a single ListView as body since the layout after selecting a tab is same for all tabs.
What I want to achieve is, change the tab while swiping left / right in the listview. How can I do this?
TabBar
TabBar(
indicatorWeight: 3,
indicatorSize: TabBarIndicatorSize.label,
onTap: (index) {
categoryId = newsProvider.categories[index].id;
page = 1;
fetchPosts(newsProvider);
},
isScrollable: true,
tabs: [
for (Category category in newsProvider.categories)
Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15),
child: Text(
category.name,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
),
),
],
),
body
ListView.builder(
padding: EdgeInsets.only(bottom: 60),
physics: BouncingScrollPhysics(),
controller: _scrollController,
itemCount: newsProvider.posts.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) =>
HabaruDetails(newsProvider.posts[index]),
));
},
child: Container(
height: 200,
margin: EdgeInsets.only(left: 10, right: 10, top: 10),
child: ClipRRect(
borderRadius: BorderRadius.circular(7),
child: Stack(
children: [
Positioned.fill(
child: Hero(
tag: newsProvider.posts[index].id,
child: FadeInImage.memoryNetwork(
placeholder: kTransparentImage,
fit: BoxFit.cover,
image: newsProvider
.posts[index].betterFeaturedImage.mediumLarge),
),
),
Container(
height: 200,
decoration: BoxDecoration(
color: Colors.white,
gradient: LinearGradient(
begin: FractionalOffset.topCenter,
end: FractionalOffset.bottomCenter,
colors: [
Colors.black.withOpacity(0.0),
Colors.black.withOpacity(0.95),
],
stops: [
0.0,
1.0
])),
),
Positioned.fill(
child: Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 30),
child: Text(
newsProvider.posts[index].title.rendered,
textAlign: TextAlign.center,
textDirection: TextDirection.rtl,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
height: 1.7,
),
),
),
),
),
],
),
),
),
);
},
)
I think you still must use a TabBarView, but you can generate its children dynamically based on the categories list like below.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
tabs: [...],
),
),
body: TabBarView(
children: newsProvider.categories.map(
(e) => ListView.builder(...).toList(),
),
),
),
),
);
}
From what I can tell, I think you're trying to create a tab bar that moves when the user scrolls down on the listview? If so, I have created an example of a class that uses a selection of tabs you can implement as a TabBar for your title in your AppBar whose controller is set to the TabBarController I have created above the build method. The listview is then set to the controller of the scrollcontroller which listens to the state of the tabbarcontroller.
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: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
TabController _tabController;
ScrollController _scrollController;
void _scrollListener() {
var index = (_scrollController.offset / 70).round();
if(index >= choices.length){
index = choices.lastIndexOf(choices.last);
}
if(index <= 0){
index = 0;
}
if (index == choices.length)
index = choices.length;
if(mounted){
setState(() {
_tabController.animateTo(index, duration: Duration(milliseconds: 300), curve: Curves.easeIn);
});
}
}
#override
void initState() {
super.initState();
_tabController = TabController(vsync: this, length: choices.length);
_scrollController = ScrollController();
_scrollController.addListener(_scrollListener);
}
#override
void dispose() {
super.dispose();
_tabController.dispose();
_scrollController.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: TabBar(
labelColor: Colors.black,
unselectedLabelColor: Colors.black45,
labelStyle: TextStyle(fontWeight: FontWeight.w700, fontFamily: 'Valera'),
controller: _tabController,
isScrollable: true,
indicator: BoxDecoration(
borderRadius: BorderRadius.circular(50),
color: Colors.transparent),
onTap: (int index){
setState(() {
_scrollController.animateTo(index*80.0, duration: Duration(milliseconds: 300), curve: Curves.easeIn);
});
},
tabs: choices.map((Choice choice) {
return Padding(
padding: const EdgeInsets.only(top: 20.0),
child: Tab(
text: choice.title,
),
);
}).toList(),
),
),
body: Container(
child: ListView.builder(
controller: _scrollController,
itemBuilder: (context, index){
return ListTile(
);
}),
),
);
}
}
class Choice {
const Choice({this.title});
final String title;
}
const List<Choice> choices = const <Choice>[
const Choice(title: 'First Tab'),
const Choice(title: 'Second Tab'),
const Choice(title: 'Third Tab'),
const Choice(title: 'Fourth Tab'),
const Choice(title: 'Fifth Tab'),
const Choice(title: 'Sixth Tab'),
];
class ChoiceCard extends StatelessWidget {
const ChoiceCard({Key key, this.choice}) : super(key: key);
final Choice choice;
#override
Widget build(BuildContext context) {
return Text(choice.title, style: TextStyle(
color: Colors.black,
),
);
}
}
You can do this by TabController and Listener
First you can define a TabController. It need to be in StatefulWidget.
TabController _tabController;
#override
void initState() {
super.initState();
_tabController = TabController(length: tabLenght, vsync: this);
}
TabBar(
...
controller: _tabController,
...
)
Then you can add Listener class to the ListView. Save the clicked position and swiping distance in onPointerDown,onPointerMove,onPointerUp and
onPointerCancel (same as onPointerUp).
Change the tab by control the offset and index. Notice offset is in range (-1.0,1.0). You should animate by yourself if it reach the next index.
double startX;
double sensitivity = 0.01;
...
child: Listener(
onPointerDown: (event) {
startX = event.position.dx;
},
onPointerMove: (event) {
double newX = event.position.dx;
double offset = ((startX - newX) * sensitivity).clamp(-1.0, 1.0);
if (!_tabController.indexIsChanging)
_tabController.offset = offset;
if (offset == 1.0 &&
_tabController.index < _tabController.length - 1) {
_tabController.animateTo(_tabController.index + 1);
startX = newX;
}
if (offset == -1.0 && _tabController.index > 0) {
_tabController.animateTo(_tabController.index - 1);
startX = newX;
}
},
onPointerUp: (_) {
if (_tabController.offset > 0.5 &&
_tabController.index < _tabController.length - 1)
_tabController.animateTo(_tabController.index + 1);
else if (_tabController.offset < -0.5 && _tabController.index > 0)
_tabController.animateTo(_tabController.index - 1);
else {
if (!_tabController.indexIsChanging)
_tabController.offset = 0;
}
},
onPointerCancel: (_) {
if (_tabController.offset > 0.5 &&
_tabController.index < _tabController.length - 1)
_tabController.animateTo(_tabController.index + 1);
else if (_tabController.offset < -0.5 && _tabController.index > 0)
_tabController.animateTo(_tabController.index - 1);
else {
if (!_tabController.indexIsChanging)
_tabController.offset = 0;
}
},
child: ListView.builder(
...
I think you want the drag effect like TabBarView? You can also check the source code inside TabBarView.
Related
I can render either the horizontal listview or the vertical list view on the screen, but not both successfully in a column. This layout is common on many ecommerce sites. where the user select a genre from a horizontal list and a list of movies display vertically
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:stackoverflow_pkg/stackoverflow_pkg.dart';
void main() {
runApp(const MyApp());
}
class MyCustomScrollBehavior extends MaterialScrollBehavior {
// Override behavior methods and getters like dragDevices
#override
Set<PointerDeviceKind> get dragDevices => {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
};
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
scrollBehavior: MyCustomScrollBehavior(),
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: TestHorizontalListView(),
);
}
}
class TestHorizontalListView extends StatefulWidget {
TestHorizontalListView({Key? key}) : super(key: key);
#override
State<TestHorizontalListView> createState() => _TestHorizontalListViewState();
}
class _TestHorizontalListViewState extends State<TestHorizontalListView> {
List<String> lstData=['A','B','C','D','E','F','G'];
final ScrollController _horizontal_scrollcontroller = ScrollController();
final ScrollController _vertical_scrollcontroller=ScrollController();
/*_buildCard(String value)
{
return Expanded(child:Container(
margin: const EdgeInsets.symmetric(vertical: 20.0),
width:300,height:400,child:Card(child: Expanded(child:Text(value,textAlign: TextAlign.center, style:TextStyle(fontSize:30))),)));
}
*/
void _scrollRight() {
_horizontal_scrollcontroller.animateTo(
_horizontal_scrollcontroller.position.maxScrollExtent,
duration: Duration(seconds: 1),
curve: Curves.fastOutSlowIn,
);
}
void _scrollLeft() {
_horizontal_scrollcontroller.animateTo(
0,
duration: Duration(seconds: 1),
curve: Curves.fastOutSlowIn,
);
}
_segment1()
{
return
Expanded(child:SingleChildScrollView(child:
Expanded(child:
Container(height:300,
width:MediaQuery.of(context).size.width,
child:Row(children: [
FloatingActionButton.small(
onPressed: _scrollRight, child: const Icon(Icons.arrow_right),),
Expanded(child:Scrollbar(child:ListView.builder(
itemCount: lstData.length,
controller: _horizontal_scrollcontroller,
scrollDirection: Axis.horizontal,
itemBuilder:(context,index)
{
return CardFncs.getSimpleCard(lstData[index]);
})
,),
),
FloatingActionButton.small(onPressed: _scrollLeft, child: const
Icon(Icons.arrow_left),),
]))
,
)
));
}
_segment2()
{
return
SizedBox(
height:500,
child: ListView.builder(
controller: _vertical_scrollcontroller,
itemCount: lstData.length,itemBuilder:(context, index) {
return
Container(width:350,
height:300,
margin: const EdgeInsets.symmetric(vertical: 20.0),
child:ListTile(
title:Text(lstData[index])
));
}));
}
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: Text("horizontal listview",)),body:
Column(children:[
Row(
children: [
Container(
width: 300,
child: TextFormField(
decoration: const InputDecoration(
labelText:"Input",
enabledBorder: UnderlineInputBorder(borderSide:BorderSide(color:Colors.red),
borderRadius: BorderRadius.all(Radius.elliptical(5,10))
),
)))]),
_segment1(),
_segment2(),
])
);
}
}
class CardFncs
{
static getSimpleCard(String value)
{
return Expanded(child:Container(
margin: const EdgeInsets.symmetric(vertical: 20.0),
width:300,height:400,child:Card(child: Expanded(child:Text(value,textAlign:
TextAlign.center, style:TextStyle(fontSize:30))),)));
}
}
Code update, remove the Expanded inside inside SingleChildScrollView, just the first one bellow it:
class TestHorizontalListView extends StatefulWidget {
TestHorizontalListView({Key? key}) : super(key: key);
#override
State<TestHorizontalListView> createState() => _TestHorizontalListViewState();
}
class _TestHorizontalListViewState extends State<TestHorizontalListView> {
List<String> lstData = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'P', 'U', 'V', 'W'];
final ScrollController _horizontal_scrollcontroller = ScrollController();
final ScrollController _vertical_scrollcontroller = ScrollController();
/*_buildCard(String value)
{
return Expanded(child:Container(
margin: const EdgeInsets.symmetric(vertical: 20.0),
width:300,height:400,child:Card(child: Expanded(child:Text(value,textAlign: TextAlign.center, style:TextStyle(fontSize:30))),)));
}
*/
void _scrollRight() {
_horizontal_scrollcontroller.animateTo(
_horizontal_scrollcontroller.position.maxScrollExtent,
duration: Duration(seconds: 1),
curve: Curves.fastOutSlowIn,
);
}
void _scrollLeft() {
_horizontal_scrollcontroller.animateTo(
0,
duration: Duration(seconds: 1),
curve: Curves.fastOutSlowIn,
);
}
_segment1() {
return Expanded(
flex: 1,
child: SingleChildScrollView(
child: Container(
height: 150,
width: MediaQuery.of(context).size.width,
child: Row(children: [
FloatingActionButton.small(
onPressed: _scrollRight,
child: const Icon(Icons.arrow_right),
),
Expanded(
child: Scrollbar(
child: ListView.builder(
itemCount: lstData.length,
controller: _horizontal_scrollcontroller,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
return Container(
margin: const EdgeInsets.symmetric(vertical: 20.0),
width: 100,
height: 100,
child: Card(
child: Text(lstData[index], textAlign: TextAlign.center, style: const TextStyle(fontSize: 30)),
));
}),
),
),
FloatingActionButton.small(
onPressed: _scrollLeft,
child: const Icon(Icons.arrow_left),
),
]))));
}
_segment2() {
return Expanded(
// height: 480,
flex: 3,
child: ListView.builder(
controller: _vertical_scrollcontroller,
itemCount: lstData.length,
itemBuilder: (context, index) {
return Container(width: 150, height: 100, margin: const EdgeInsets.symmetric(vertical: 20.0), child: ListTile(title: Text(lstData[index])));
}));
}
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
"horizontal listview",
)
),
resizeToAvoidBottomInset: false,
body: Column(children: [
Expanded(child:Row(children: [
Container(
width: 300,
child: TextFormField(
decoration: const InputDecoration(
labelText: "Input",
enabledBorder: UnderlineInputBorder(borderSide: BorderSide(color: Colors.red), borderRadius: BorderRadius.all(Radius.elliptical(5, 10))),
)))
])),
_segment1(),
_segment2(),
]
)
);
}
}
I want to use sliver app bar with tab bar and grid view.
I tried this code:
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxScrolled) {
return <Widget>[
SliverAppBar(
backgroundColor: Colors.white,
centerTitle: true,
pinned: true,
snap: false,
floating: true,
title: Image(
image: AssetImage('assets/images/appbar_logo.png'),
width: 152,
height: 42,
),
bottom: PreferredSize(
preferredSize: Size.fromHeight(48),
child: TabBar(
isScrollable: true,
automaticIndicatorColorAdjustment: false,
indicatorSize: TabBarIndicatorSize.tab,
tabs: STR_TAB_TITLE_LIST.map((e) => Container(
padding: EdgeInsets.only(left: 4, right: 4),
child: Tab(text: e),
),
).toList(),
controller: _tabController,
),
),
),
];
},
body: TabBarView(
children: []..addAll(STR_TAB_TITLE_LIST.map((e) {
if (e == 'myWork') {
return MyWorkPage(e);
} else if (e == 'character') {
return CharactersPage(onCharacterPageItemSelected, e);
}
return TabPage(e);
})),
controller: _tabController,
),
),
),
);
}
And pages:
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.topCenter,
child: GridView.builder(
shrinkWrap: true,
padding: EdgeInsets.all(4.0),
physics: BouncingScrollPhysics(),
itemCount: SAMPLE_CARD_LIST.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
itemBuilder: (context, index) {
return ItemCard(SAMPLE_CARD_LIST[index]);
},
),
);
}
The problems I faced are two
It does not save page's scroll position
like this: https://github.com/flutter/flutter/issues/40740
it cuts top of page when swipe to next tab
like this: Flutter TabBar and SliverAppBar that hides when you scroll down
I tried all suggestions above links but it did not worked
How can I fix this?
Use of code:
You can make your custom appbar. I think this code will help you.
appBar: CustomTab(
onDone: (tabNo) {
},
),)
Where we have used:
import 'package:filepath/md2indicator.dart';
class CustomTab extends StatefulWidget implements PreferredSizeWidget {
final void Function(int) onDone;
CustomTab({
Key? key,
required this.onDone,
}) : super(key: key);
#override
State<CustomTab> createState() => _CustomTabState();
#override
// TODO: implement preferredSize
final Size preferredSize = const Size.fromHeight(kToolbarHeight);
}
class _CustomTabState extends State<CustomTab>
with SingleTickerProviderStateMixin {
late TabController tabcontroller;
// final GlobalKey<ScaffoldState> _key = GlobalKey<ScaffoldState>();
#override
void initState() {
// TODO: implement initState
super.initState();
tabcontroller = TabController(length: 9, vsync: this);
tabcontroller.addListener(() {
setState(() {
widget.onDone(tabcontroller.index);
tabcontroller.animateTo(tabcontroller.index);
});
});
}
#override
void dispose() {
tabcontroller.dispose();
// TODO: implement dispose
super.dispose();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: TabBar(
controller: tabcontroller,
labelStyle: TextStyle(
fontWeight: FontWeight.w700,
),
indicatorSize: TabBarIndicatorSize.label,
labelColor: Colors.green,
unselectedLabelColor: Colors.black,
isScrollable: true,
indicator: MD2Indicator(
indicatorHeight: 3,
indicatorColor: Colors.green.shade700,
indicatorSize: MD2IndicatorSize.full,
),
tabs: [
Tab(
text: 'Trending',
),
Tab(
text: 'Sports',
),
Tab(
text: 'Economy',
),
Tab(
text: 'Fashion',
),
Tab(
text: 'Entertainment',
),
Tab(
text: 'Technology',
),
Tab(
text: 'POLITICS',
),
Tab(
text: 'Viral',
),
Tab(
text: 'Videos',
)
],
),
);
}
}
And custom tab bar md2_tab_indicator style can be designed as:
import 'package:flutter/widgets.dart';
enum MD2IndicatorSize {
tiny,
normal,
full,
}
class MD2Indicator extends Decoration {
final double indicatorHeight;
final Color indicatorColor;
final MD2IndicatorSize indicatorSize;
const MD2Indicator({
required this.indicatorHeight,
required this.indicatorColor,
required this.indicatorSize,
});
#override
_MD2Painter createBoxPainter([VoidCallback? onChanged]) {
return _MD2Painter(this, onChanged);
}
}
class _MD2Painter extends BoxPainter {
final MD2Indicator decoration;
_MD2Painter(this.decoration, VoidCallback? onChanged) : super(onChanged);
#override
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
assert(configuration.size != null);
Rect rect;
if (decoration.indicatorSize == MD2IndicatorSize.full) {
rect = Offset(
offset.dx,
configuration.size!.height - decoration.indicatorHeight,
) &
Size(configuration.size!.width, decoration.indicatorHeight);
} else if (decoration.indicatorSize == MD2IndicatorSize.tiny) {
rect = Offset(
offset.dx + configuration.size!.width / 2 - 8,
configuration.size!.height - decoration.indicatorHeight,
) &
Size(16, decoration.indicatorHeight);
} else {
rect = Offset(
offset.dx + 6,
configuration.size!.height - decoration.indicatorHeight,
) &
Size(configuration.size!.width - 12, decoration.indicatorHeight);
}
final Paint paint = Paint()
..color = decoration.indicatorColor
..style = PaintingStyle.fill;
canvas.drawRRect(
RRect.fromRectAndCorners(
rect,
topRight: Radius.circular(8),
topLeft: Radius.circular(8),
),
paint,
);
}
}```
I'm developing an application with flutter. But I cannot fix the buttons in the project. On my chat page, the button goes up. I'm new to the Flutter language, can you help me?
Hello, I'm developing an application with flutter. But I cannot fix the buttons in the project. On my chat page, the button goes up. I'm new to the Flutter language, can you help me?
Screenshot:
My Button Code :
class HomePage extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<HomePage> {
final _scrollController = ScrollController();
List<TabItem> tabItems = List.of([
new TabItem(Icons.home, "Anasayfa", Colors.blue),
new TabItem(Icons.message, "Sohbet Odası", Colors.orange),
new TabItem(Icons.person, "Profil", Colors.red),
]);
int seciliPozisyon = 0;
CircularBottomNavigationController _navigationController;
#override
void initState() {
super.initState();
_navigationController =
new CircularBottomNavigationController(seciliPozisyon);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.black,
centerTitle: true,
title: Text("Crypto App"),
),
body: Stack(
children: <Widget>[
Padding(
child: bodyContainer(),
padding: EdgeInsets.only(bottom: 60),
),
Align(alignment: Alignment.bottomCenter, child: bottomNav())
],
),
);
}
Widget bodyContainer() {
String activeUserId =
Provider.of<AuthorizationService>(context, listen: false).activeUserId;
Color selectedColor = tabItems[seciliPozisyon].color;
switch (seciliPozisyon) {
case 0:
return HomeScreen();
break;
case 1:
return FriendlyChatApp();
break;
case 2:
return Profile(
profileId: activeUserId,
);
break;
}
}
Widget bottomNav() {
return CircularBottomNavigation(
tabItems,
controller: _navigationController,
barHeight: 60,
barBackgroundColor: Colors.white,
animationDuration: Duration(milliseconds: 300),
selectedCallback: (int selectedPos) {
setState(() {
seciliPozisyon = selectedPos;
});
},
);
}
}
ChatApp Code :
void main() {
runApp(
FriendlyChatApp(),
);
}
final ThemeData kIOSTheme = ThemeData(
primarySwatch: Colors.blue,
primaryColor: Colors.grey[100],
primaryColorBrightness: Brightness.light,
);
final ThemeData kDefaultTheme = ThemeData(
primarySwatch: Colors.orange,
accentColor: Colors.orangeAccent,
);
String _name = '';
class FriendlyChatApp extends StatelessWidget {
const FriendlyChatApp({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: ChatScreen(),
);
}
}
class ChatMessage extends StatelessWidget {
ChatMessage({this.text, this.animationController});
final String text;
final AnimationController animationController;
#override
Widget build(BuildContext context) {
return SizeTransition(
sizeFactor:
CurvedAnimation(parent: animationController, curve: Curves.easeOut),
axisAlignment: 0.0,
child: Container(
margin: EdgeInsets.symmetric(vertical: 10.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: const EdgeInsets.only(right: 16.0),
child: CircleAvatar(child: Text(_name[0])),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(_name, style: Theme.of(context).textTheme.headline4),
Container(
margin: EdgeInsets.only(top: 5.0),
child: Text(text),
),
],
),
),
],
),
),
);
}
}
class ChatScreen extends StatefulWidget {
#override
_ChatScreenState createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
final List<ChatMessage> _messages = [];
final _textController = TextEditingController();
final FocusNode _focusNode = FocusNode();
bool _isComposing = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
decoration: Theme.of(context).platform == TargetPlatform.iOS //new
? BoxDecoration(
border: Border(
top: BorderSide(color: Colors.grey[200]),
),
)
: null,
child: Column(
children: [
Flexible(
child: ListView.builder(
padding: EdgeInsets.all(8.0),
reverse: true,
itemBuilder: (_, int index) => _messages[index],
itemCount: _messages.length,
),
),
Divider(height: 1.0),
Container(
decoration: BoxDecoration(color: Theme.of(context).cardColor),
child: _buildTextComposer(),
),
],
),
),
);
}
Widget _buildTextComposer() {
return IconTheme(
data: IconThemeData(color: Theme.of(context).accentColor),
child: Container(
margin: EdgeInsets.symmetric(horizontal: 8.0),
child: Row(
children: [
Flexible(
child: TextField(
controller: _textController,
onChanged: (String text) {
setState(() {
_isComposing = text.isNotEmpty;
});
},
onSubmitted: _isComposing ? _handleSubmitted : null,
decoration: InputDecoration.collapsed(
hintText: 'Mesajınızı Buraya Yazınız:'),
focusNode: _focusNode,
),
),
Container(
margin: EdgeInsets.symmetric(horizontal: 4.0),
child: Theme.of(context).platform == TargetPlatform.iOS
? CupertinoButton(
onPressed: _isComposing
? () => _handleSubmitted(_textController.text)
: null,
child: Text('Gönder'),
)
: IconButton(
icon: const Icon(Icons.send),
onPressed: _isComposing
? () => _handleSubmitted(_textController.text)
: null,
))
],
),
),
);
}
void _handleSubmitted(String text) {
_textController.clear();
setState(() {
_isComposing = false;
});
var message = ChatMessage(
text: text,
animationController: AnimationController(
duration: const Duration(milliseconds: 700),
vsync: this,
),
);
setState(() {
_messages.insert(0, message);
});
_focusNode.requestFocus();
message.animationController.forward();
}
#override
void dispose() {
for (var message in _messages) {
message.animationController.dispose();
}
super.dispose();
}
}
You need to add :
resizeToAvoidBottomInset: false,
Here as shown:
Scaffold(
//here
resizeToAvoidBottomInset: false,
appBar: AppBar(
backgroundColor: Colors.black,
centerTitle: true,
title: Text("Crypto App"),
),
body: Stack(
children: <Widget>[
Padding(
child: bodyContainer(),
padding: EdgeInsets.only(bottom: 60),
),
Align(alignment: Alignment.bottomCenter, child: bottomNav())
],
),
);
}
Here using this whenever user will type something setting value to false will make keyboard overlap the bottom navigation bar.
Hope this is what you wanted to achieve.
I'm trying to check if the keyboard is visible after tapping on the TextFormField by calling:
if (MediaQuery.of(context).viewInsets.bottom != 0) {
...
}
but as soon as I have this MediaQuery call in my code, the Keyboard doesn't even open anymore after tapping on the TextFormField...
Edited:
This is what happens when tapping on the TextFormField:
I added the code of the page which causes this faulty behavior:
class LearnPage extends StatefulWidget {
final int topicId;
final String topicName;
LearnPage(this.topicId, this.topicName);
#override
_LearnPageState createState() => _LearnPageState();
}
class _LearnPageState extends State<LearnPage> {
final mainCaardIndex = ValueNotifier<int>(0);
PageController _mainCaardController;
PageController _inputCaardController;
List<CaardM> caards;
List<PageM> mainCaardList = [];
List<List<PageM>> inputCaardList = [];
List<List<TextEditingController>> textControllers = [];
Future<void> async_init() async {
List<CaardM> caardList =
await DatabaseProviderCaard.db.getCaards(widget.topicId);
caards = caardList;
setState(() {});
}
bool _keyboardIsVisible() {
return !(MediaQuery.of(context).viewInsets.bottom == 0.0);
}
#override
void initState() {
async_init();
_mainCaardController = PageController();
_inputCaardController = PageController();
super.initState();
}
#override
void dispose() {
_mainCaardController.dispose();
_inputCaardController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.lightBlue,
title: Center(
child: Text(
widget.topicName,
textAlign: TextAlign.center,
style: TextStyle(fontWeight: FontWeight.bold),
),
),
actions: [
!_keyboardIsVisible()
? IconButton(
icon: Icon(Icons.check_circle_outline),
tooltip: 'Validate',
onPressed: validate,
)
: IconButton(
icon: Icon(Icons.keyboard_hide),
onPressed: () {
FocusManager.instance.primaryFocus.unfocus();
},
),
],
),
body: Column(
children: [
Expanded(
flex: 3,
child: FutureBuilder(
future: getMainContent(),
builder: (context, AsyncSnapshot<int> snapshotMain) {
if (snapshotMain.connectionState == ConnectionState.done) {
return PageView.builder(
itemCount: snapshotMain.data,
controller: _mainCaardController,
onPageChanged: (position) {
mainCaardIndex.value = position;
mainCaardIndex.notifyListeners();
_inputCaardController.jumpToPage(0);
},
itemBuilder: (context, position) {
return LearnMainCaard(
mainCaardList[position].title,
mainCaardList[position].content,
);
},
);
} else {
return CircularProgressIndicator();
}
},
),
),
Expanded(
flex: 5,
child: FutureBuilder(
future: getInputContent(),
builder: (context, AsyncSnapshot<int> snapshotInput) {
if (snapshotInput.connectionState == ConnectionState.done) {
return ValueListenableBuilder(
valueListenable: mainCaardIndex,
builder: (context, value, _) {
return PageView.builder(
itemCount: snapshotInput.data,
controller: _inputCaardController,
itemBuilder: (context, position) {
return LearnInputCaard(
inputCaardList[mainCaardIndex.value][position].title,
textControllers[mainCaardIndex.value][position],
);
},
);
},
);
} else {
return CircularProgressIndicator();
}
},
),
),
],
),
);
}
Future<int> getMainContent() async {
List<PageM> caardPages;
mainCaardList.clear();
for (var i = 0; i < caards.length; i++) {
caardPages = await DatabaseProviderPage.db.getPages(caards[i].id);
if (caards[i].pageAmount > 1) {
mainCaardList.add(caardPages[0]);
}
}
return mainCaardList.length;
}
Future<int> getInputContent() async {
List<PageM> caardPages = [];
List<PageM> list = [];
inputCaardList.clear();
for (var i = 0; i < caards.length; i++) {
caardPages = await DatabaseProviderPage.db.getPages(caards[i].id);
if (caards[i].pageAmount > 1) {
addController(caards[i].pageAmount - 1);
list = [];
for (var i = 1; i < caardPages.length; i++) {
list.add(caardPages[i]);
}
inputCaardList.add(list);
}
}
return inputCaardList[mainCaardIndex.value].length;
}
void addController(int controllerAmount) {
List<TextEditingController> currentTextControllers = [];
print('addController called');
currentTextControllers.clear();
currentTextControllers = List.generate(
controllerAmount, (index) => TextEditingController()
);
textControllers.add(currentTextControllers);
}
And here the LearnInputCaard widget:
import 'package:flutter/material.dart';
class LearnInputCaard extends StatefulWidget {
final String title;
final TextEditingController textController;
LearnInputCaard(
this.title,
this.textController,
);
#override
_LearnInputCaardState createState() => _LearnInputCaardState();
}
class _LearnInputCaardState extends State<LearnInputCaard> {
#override
Widget build(BuildContext context) {
return Container(
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
margin: EdgeInsets.all(20),
color: Colors.amberAccent.shade100,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Expanded(
flex: 1,
child: Text(
widget.title,
style: TextStyle(fontSize: 20),
),
),
Divider(color: Colors.black38,),
Expanded(
flex: 10,
child: Container(
padding: EdgeInsets.all(10.0),
child: TextFormField(
controller: widget.textController,
maxLines: 30,
decoration: InputDecoration(
hintText: "Enter content",
border: InputBorder.none,
),
),
),
)
],
),
),
),
);
}
}
you need to check MediaQuery.of(context).viewInsets.bottom == 0.0
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Keyboard Visibility Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
width: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
_keyboardIsVisible()
? Text(
"Keyboard is visible",
style: Theme.of(context)
.textTheme
.display1
.copyWith(color: Colors.blue),
)
: RichText(
text: TextSpan(children: [
TextSpan(
text: "Keyboard is ",
style: Theme.of(context)
.textTheme
.display1
.copyWith(color: Colors.blue),
),
TextSpan(
text: "not ",
style: Theme.of(context)
.textTheme
.display1
.copyWith(color: Colors.red),
),
TextSpan(
text: "visible",
style: Theme.of(context)
.textTheme
.display1
.copyWith(color: Colors.blue),
)
]),
),
SizedBox(
height: 20,
),
Container(
width: 200.0,
child: TextField(
style: Theme.of(context).textTheme.display1,
decoration: InputDecoration(
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
),
borderRadius: BorderRadius.circular(10.0),
),
),
),
)
],
),
));
}
bool _keyboardIsVisible() {
return !(MediaQuery.of(context).viewInsets.bottom == 0.0);
}
}
The problem is that you get the context from the parent widget.
If you call:
MediaQuery.of(context);
in the same widget where your forms are, you shouldn't get this behavior.
You need to define a GlobalKey<FormState> in your highest widget and pass this one down. Then it works. I defined it first in my SafeArea and therefore it failed and I had the same problem with the keyboard.
Here are some snippets of my code. I have a PageController and use two different forms on my two pages.
class OnboardingScaffold extends HookConsumerWidget {
OnboardingScaffold({Key? key}) : super(key: key);
// here you define your GlobalKeys
final _formKeyLogin = GlobalKey<FormState>();
final _formKeyApply = GlobalKey<FormState>();
#override
Widget build(BuildContext context, WidgetRef ref) {
final controller = usePageController();
bool isKeyboard = MediaQuery.of(context).viewInsets.bottom != 0;
return Scaffold(
body: Container(
padding: !isKeyboard
? const EdgeInsets.only(bottom: 80)
: const EdgeInsets.only(bottom: 0),
child: PageView(
controller: controller,
children: [
// here you pass these keys into your child Widget
LoginSafeArea(
formKey: _formKeyLogin,
),
ApplySafeArea(
formKey: _formKeyApply,
),
],
),
),
bottomSheet: !isKeyboard
? Container(height: 80)
: Container(height: 0),
);
}
}
The child Widget should contain a Form Widget:
class LoginSafeArea extends HookConsumerWidget {
const LoginSafeArea({Key? key, required this.formKey}) : super(key: key);
final GlobalKey<FormState> formKey;
#override
Widget build(BuildContext context, WidgetRef ref) {
return SafeArea(
child: Center(
child: Form(
key: formKey,
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.only(left: 24.0, right: 24.0),
child: Column(
children: <Widget>[
const EmailFieldWidget(),
const SizedBox(height: 8.0),
const PasswordFieldWidget(),
const SizedBox(height: 16.0),
LoginButtonWidget(
formKey: formKey,
),
const SizedBox(height: 8.0),
],
),
),
),
),
);
}
}
In Flutter you suppose I have a simple Container and I would like to change the size of that to up, for example in this simple screenshot I want to change top container in section 1 to up to have a top container in section 2
and top container in section 1 should behave only 100.0 after size to up
container B in section 1 and section 2 are in the same axis without change position when container A will be resized to up
for example, this is what I want to have
I'm not sure with which one animation I can have this feature
this code work, but this is not what I want to have.
i want to have draggable bottom sheet with changing border radius when bottom sheet arrived to top of screen like with pastes sample video screen and fade0n/out widget inside appbar which that inside top of bottom sheet, which that visible when bottom sheet arrived top or hide when bottom sheet don't have maximum size
import 'package:flutter/material.dart';
void main()=>runApp(SizeUp());
class SizeUp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'test',
home: SizeUpAnim(),
);
}
}
class SizeUpAnim extends StatefulWidget {
#override
State<StatefulWidget> createState() =>_SizeUpAnim();
}
class _SizeUpAnim extends State with SingleTickerProviderStateMixin {
AnimationController _controller;
// ignore: constant_identifier_names
static const _PANEL_HEADER_HEIGHT = 32.0;
bool get _isPanelVisible {
final AnimationStatus status = _controller.status;
return status == AnimationStatus.completed ||
status == AnimationStatus.forward;
}
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 100), value: 1.0, vsync: this);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 8.0,
title: const Text("Step4"),
leading: IconButton(
onPressed: () {
_controller.fling(velocity: _isPanelVisible ? -1.0 : 1.0);
},
icon: AnimatedIcon(
icon: AnimatedIcons.close_menu,
progress: _controller.view,
),
),
),
body: Column(
children: <Widget>[
Expanded(
child: LayoutBuilder(
builder: _buildStack,
),
),
Text('aaa'),
],
),
);
}
Animation<RelativeRect> _getPanelAnimation(BoxConstraints constraints) {
final double height = constraints.biggest.height;
final double top = height - _PANEL_HEADER_HEIGHT;
const double bottom = -_PANEL_HEADER_HEIGHT;
return RelativeRectTween(
begin: RelativeRect.fromLTRB(0.0, top, 0.0, bottom),
end: const RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0),
).animate( CurvedAnimation(parent: _controller, curve: Curves.linear));
}
Widget _buildStack(BuildContext context, BoxConstraints constraints) {
final Animation<RelativeRect> animation = _getPanelAnimation(constraints);
final ThemeData theme = Theme.of(context);
return Container(
color: theme.primaryColor,
child: Stack(
children: <Widget>[
const Center(
child: Text("base"),
),
PositionedTransition(
rect: animation,
child: Material(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(16.0),
topRight: Radius.circular(16.0)),
elevation: 12.0,
child: Container(
height: _PANEL_HEADER_HEIGHT,
child: const Center(child: Text("panel")),
),
),
),
],
),
);
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
}
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,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool isLong = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First'),
),
body: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('hey'),
RaisedButton(
onPressed: () {
setImages();
setState(() {
isLong = !isLong;
});
},
child: Text(isLong ? 'long' : 'short'),
),
RaisedButton(
onPressed: _onPressed,
child: Text('open'),
)
],
),
),
);
}
_onPressed() {
Navigator.of(context)
.push(TransparentRoute(builder: (context) => NewWidget(images)));
}
List<String> images = List.generate(
5,
(_) => 'http://placeimg.com/100/100/any',
);
void setImages() {
images = List.generate(
isLong ? 5 : 25,
(_) => 'http://placeimg.com/100/100/any',
);
}
}
class NewWidget extends StatefulWidget {
const NewWidget(this.images, {Key key}) : super(key: key);
final List<String> images;
#override
_NewWidgetState createState() => _NewWidgetState();
}
class _NewWidgetState extends State<NewWidget> {
bool isBig = false;
bool isStack = false;
bool isBounsing = true;
final double topOffset = 200;
final double miniHandleHeigh = 30;
double safeAreaPadding = 0;
double startBigAnimationOffset;
double startStickyOffset;
double backgroundHeight = 0;
double get savedAppBarHeigh => safeAreaPadding + kToolbarHeight;
final ScrollController controller = ScrollController();
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback(_afterLayout);
super.initState();
}
void _afterLayout(_) {
var media = MediaQuery.of(context);
safeAreaPadding = media.padding.top;
startBigAnimationOffset = topOffset - savedAppBarHeigh;
startStickyOffset = startBigAnimationOffset + 20;
backgroundHeight = media.size.height - miniHandleHeigh - topOffset;
var canScroll = !_isImageSizeBiggerThenBottomSheetSize(
media.size.width,
media.size.height,
);
controller.addListener(
canScroll ? withoutScrolling : withScrolling,
);
}
void withoutScrolling() {
var offset = controller.offset;
if (offset < 0) {
goOut();
} else {
controller.animateTo(
0,
duration: Duration(milliseconds: 100),
curve: Curves.easeIn,
);
}
}
void withScrolling() {
var offset = controller.offset;
if (offset < 0) {
goOut();
} else if (offset < startBigAnimationOffset && isBig) {
setState(() {
isBig = false;
});
} else if (offset > startBigAnimationOffset && !isBig) {
setState(() {
isBig = true;
});
} else if (offset > startStickyOffset && !isStack) {
setState(() {
isStack = true;
});
} else if (offset < startStickyOffset && isStack) {
setState(() {
isStack = false;
});
}
if (offset < topOffset && !isBounsing) {
setState(() => isBounsing = true);
} else if (offset > topOffset && isBounsing) {
setState(() => isBounsing = false);
}
}
void goOut() {
controller.dispose();
Navigator.of(context).pop();
}
_isImageSizeBiggerThenBottomSheetSize(
double screenWidth,
double screenHeight,
) {
// padding: 10, 3 in row;
print(screenHeight);
var itemHeight = (screenWidth - 20) / 3;
print(itemHeight);
var gridMaxHeight = screenHeight - topOffset - miniHandleHeigh;
print(gridMaxHeight);
return (widget.images.length / 3).floor() * itemHeight > gridMaxHeight;
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: isStack ? Colors.white : Colors.transparent,
body: Stack(
children: [
Positioned(
bottom: 0,
right: 0,
left: 0,
child: Container(
constraints: BoxConstraints(minHeight: backgroundHeight),
decoration: BoxDecoration(
color: Colors.white,
),
),
),
ListView(
physics:
isBounsing ? BouncingScrollPhysics() : ClampingScrollPhysics(),
controller: controller,
children: <Widget>[
Container(
alignment: Alignment.bottomCenter,
height: topOffset,
child: TweenAnimationBuilder(
tween: Tween(begin: 0.0, end: isBig ? 1.0 : 0.0),
duration: Duration(milliseconds: 500),
child: Align(
alignment: Alignment.topCenter,
child: Padding(
padding: EdgeInsets.only(top: 15),
child: Container(
decoration: BoxDecoration(
color: Colors.black38,
borderRadius: BorderRadius.circular(2),
),
height: 5,
width: 60,
),
),
),
builder: (_, number, child) {
return Container(
height: savedAppBarHeigh * number + miniHandleHeigh,
decoration: BoxDecoration(
borderRadius: BorderRadius.vertical(
top: Radius.circular((1 - number) * 20)),
color: Colors.white,
),
child: Opacity(opacity: 1 - number, child: child),
);
}),
),
Container(
padding: EdgeInsets.all(10),
constraints: BoxConstraints(
minHeight:
MediaQuery.of(context).size.height - savedAppBarHeigh),
decoration: BoxDecoration(
color: Colors.white,
),
child: getGrid(),
)
],
),
if (isStack)
_AppBar(
title: 'Gallery',
)
],
),
);
}
Widget getGrid() {
return GridView.count(
crossAxisSpacing: 10,
mainAxisSpacing: 10,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
crossAxisCount: 3,
children: widget.images.map((url) {
return Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.blueAccent,
),
),
child: Image(
image: NetworkImage(url),
),
);
}).toList(),
);
}
}
class _AppBar extends StatelessWidget {
const _AppBar({Key key, this.title}) : super(key: key);
final Color backgroundColor = Colors.white;
final Color color = Colors.grey;
final String title;
#override
Widget build(BuildContext context) {
return Material(
elevation: 5,
child: Container(
height: kToolbarHeight + MediaQuery.of(context).padding.top,
color: backgroundColor,
child: OverflowBox(
alignment: Alignment.topCenter,
maxHeight: 200,
child: SafeArea(
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: kToolbarHeight),
child: AppBar(
centerTitle: false,
backgroundColor: backgroundColor,
iconTheme: IconThemeData(
color: color, //change your color here
),
primary: false,
title: Text(
title,
style: TextStyle(color: color),
),
elevation: 0,
),
),
),
),
),
);
;
}
}
class TransparentRoute extends PageRoute<void> {
TransparentRoute({
#required this.builder,
RouteSettings settings,
}) : assert(builder != null),
super(settings: settings, fullscreenDialog: false);
final WidgetBuilder builder;
#override
bool get opaque => false;
#override
Color get barrierColor => null;
#override
String get barrierLabel => null;
#override
bool get maintainState => true;
#override
Duration get transitionDuration => Duration(milliseconds: 350);
#override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
final result = builder(context);
return Container(
color: Colors.black.withOpacity(0.5),
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 1),
end: Offset.zero,
).animate(CurvedAnimation(
parent: animation,
curve: Curves.easeIn,
)),
child: result,
),
);
}
}
I think you may try this library, sliding_sheet
when you detect the expand status by controller, then you animate/enlarge the container A.