how to Pin the silver app bar programatically on click event - android

I have a SliverAppBar, which scrolls up to shrink and scrolls down to expand.
Here I am trying to programmatically collapse or expand the SliverAppBar I want to pin the
silver app bar programmatically on the Tap() function
Any advice is helpful. Thanks.
Here is my code for my custom SliverAppBar code:
class _Test3State extends State<Test3> {
bool _pinned = false;
bool check = true;
final ScrollController _scrollController = ScrollController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
controller: _scrollController,
shrinkWrap: true,
physics: BouncingScrollPhysics(),
slivers: <Widget>[
SliverAppBar(
pinned: _pinned,
floating: false,
expandedHeight: 250.0,
bottom: PreferredSize(
// Add this code
preferredSize: Size.fromHeight(60.0), // Add this code
child: Text(''), // Add this code
),
flexibleSpace: FlexibleSpaceBar(
title: const Text('SliverAppBar'),
background: Image.asset(
"assets/images/serviceHeader.png",
fit: BoxFit.fill,
)),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return GestureDetector(
onTap: () {
_scrollController.addListener(() {
print('object');
if (!_pinned &&
_scrollController.hasClients &&
_scrollController.offset > kToolbarHeight) {
setState(() {
print('true');
_pinned = true;
});
} else if (_pinned &&
_scrollController.hasClients &&
_scrollController.offset < kToolbarHeight) {
setState(() {
print('false');
_pinned = false;
});
}
});
},
child: Container(
height: 100.0,
child: Center(
child: Text('$index', textScaleFactor: 5),
),
),
);
},
childCount: 20,
),
),
],
),
);
}
}

Related

Flutter lags when opening the keyboard

I have a flutter project (chat) with a flutter/material.dart vaporizer. When opening the keyboard (TextField), lags appear. I don't quite understand the reason. Can you help?
Video
I added Scaffold(resizeToAvoidBottomInset: false, but it didn't help.
I don't understand why even after adding resizeToAvoidBottomInset : false, a lot of frames appear there when opening the keyboard
The shortest possible code. Link to the full below
#override
Widget build(BuildContext context) {
final observer = Provider.of<Observer>(context, listen: true);
var _keyboardVisible = MediaQuery.of(context).viewInsets.bottom != 0;
return PickupLayout(
scaffold: Fiberchat.getNTPWrappedWidget(WillPopScope(
onWillPop: isgeneratingSomethingLoader == true
? () async {
return Future.value(false);
}
: isemojiShowing == true
? () {
setState(() {
isemojiShowing = false;
keyboardFocusNode.unfocus();
});
return Future.value(false);
}
: () async {
setLastSeen();
WidgetsBinding.instance!
.addPostFrameCallback((timeStamp) async {
var currentpeer = Provider.of<CurrentChatPeer>(
this.context,
listen: false);
currentpeer.setpeer(newpeerid: '');
if (lastSeen == peerNo)
await FirebaseFirestore.instance
.collection(DbPaths.collectionusers)
.doc(currentUserNo)
.update(
{Dbkeys.lastSeen: true},
);
});
return Future.value(true);
},
child: ScopedModel<DataModel>(
model: _cachedModel,
child: ScopedModelDescendant<DataModel>(
builder: (context, child, _model) {
_cachedModel = _model;
updateLocalUserData(_model);
return peer != null
? Stack(
children: [
Scaffold(
key: _scaffold,
appBar: AppBar(
elevation: DESIGN_TYPE == Themetype.messenger
? 0.4
: 1,
titleSpacing: -14,
leading: Container(
margin: EdgeInsets.only(right: 0),
width: 10,
child: IconButton(
icon: Icon(
Icons.arrow_back_ios,
size: 20,
color: DESIGN_TYPE == Themetype.whatsapp
? fiberchatWhite
: fiberchatBlack,
),
onPressed: () {
if (isDeletedDoc == true) {
Navigator.of(context)
.pushAndRemoveUntil(
MaterialPageRoute(
builder: (BuildContext context) =>
FiberchatWrapper(),
),
(Route route) => false,
);
} else {
Navigator.pop(context);
}
},
),
),
...
),
],
),
body: Stack(
children: <Widget>[
new Container(
decoration: new BoxDecoration(
color: DESIGN_TYPE == Themetype.whatsapp
? fiberchatChatbackground
: fiberchatWhite,
image: new DecorationImage(
image: peer![Dbkeys.wallpaper] == null
? AssetImage(
"assets/images/background.png")
: Image.file(File(
peer![Dbkeys.wallpaper]))
.image,
fit: BoxFit.cover),
),
),
Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(),
body: PageView(
children: <Widget>[
isDeletedDoc == true
? Center(
child: Padding(
padding:
const EdgeInsets.fromLTRB(
15, 60, 15, 15),
child: Text(
getTranslated(this.context,
'chatdeleted'),
style: TextStyle(
color: fiberchatGrey)),
),
)
: Column(
children: [
// List of messages
buildMessages(context),
// Input content
isBlocked()
? AlertDialog(
...
],
)
: hasPeerBlockedMe == true
? Container(
...
: buildInputAndroid(
context,
isemojiShowing,
refreshInput,
_keyboardVisible)
],
),
],
)),
buildLoading()
],
)),
buildLoadingThumbnail(),
],
)
: Container();
})))),
);
}
Full code: https://pastebin.com/4qvGRmyL

Ads overlapping bottom navigation in flutter

i have setup banner ads in flutter and those are overlapping the bottom navigation bar
I want to display ads below that bottom navigation bar,
is there any way that i can add a margin below the bottom navigation bar ?
i have implemented ads in home.dart (mainpage)
import 'package:provider/provider.dart';
import '../../ui/widgets/bottom_nav_bar.dart';
import '../../core/utils/theme.dart';
import 'search_page.dart';
import 'category.dart';
import 'main_page.dart';
import 'settings.dart';
import 'package:flutter/material.dart';
import 'package:firebase_admob/firebase_admob.dart';
import 'for_you.dart';
const String AD_MOB_APP_ID = 'ca-app-pub-3940256099942544~3347511713';
const String AD_MOB_TEST_DEVICE = 'DEC3010B2445165B43EB949F5D97D0F8 - run ad then check device logs for value';
const String AD_MOB_AD_ID = 'ca-app-pub-3940256099942544/6300978111';
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
BannerAd _bannerAd;
static final MobileAdTargetingInfo targetingInfo = new MobileAdTargetingInfo(
testDevices: <String>[AD_MOB_TEST_DEVICE],
);
BannerAd createBannerAd() {
return new BannerAd(
adUnitId: AD_MOB_AD_ID,
size: AdSize.banner,
targetingInfo: targetingInfo,
);
}
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
int _selectedIndex = 0;
final PageController _pageController = PageController();
#override
void initState() {
super.initState();
}
#override
void dispose() {
super.dispose();
_pageController.dispose();
}
#override
Widget build(BuildContext context) {
final stateData = Provider.of<ThemeNotifier>(context);
final ThemeData state = stateData.getTheme();
FirebaseAdMob.instance.initialize(appId: AD_MOB_APP_ID);
_bannerAd = createBannerAd()..load()..show();
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
centerTitle: false,
backgroundColor: state.primaryColor,
elevation: 0,
title: Text(
'RevWalls',
style: state.textTheme.headline,
),
actions: <Widget>[
IconButton(
icon: Icon(
Icons.search,
color: state.textTheme.body1.color,
),
onPressed: () => showSearch(
context: context, delegate: WallpaperSearch(themeData: state)),
)
],
),
body: Container(
color: state.primaryColor,
child: PageView(
controller: _pageController,
physics: BouncingScrollPhysics(),
onPageChanged: (index) {
setState(() {
_selectedIndex = index;
});
},
children: <Widget>[
MainBody(),
Category(),
ForYou(),
SettingsPage(),
],
),
),
bottomNavigationBar: BottomNavyBar(
selectedIndex: _selectedIndex,
unselectedColor: state.textTheme.body1.color,
onItemSelected: (index) {
_pageController.jumpToPage(index);
},
selectedColor: state.accentColor,
backgroundColor: state.primaryColor,
showElevation: false,
items: [
BottomNavyBarItem(
icon: Icon(Icons.home),
title: Text('Home'),
),
BottomNavyBarItem(
icon: Icon(Icons.category),
title: Text('Subreddits'),
),
BottomNavyBarItem(
icon: Icon(Icons.phone_android),
title: Text('Exact Fit'),
),
BottomNavyBarItem(
icon: Icon(Icons.settings),
title: Text('Settings'),
),
],
),
);
}
Widget oldBody(ThemeData state) {
return NestedScrollView(
headerSliverBuilder: (BuildContext context, bool boxIsScrolled) {
return <Widget>[
SliverAppBar(
backgroundColor: state.primaryColor,
elevation: 4,
title: Text(
'reWalls',
style: state.textTheme.headline,
),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search, color: state.accentColor),
onPressed: () {
showSearch(
context: context,
delegate: WallpaperSearch(themeData: state));
},
)
],
floating: true,
pinned: _selectedIndex == 0 ? false : true,
snap: false,
centerTitle: false,
),
];
},
body: Container(
color: state.primaryColor,
child: PageView(
controller: _pageController,
onPageChanged: (index) {
setState(() {
_selectedIndex = index;
});
},
children: <Widget>[
MainBody(),
Category(),
ForYou(),
SettingsPage(),
],
),
),
);
}
}
and here is the bottom navigation bar -
library bottom_navy_bar;
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class BottomNavyBar extends StatelessWidget {
final int selectedIndex;
final double iconSize;
final Color backgroundColor, selectedColor, unselectedColor;
final bool showElevation;
final Duration animationDuration;
final List<BottomNavyBarItem> items;
final ValueChanged<int> onItemSelected;
BottomNavyBar(
{Key key,
this.selectedIndex = 0,
this.showElevation = true,
this.iconSize = 20,
this.backgroundColor,
this.selectedColor,
this.unselectedColor,
this.animationDuration = const Duration(milliseconds: 250),
#required this.items,
#required this.onItemSelected}) {
assert(items != null);
assert(items.length >= 2 && items.length <= 5);
assert(onItemSelected != null);
}
Widget _buildItem(BottomNavyBarItem item, bool isSelected) {
return AnimatedContainer(
width: isSelected ? 120 : 50,
height: double.maxFinite,
duration: animationDuration,
decoration: BoxDecoration(
color: isSelected ? selectedColor.withOpacity(0.2) : backgroundColor,
borderRadius: BorderRadius.all(Radius.circular(100.0)),
),
alignment: Alignment.center,
child: ListView(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(right: 8),
child: IconTheme(
data: IconThemeData(
size: iconSize,
color: isSelected ? selectedColor : unselectedColor),
child: item.icon,
),
),
isSelected
? DefaultTextStyle.merge(
style: TextStyle(
fontSize: 16,
color: selectedColor,
fontWeight: FontWeight.bold),
child: item.title,
)
: SizedBox.shrink()
],
)
],
),
);
}
#override
Widget build(BuildContext context) {
return Container(
color: backgroundColor,
child: Container(
width: double.infinity,
height: 55,
padding: EdgeInsets.all(8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: items.map((item) {
var index = items.indexOf(item);
return GestureDetector(
onTap: () {
onItemSelected(index);
},
child: _buildItem(item, selectedIndex == index),
);
}).toList(),
),
),
);
}
}
class BottomNavyBarItem {
final Icon icon;
final Text title;
BottomNavyBarItem({
#required this.icon,
#required this.title,
}) {
assert(icon != null);
assert(title != null);
}
}
Please help
adding builder like this will solve the problem
var paddingBottom = 60.0;
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
//
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'World General info',
//theme: ThemeData(primarySwatch: Colors.cyan,),
theme: ThemeData(
primaryColor: Colors.cyan,
accentColor: Colors.green,
textTheme: TextTheme(bodyText2: TextStyle(color: Colors.purple)),
),
home: MyHomePage(title: 'World General Info'),
builder: (BuildContext context, Widget child) {
return new Padding(
child: child,
padding: EdgeInsets.only(bottom: paddingBottom),
);
}
);
}
}
You can use the MediaQuery.of(context) for that.
Wrap the whole Code inside a Container of height: MediaQuery.of(context).size.height - 60 . (the height of ad)
Column(
children: [
Container(
height: MediaQuery.of(context).size.height - 60,
child: HomePage(),
),
BannerAd(),
]
);
Found answer myself
We can use this to set margin in a container with other things like height, width
This code will add margin to bottom of bottom nav bar, as per my need i want to show ads below navbar so this solves my problem
margin: const EdgeInsets.only(bottom: 50)
Add this to your scaffold
bottomNavigationBar: Container(
height: 50.0,
color: Colors.white,
),
Think Simplest bro ... wrap your column ( mainAxisSize : MainAxisSize.min )
Scaffold(
appBar: _appBar,
body: _body,
bottomNavigationBar: Column(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
Container(
color: Colors.amber,
height: 70,
child: Center(child: Text('Banner'))),
BottomNavigationBar(
items: const [
BottomNavigationBarItem(icon: Icon(Icons.add), label: 'Add'),
BottomNavigationBarItem(icon: Icon(Icons.add), label: 'Add'),
BottomNavigationBarItem(icon: Icon(Icons.add), label: 'Add')
],
)
]))

Flutter : How to prevent rebuild of whole Reorderable Listview?

Currently I'm using flutter package 'Reorderables' to show a reorderable listview which contains several images.These images can be deleted from listview through a button , everything works fine. But the listview rebuild everytime when I delete an image. I'm using a class called 'ReorderableListviewManager' with ChangeNotifier to update images and Provider.of<ReorderableListviewManager>(context) to get latest images . The problem now is that using Provider.of<ReorderableListviewManager>(context) makes build() called everytime I delete an image , so the listview rebuid. I koow I
can use consumer to only rebuild part of widget tree, but it seems like that there's no place to put consumer in children of this Listview. Is there a way to rebuild only image but not whole ReorderableListview ? Thanks very much!
Below is my code:
class NotePicturesEditScreen extends StatefulWidget {
final List<Page> notePictures;
final NotePicturesEditBloc bloc;
NotePicturesEditScreen({#required this.notePictures, #required this.bloc});
static Widget create(BuildContext context, List<Page> notePictures) {
return Provider<NotePicturesEditBloc>(
create: (context) => NotePicturesEditBloc(),
child: Consumer<NotePicturesEditBloc>(
builder: (context, bloc, _) =>
ChangeNotifierProvider<ReorderableListviewManager>(
create: (context) => ReorderableListviewManager(),
child: NotePicturesEditScreen(
bloc: bloc,
notePictures: notePictures,
),
)),
dispose: (context, bloc) => bloc.dispose(),
);
}
#override
_NotePicturesEditScreenState createState() => _NotePicturesEditScreenState();
}
class _NotePicturesEditScreenState extends State<NotePicturesEditScreen> {
PreloadPageController _pageController;
ScrollController _reorderableScrollController;
List<Page> notePicturesCopy;
int longPressIndex;
List<double> smallImagesWidth;
double scrollOffset = 0;
_reorderableScrollListener() {
scrollOffset = _reorderableScrollController.offset;
}
#override
void initState() {
Provider.of<ReorderableListviewManager>(context, listen: false)
.notePictures = widget.notePictures;
notePicturesCopy = widget.notePictures;
_reorderableScrollController = ScrollController();
_pageController = PreloadPageController();
_reorderableScrollController.addListener(_reorderableScrollListener);
Provider.of<ReorderableListviewManager>(context, listen: false)
.getSmallImagesWidth(notePicturesCopy, context)
.then((imagesWidth) {
smallImagesWidth = imagesWidth;
});
super.initState();
}
#override
void dispose() {
_pageController.dispose();
_reorderableScrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
ReorderableListviewManager reorderableManager =
Provider.of<ReorderableListviewManager>(context, listen: false);
return SafeArea(
child: Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
shape: Border(bottom: BorderSide(color: Colors.black12)),
iconTheme: IconThemeData(color: Colors.black87),
elevation: 0,
automaticallyImplyLeading: false,
titleSpacing: 0,
centerTitle: true,
title: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
child: IconButton(
padding: EdgeInsets.only(left: 20, right: 12),
onPressed: () => Navigator.of(context).pop(),
icon: Icon(Icons.close),
),
),
Text('編輯',
style: TextStyle(color: Colors.black87, fontSize: 18))
],
),
actions: <Widget>[
FlatButton(
onPressed: () {},
child: Text(
'下一步',
),
)
],
),
backgroundColor: Color(0xffeeeeee),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Spacer(),
StreamBuilder<List<Page>>(
initialData: widget.notePictures,
stream: widget.bloc.notePicturesStream,
builder: (context, snapshot) {
notePicturesCopy = snapshot.data;
return Container(
margin: EdgeInsets.symmetric(horizontal: 20),
height: MediaQuery.of(context).size.height * 0.65,
child: PreloadPageView.builder(
preloadPagesCount: snapshot.data.length,
controller: _pageController,
itemCount: snapshot.data.length,
onPageChanged: (index) {
reorderableManager.updateCurrentIndex(index);
reorderableManager.scrollToCenter(
smallImagesWidth,
index,
scrollOffset,
_reorderableScrollController,
context);
},
itemBuilder: (context, index) {
return Container(
child: Image.memory(
File.fromUri(
snapshot.data[index].polygon.isNotEmpty
? snapshot.data[index]
.documentPreviewImageFileUri
: snapshot.data[index]
.originalPreviewImageFileUri)
.readAsBytesSync(),
gaplessPlayback: true,
alignment: Alignment.center,
),
);
}),
);
},
),
Spacer(),
Container(
height: MediaQuery.of(context).size.height * 0.1,
margin: EdgeInsets.symmetric(horizontal: 20),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: ReorderableRow(
scrollController: _reorderableScrollController,
buildDraggableFeedback: (context, constraints, __) =>
Container(
width: constraints.maxWidth,
height: constraints.maxHeight,
child: Image.memory(File.fromUri(
notePicturesCopy[longPressIndex]
.polygon
.isNotEmpty
? notePicturesCopy[longPressIndex]
.documentPreviewImageFileUri
: notePicturesCopy[longPressIndex]
.originalPreviewImageFileUri)
.readAsBytesSync()),
),
onReorder: (oldIndex, newIndex) async {
List<Page> result = await widget.bloc.reorderPictures(
oldIndex,
newIndex,
reorderableManager.notePictures);
_pageController.jumpToPage(newIndex);
reorderableManager.updateNotePictures(result);
reorderableManager
.getSmallImagesWidth(result, context)
.then((imagesWidth) {
smallImagesWidth = imagesWidth;
});
},
footer: Container(
width: 32,
height: 32,
margin: EdgeInsets.only(left: 16),
child: SizedBox(
child: FloatingActionButton(
backgroundColor: Colors.white,
elevation: 1,
disabledElevation: 0,
highlightElevation: 1,
child: Icon(Icons.add, color: Colors.blueAccent),
onPressed: notePicturesCopy.length >= 20
? () {
Scaffold.of(context)
.showSnackBar(SnackBar(
content: Text('筆記上限為20頁 !'),
));
}
: () async {
List<Page> notePictures =
await widget.bloc.addPicture(
reorderableManager.notePictures);
List<double> imagesWidth =
await reorderableManager
.getSmallImagesWidth(
notePictures, context);
smallImagesWidth = imagesWidth;
reorderableManager.updateCurrentIndex(
notePictures.length - 1);
reorderableManager
.updateNotePictures(notePictures);
_pageController
.jumpToPage(notePictures.length - 1);
},
),
),
),
children: Provider.of<ReorderableListviewManager>(
context)
.notePictures
.asMap()
.map((index, page) {
return MapEntry(
index,
Consumer<ReorderableListviewManager>(
key: ValueKey('value$index'),
builder: (context, manager, _) =>
GestureDetector(
onTapDown: (_) {
longPressIndex = index;
},
onTap: () {
reorderableManager.scrollToCenter(
smallImagesWidth,
index,
scrollOffset,
_reorderableScrollController,
context);
_pageController.jumpToPage(index);
},
child: Container(
margin: EdgeInsets.only(
left: index == 0 ? 0 : 12),
decoration: BoxDecoration(
border: Border.all(
width: 1.5,
color: index ==
manager
.getCurrentIndex
? Colors.blueAccent
: Colors.transparent)),
child: index + 1 <=
manager.notePictures.length
? Image.memory(
File.fromUri(manager
.notePictures[
index]
.polygon
.isNotEmpty
? manager
.notePictures[
index]
.documentPreviewImageFileUri
: manager
.notePictures[
index]
.originalPreviewImageFileUri)
.readAsBytesSync(),
gaplessPlayback: true,
)
: null),
),
));
})
.values
.toList()),
)),
Spacer(),
Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border(top: BorderSide(color: Colors.black12))),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
FlatButton(
onPressed: () async => await widget.bloc
.cropNotePicture(reorderableManager.notePictures,
_pageController.page.round())
.then((notePictures) {
reorderableManager.updateNotePictures(notePictures);
reorderableManager
.getSmallImagesWidth(notePictures, context)
.then((imagesWidth) {
smallImagesWidth = imagesWidth;
});
}),
child: Column(
children: <Widget>[
Icon(
Icons.crop,
color: Colors.blueAccent,
),
Container(
margin: EdgeInsets.only(top: 1),
child: Text(
'裁切',
style: TextStyle(color: Colors.blueAccent),
),
)
],
),
),
FlatButton(
onPressed: () {
int deleteIndex = _pageController.page.round();
widget.bloc
.deletePicture(
reorderableManager.notePictures, deleteIndex)
.then((notePictures) {
if (deleteIndex == notePictures.length) {
reorderableManager
.updateCurrentIndex(notePictures.length - 1);
}
reorderableManager.updateNotePictures(notePictures);
reorderableManager
.getSmallImagesWidth(notePictures, context)
.then((imagesWidth) {
smallImagesWidth = imagesWidth;
});
if (reorderableManager.notePictures.length == 0) {
Navigator.pop(context);
}
});
},
child: Column(
children: <Widget>[
Icon(
Icons.delete_outline,
color: Colors.blueAccent,
),
Container(
margin: EdgeInsets.only(top: 1),
child: Text(
'刪除',
style: TextStyle(color: Colors.blueAccent),
),
),
],
),
)
],
),
)
],
)),
);
}
}
You can't prevent a rebuild on your ReorderableListView widget because it will be rebuild every time there's an update on the Provider. What you can do here is to keep track the current index of all visible ListView items. When new data should be displayed coming from the Provider, you can retain the current indices of previous ListView items, and add the newly added items at the end of the list, or wherever you like.

SliverAppBar doesn't fully collapse with short list

I'm using Flutter's SliverAppBar with SliverList.
The app bar works fine with long list, that scroll a lot.
However, if I have medium size list with 8 items the app bar only partially collapses until the list runs out of items. It makes logical sense that the scroll stops because there are no more items but it leaves a really nasty effect on the app bar, which is being left in the middle of its transition in to collapsed toolbar.
Is there a way to force the list to scroll up until the toolbar is fully collapsed?
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton.extended(
label: Text("Start!"),
icon: Icon(Icons.play_arrow),
backgroundColor: Colors.orange,
elevation: 12,
onPressed: () {
Routing.navigate(
context,
LoggingPage(
workout: _workout,
exercises: workoutExercises,
),
);
},
),
body: CustomScrollView( physics: ScrollPhysics(),
slivers: <Widget>[
SliverAppBar(
actions: <Widget>[
MaterialButton(onPressed: (){}, child: Text("data"),)
],
expandedHeight: 300,
pinned: true,
floating: true,
snap: true,
flexibleSpace: FlexibleSpaceBar(
title: Text(_workout.name),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
buildSliverListItem,
childCount: workoutExercises.length,
),
),
],
),
);
}
We can use NestedScrollView, SliverOverlapAbsorber and SliverOverlapInjector based on Code With Andrea's example here
https://github.com/bizz84/slivers_demo_flutter/blob/master/lib/pages/nested_scroll_view_page.dart.
We use a NestedScrollView. First, we embed the SliverAppbar within the headerSliverBuilder with a SliverOverlapAbsorber above the SliverAppBar.
Next, we embed the CustomScrollView within a builder inside the body of the NestedScrollView. We add a SliverOverlapInjector at the top of the body.
return Scaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled){
return <Widget>[
SliverOverlapAbsorber(
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverAppBar(
pinned: true,
//floating: true,
stretch: true,
expandedHeight: 300.0,
flexibleSpace: FlexibleSpaceBar(
centerTitle: true,
title: const Text('Weather Report'),
background: Image.asset(
'assets/images/logo1.jpg',
fit: BoxFit.cover,
),
),
),
),
];
},
body: SafeArea(
child: Builder(
builder:(BuildContext context) {
return CustomScrollView(
slivers: <Widget>[
SliverOverlapInjector(
// This is the flip side of the SliverOverlapAbsorber above.
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
context),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int i){
return ListTile(
leading: Icon(Icons.wb_sunny),
title: i%2 != 1 ? Text('Sunday ${i+1}') : Text('Monday ${i+1}'),
subtitle: Text('sunny, h: 80, l: 65'),
);
},
childCount: 5
),
),
],
);
}
),
),
),
);
As a workaround you can use widget with custom RenderSliver. It must be added to the end of a slivers list.
class CustomRenderSliver extends RenderSliver {
final double _minHeaderHeight;
final double _maxHeaderHeight;
CustomRenderSliver(this._minHeaderHeight, this._maxHeaderHeight);
#override
void performLayout() {
double maxHeight = this.constraints.viewportMainAxisExtent -
(this.constraints.precedingScrollExtent - _maxHeaderHeight) /*height of regular list elements*/ -
_minHeaderHeight;
if (maxHeight < 0.0) maxHeight = 0.0;
this.geometry = SliverGeometry(scrollExtent: maxHeight);
}
}
list with custom sliver
Complete code
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
final minHeaderHeight = 90.0;
final maxHeaderHeight = 180.0;
List<Widget> slivers = List.generate(4, (index) {
return SliverToBoxAdapter(
child: SizedBox(
height: 100,
child: Padding(
padding: EdgeInsets.symmetric(vertical: 4),
child: Container(
color: Colors.blue,
),
),
),
);
});
// header
slivers.insert(
0, SliverPersistentHeader(
pinned: true,
delegate: _SliverAppBarDelegate(
minHeight: minHeaderHeight,
maxHeight: maxHeaderHeight
),
));
// custom sliver
slivers.add(CustomRenderSliverWidget(minHeaderHeight, maxHeaderHeight));
runApp(MaterialApp(
home: CustomScrollView(
slivers: slivers,
),
));
}
class CustomRenderSliverWidget extends LeafRenderObjectWidget {
final double _minHeaderHeight;
final double _maxHeaderHeight;
CustomRenderSliverWidget(this._minHeaderHeight, this._maxHeaderHeight);
#override
RenderObject createRenderObject(BuildContext context) {
return CustomRenderSliver(_minHeaderHeight, _maxHeaderHeight);
}
}
class CustomRenderSliver extends RenderSliver {
final double _minHeaderHeight;
final double _maxHeaderHeight;
CustomRenderSliver(this._minHeaderHeight, this._maxHeaderHeight);
#override
void performLayout() {
double maxHeight = this.constraints.viewportMainAxisExtent -
(this.constraints.precedingScrollExtent - _maxHeaderHeight) /*height of regular list elements*/ -
_minHeaderHeight;
if (maxHeight < 0.0) maxHeight = 0.0;
this.geometry = SliverGeometry(scrollExtent: maxHeight);
}
}
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
_SliverAppBarDelegate({
#required this.minHeight,
#required this.maxHeight,
});
final double minHeight;
final double maxHeight;
#override
double get minExtent => minHeight;
#override
double get maxExtent => max(maxHeight, minHeight);
#override
Widget build(BuildContext context, double shrinkOffset,
bool overlapsContent) {
return Container(color: Colors.red,);
}
#override
bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
return maxHeight != oldDelegate.maxHeight ||
minHeight != oldDelegate.minHeight;
}
}

Enable button in flutter when scrollbar is at the bottom of the list

Can anyone post a sample flutter code to enable a button when the scrollbar is at the bottom of the list?
I used NotificationListener and NotificationListener but event is not firing when scrollbar is at the bottom of the list.
I want to enable the button only when the scrollbar is at the bottom of the list else it should be disabled.
Below is the code that I am using.
class _TermsAndCondnsPage extends State<TermsAndCondnsPage> {
bool _isButtonEnabled = false;
bool _scrollingStarted() {
setState(() => _isButtonEnabled = false);
return false;
}
bool _scrollingEnded() {
setState(() => _isButtonEnabled = true);
return true;
}
ScrollController scrollcontroller;
#override
Widget build(BuildContext context) {
var acceptAndContinueButtonPressed;
if (_isButtonEnabled) {
acceptAndContinueButtonPressed = () {};
} else {
acceptAndContinueButtonPressed == null;
}
return new Scaffold(
appBar: new AppBar(
automaticallyImplyLeading: false,
title: new Text('Terms And Conditions', textAlign: TextAlign.center),
),
body:
NotificationListener<ScrollStartNotification>(
onNotification: (_) => _scrollingStarted(),
child: NotificationListener<ScrollEndNotification>(
onNotification: (_) => _scrollingEnded(),
child: new MaterialApp(
home: new Scaffold(
body: new SingleChildScrollView(
controller: scrollcontroller,
child: new Column(
children: <Widget>[
new Center(
child: new HtmlView(
data: html,
),
),
],
),
),
persistentFooterButtons: <Widget>[
new RaisedButton(
color: Colors.blue,
onPressed: _isButtonEnabled ? () {} : null,
child: new Text(
'Accept and Continue',
style: new TextStyle(
color: Colors.white,
),
)),
],
),
),
)),
);
}
String html = '''
<p>Sample HTML</p>
''';
}
Here you have a basic example, enable the button when reach the bottom, and disable when reach the top.
class ExampleState extends State<Example> {
final list = List.generate((40), (val) => "val $val");
final ScrollController _controller = new ScrollController();
var reachEnd = false;
_listener() {
final maxScroll = _controller.position.maxScrollExtent;
final minScroll = _controller.position.minScrollExtent;
if (_controller.offset >= maxScroll) {
setState(() {
reachEnd = true;
});
}
if (_controller.offset <= minScroll) {
setState(() {
reachEnd = false;
});
}
}
#override
void initState() {
_controller.addListener(_listener);
super.initState();
}
#override
void dispose() {
_controller.removeListener(_listener);
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Stack(children: [
Positioned(
top: 0.0,
bottom: 50.0,
width: MediaQuery.of(context).size.width,
child: ListView.builder(
controller: _controller,
itemBuilder: (context, index) {
return ListTile(
title: Text(list[index]),
);
},
itemCount: list.length,
),
),
Align(
alignment: Alignment.bottomCenter,
child: RaisedButton(
child: Text("button"),
color: Colors.blue,
onPressed: reachEnd ? () => null : null,
))
]);
}
}

Categories

Resources