Unable to use precacheImage function to load and cache the local images from assets to GridView or ListView in flutter.
ISSUE: When scrolling the list, images always reload.
class AppLandingPage extends StatelessWidget {
final String title;
AppLandingPage({Key key, this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: DrawerPage(),
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return getToolbarWidget("Home title");
},
body: setDataToContainer(),
));
}
}
Container setDataToContainer() {
return Container(
color: Colors.white,
margin: EdgeInsets.only(left: 4.0, right: 4, bottom: 4, top: 4),
child: CustomScrollView(
slivers: <Widget>[
SliverList(
delegate: SliverChildListDelegate(
[
HeaderWidget("Header 1"),
],
),
),
SliverGrid(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
delegate: SliverChildListDelegate(
[
BodyWidget("title", "key_x", "pnl.svg"),
BodyWidget("title2", "key_x", "cls.svg"),
BodyWidget(
"title3", "key_x", "irr.svg"),
BodyWidget(
"title4", "key_x", "icp.svg"),
],
),
),
SliverList(
delegate: SliverChildListDelegate(
[
HeaderWidget("Header 2"),
],
),
),
SliverGrid(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
delegate: SliverChildListDelegate(
[
BodyWidget("title5", "key_x", "ict.svg"),
BodyWidget("title6", "key_x", "icc.svg"),
],
),
),
SliverList(
delegate: SliverChildListDelegate(
[
HeaderWidget("Others"),
],
),
),
SliverGrid(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
delegate: SliverChildListDelegate(
[
BodyWidget("title7", "key_x", "icd.svg"),
BodyWidget("title8", "6", "ici.svg"),
],
),
),
],
),
);
}
class HeaderWidget extends StatelessWidget {
final String text;
HeaderWidget(this.text);
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.only(left: 10.0, right: 10, bottom: 2, top: 20),
child: Text(
text.toUpperCase(),
style: TextStyle(
color: hexToColor(themeColor1),
fontSize: 16,
fontWeight: FontWeight.bold),
),
color: Colors.white,
);
}
}
class BodyWidget extends StatelessWidget {
final String imagePath;
final String title;
final String navigationKey;
BodyWidget(this.title, this.navigationKey, this.imagePath);
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
child: Card(
color: hexToColor(themeColor1),
elevation: 5,
child: InkWell(
onTap: () {
navigateToView(context, navigationKey);
},
child: Stack(
children: <Widget>[
Align(
alignment: Alignment.topCenter,
child: Container(
margin: EdgeInsets.only(top: 40),
child: SvgPicture.asset(
"assets/images/$imagePath",
color: Colors.white,
width: 35,
height: 35,
),
),
),
Align(
alignment: Alignment.bottomCenter,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
verticalDirection: VerticalDirection.up,
children: <Widget>[
Container(
padding: EdgeInsets.all(10),
color: Colors.black.withOpacity(.2),
child: Text(
title,
style: TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.normal),
),
)
],
),
),
],
),
),
));
}
void navigateToView(BuildContext context, String navigationKey) {
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) {
return NewSections();
},
transitionsBuilder: (context, animation1, animation2, child) {
return FadeTransition(
opacity: animation1,
child: child,
);
},
transitionDuration: Duration(milliseconds: 600),
),
);
}
}
Instead of calling precacheImage() inside initState(), you should do it like this:
Image myImage;
#override
void initState() {
super.initState();
myImage= Image.asset(path);
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
precacheImage(myImage.image, context);
}
I'm not sure how to solve the preloading issue. However you could use a AutomaticKeepAliveClientMixin for your Stateful Widget's state class (the one containing your grid objects. You will have to make your BodyWidget a StatefulWidget) so that you only load each image once and not re-render when the widget goes out of visibility.
class _WidgetState extends State<Widget> with AutomaticKeepAliveClientMixin<Widget>{
….
….
#override
#mustCallSuper
Widget build(BuildContext context)….
….
….
#override
bool get wantKeepAlive => true;
}'
Hope that helps!
Related
I'm trying to create a custom dialogue box, where i'm passing title, text and image parameter, when i call the dialogue box, it is not display image, here is the code
this is the code of Dialogue box.
class LoginSucessDailog extends StatefulWidget {
final String title, text;
final Image img;
const LoginSucessDailog({ required this.title, required this.text,required this.img });
#override
_LoginSucessDailogState createState() => _LoginSucessDailogState();
}
class _LoginSucessDailogState extends State<LoginSucessDailog> {
#override
Widget build(BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(Constants.padding),
),
elevation: 0,
backgroundColor: Colors.transparent,
child: contentBox(context),
);
}
contentBox(context) {
return Stack(
children: <Widget>[
Container(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Image.asset(
widget.img.toString(),
width: 100,
),
Text(
widget.title,
style:GoogleFonts.montserrat(fontSize: 22, fontWeight: FontWeight.w600),
),
Padding(
padding: const EdgeInsets.only(left: 20, right: 20),
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
style: TextStyle(
color: Colors.black,
),
children: <TextSpan>[
TextSpan(
text:widget.text,
style: GoogleFonts.montserrat(fontSize: 16, color: Colors.grey)),
],
),
),
),
SizedBox50(),
okay()
],
),
),
],
);
}
}
and here i'm calling it as like this
showDialog(
context: context,
builder: (BuildContext context) {
return LoginSucessDailog( text: 'Phone number doesnt exists!',
title: 'Error',
img:Image.asset("assets/img/alert.png"));
});
but it send me this error
Unable to load asset: Image(image: AssetImage(bundle: null, name: "assets/img/alert.png"), frameBuilder: null, loadingBuilder: null, alignment: Alignment.center, this.excludeFromSemantics: false, filterQuality: low)
widget.img.toString(), if i'm not converting it into string then it gives me this error
The argument type 'Image' can't be assigned to the parameter type 'String'.
please help how to solve it .
I changed the passed value and adapt the constructor dialog.
showDialog(
context: context,
builder: (BuildContext context) {
return LoginSucessDailog( text: 'Phone number doesnt exists!',
title: 'Error',
img:'assets/img/alert.png');
});
class LoginSucessDailog extends StatefulWidget {
final String title, text, img;
const LoginSucessDailog({ required this.title, required this.text,required this.img });
#override
_LoginSucessDailogState createState() => _LoginSucessDailogState();
}
class _LoginSucessDailogState extends State<LoginSucessDailog> {
#override
Widget build(BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(Constants.padding),
),
elevation: 0,
backgroundColor: Colors.transparent,
child: contentBox(context),
);
}
contentBox(context) {
return Stack(
children: <Widget>[
Container(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Image.asset(
widget.img,
width: 100,
),
Text(
widget.title,
style:GoogleFonts.montserrat(fontSize: 22, fontWeight: FontWeight.w600),
),
Padding(
padding: const EdgeInsets.only(left: 20, right: 20),
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
style: TextStyle(
color: Colors.black,
),
children: <TextSpan>[
TextSpan(
text:widget.text,
style: GoogleFonts.montserrat(fontSize: 16, color: Colors.grey)),
],
),
),
),
SizedBox50(),
okay()
],
),
),
],
);
}
}
I need slide animation from left to right and vice versa.
Can anyone give me any guidance which widget i should use in it?
I have tried a few thing like :- carousel_slider, transformer_pageview, page_transformer
I used SlideTransition class, but it is not giving me expected result, I have also tried different range/values of OffSet but no luck.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:sliding_up_panel/sliding_up_panel.dart';
class HomePage extends StatefulWidget {
const HomePage({Key key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
AnimationController _controller;
Animation<Offset> _offsetAnimation;
AnimationController _controllerTwo;
Animation<Offset> _offsetAnimationTwo;
#override
void initState() {
_controller = AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
);
_offsetAnimation = Tween<Offset>(
begin: const Offset(-0.00001, 0.0),
end: const Offset(0.09, 0.0),
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
));
_controllerTwo = AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
);
_offsetAnimationTwo = Tween<Offset>(
begin: const Offset(-0.85, 0.0),
end: const Offset(-0.00001, 0.0),
).animate(CurvedAnimation(
parent: _controllerTwo,
curve: Curves.easeInOut,
));
super.initState();
}
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
child: new GestureDetector(
onHorizontalDragUpdate: (details) {
if (details.primaryDelta > 0) {
_controller..reverse();
_controllerTwo..reverse();
}
if (details.primaryDelta < 0) {
_controller..forward();
_controllerTwo..forward();
}
print(details.primaryDelta);
},
child: Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
SlideTransition(
position: _offsetAnimationTwo,
child: Card(
color: Colors.black54,
child: Container(),
),
),
Flexible(
child: SlideTransition(
position: _offsetAnimation,
child: Container(
child: SlidingUpPanel(
renderPanelSheet: false,
panel: _floatingStatement(),
body: CardsStack(context),
),
),
),
),
],
),
),
)
],
);
}
Widget _floatingStatement() {
return Container(
margin: const EdgeInsets.all(24.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(24.0)),
boxShadow: [
BoxShadow(
blurRadius: 20.0,
color: Colors.grey,
),
],
),
);
}
}
class CardsStack extends StatelessWidget {
const CardsStack(BuildContext context, {Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return new Stack(
alignment: Alignment.center,
children: <Widget>[
Positioned(
top: 20,
left: 10,
right: 10,
child: Card(
color: Colors.black54,
child: Container(),
),
),
Positioned(
top: 70,
left: 10,
right: 10,
child: Card(
color: Colors.black54,
child: Container(),
),
),
Positioned(
top: 120,
left: 10,
right: 10,
child: Card(
color: Colors.black54,
child: Container(),
),
),
],
);
}
}
You can use PageView to horizontally (even vertically) swipe between pages.
Or you can use TabBarView to implement it with bottom navigation.
Example code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
debugShowCheckedModeBanner: false,
);
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
body: TabBarView(
children: <Widget>[
Center(child: Text("Page1")),
Center(child: Text("Page2")),
Center(child: Text("Page3")),
],
),
bottomNavigationBar: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10.0),
topRight: Radius.circular(10.0),
),
boxShadow: [
BoxShadow(
color: Colors.black,
offset: Offset(0, 0),
blurRadius: 10.0,
),
],
),
child: TabBar(
indicatorColor: Colors.red,
indicatorWeight: 5.0,
indicatorSize: TabBarIndicatorSize.label,
labelColor: Colors.black,
tabs: <Widget>[
Tab(icon: Icon(Icons.home)),
Tab(icon: Icon(Icons.category)),
Tab(icon: Icon(Icons.account_circle)),
],
),
),
),
);
}
}
Row(
children: <Widget>[
new Expanded(
child: SizedBox(
height: 150.0,
width: 300,
child: Carousel(
boxFit: BoxFit.cover,
autoplay: true,
animationCurve: Curves.fastOutSlowIn,
animationDuration: Duration(milliseconds: 1000),
dotSize: 6.0,
dotIncreasedColor: Colors.grey,
dotBgColor: Colors.transparent,
dotPosition: DotPosition.bottomCenter,
dotVerticalPadding: 10.0,
showIndicator: true,
indicatorBgPadding: 7.0,
images: [
AssetImage('assets/mountain.jpg'),
AssetImage('assets/mountain2.png')
],
),
),
),
// Other children below
]
)
Hi, I've managed to implement a carousel but I am wondering how you can add Text to it on the center. When the carousel moves to a new image I want the text to change to. I am using carousel_pro 1.0.0.
You can copy paste run full code below
image attribute actually can use widget , so you can use Card
code snippet
images: [
Card(
child:
Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
const ListTile(
leading: Icon(Icons.album),
title: Text('The Enchanted Nightingale'),
subtitle:
Text('Music by Julie Gable. Lyrics by Sidney Stein.'),
),
Image.network(
'https://cdn-images-1.medium.com/max/2000/1*GqdzzfB_BHorv7V2NV7Jgg.jpeg'),
])),
working demo
full code
import 'package:flutter/material.dart';
import 'package:carousel_pro/carousel_pro.dart';
class CarouselPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: SizedBox(
height: 300.0,
width: 300.0,
child: Carousel(
boxFit: BoxFit.cover,
autoplay: false,
animationCurve: Curves.fastOutSlowIn,
animationDuration: Duration(milliseconds: 1000),
dotSize: 6.0,
dotIncreasedColor: Color(0xFFFF335C),
dotBgColor: Colors.transparent,
dotPosition: DotPosition.topRight,
dotVerticalPadding: 10.0,
showIndicator: true,
indicatorBgPadding: 7.0,
images: [
Card(
child:
Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
const ListTile(
leading: Icon(Icons.album),
title: Text('The Enchanted Nightingale'),
subtitle:
Text('Music by Julie Gable. Lyrics by Sidney Stein.'),
),
Image.network(
'https://cdn-images-1.medium.com/max/2000/1*GqdzzfB_BHorv7V2NV7Jgg.jpeg'),
])),
Card(
child:
Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
const ListTile(
leading: Icon(Icons.album),
title: Text('The Enchanted Nightingale'),
subtitle:
Text('Music by Julie Gable. Lyrics by Sidney Stein.'),
),
Image.network(
'https://cdn-images-1.medium.com/max/2000/1*wnIEgP1gNMrK5gZU7QS0-A.jpeg'),
])),
],
),
),
),
);
}
}
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: CarouselPage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
This is complete working code with Image on top with text and dots. For this you need to use these two libraries:- " carousel_slider: ^4.1.1 ", "smooth_page_indicator: ^1.0.0+2", update them to the latest.
class MyItem {
String itemName;
String path;
MyItem(this.itemName, this.path);
}
class craouselImage extends StatefulWidget {
#override
_craouselImage createState() => _craouselImage();
}
class _craouselImage extends State<craouselImage> {
int activeIndex = 0;
List<MyItem> items = [
MyItem("item 1", 'assets/images/appiconlogo.png'),
MyItem("item 2", 'assets/images/Mockup4.png'),
];
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
CarouselSlider.builder(
itemCount: items.length,
options: CarouselOptions(
height: 400,
viewportFraction: 1,
autoPlay: true,
enlargeCenterPage: true,
enlargeStrategy: CenterPageEnlargeStrategy.height,
autoPlayInterval: const Duration(seconds: 1),
onPageChanged: (index, reason) {
setState(() {
activeIndex = index;
});
},
),
itemBuilder: (context, index, realIndex) {
final imgList = items[index];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Expanded(child: buildImage(imgList.path, index)),
const SizedBox(
height: 15,
),
buildText(imgList.itemName, index),
],
);
},
),
const SizedBox(
height: 22,
),
buildIndicator(),
const SizedBox(
height: 22,
),
//buildText(itemName, index),
// buildText(),
],
),
);
}
Widget buildImage(String imgList, int index) => Container(
margin: const EdgeInsets.symmetric(horizontal: 12),
color: Colors.transparent,
child: Align(
alignment: Alignment.center,
child: Image.asset(
imgList,
fit: BoxFit.cover,
),
),
);
buildIndicator() => AnimatedSmoothIndicator(
activeIndex: activeIndex,
count: items.length,
effect: const JumpingDotEffect(
dotColor: Colors.black,
dotHeight: 15,
dotWidth: 15,
activeDotColor: mRed),
);
buildText(String itemName, int index) => Align(
alignment: FractionalOffset.bottomCenter,
child: Text(
itemName,
style: const TextStyle(
fontWeight: FontWeight.w700, fontSize: 23, color: mRed),
));
}
I'm having trouble with app bar animation, I'm using SilverAppBar, in my app. So, the problem is when I'm in the middle of my list and I scroll up, the app bar does not appear, but it appears just when scrolling reaches the top of the items list. I already tested the snap parameter and give it true, but not the result I expect. I have ideas about creating a custom animation for this, but I'm not too experienced in Flutter, and also if there is a way to add parameters, or another widget that will work for my situation, it would be great.
The actual code of the demo I'm using:
Widget _search() => Container(
color: Colors.grey[400],
child: SafeArea(
child: Container(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
enabled: false,
style: TextStyle(fontSize: 16, color: Colors.white),
decoration: InputDecoration(
prefix: SizedBox(width: 12),
hintText: "Search",
contentPadding:
EdgeInsets.symmetric(horizontal: 32.0, vertical: 14.0),
border: InputBorder.none,
),
),
),
)),
);
Container _buildBody() {
return Container(
child: new GridView.count(
crossAxisCount: 2,
children: List.generate(100, (index) {
return Center(
child: Text(
'Item $index',
style: Theme.of(context).textTheme.headline,
),
);
}),
));
}
#override
Widget build(BuildContext context) {
return new Scaffold(
resizeToAvoidBottomPadding: false,
body: new NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
new SliverAppBar(
title: Text("Demo",
style: TextStyle(
color: Colors.white,
)),
pinned: false,
floating: true,
forceElevated: innerBoxIsScrolled,
),
];
},
body: new Column(children: <Widget>[
_search(),
new Expanded(child: _buildBody())
])));
}
The result I have now:
Image 1
The result I got after giving true to the snap parameter:
Image 2
Plenty of applications like WhatsApp, Facebook, LinkedIn ... have this animating app bar. To explain more what exactly I expect with this animating app bar, I added an example of Google Play Store, showing the wanted animation: Play Store example
I had a similar issue with CustomScrollView and SliverAppbar using refresh indicator i ended up creating my own custom appbar.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class HomeView extends StatefulWidget {
#override
HomeState createState() => HomeState();
}
class HomeState extends State<HomeView> with SingleTickerProviderStateMixin {
bool _isAppbar = true;
ScrollController _scrollController = new ScrollController();
#override
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.userScrollDirection ==
ScrollDirection.reverse) {
appBarStatus(false);
}
if (_scrollController.position.userScrollDirection ==
ScrollDirection.forward) {
appBarStatus(true);
}
});
}
void appBarStatus(bool status) {
setState(() {
_isAppbar = status;
});
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(kToolbarHeight),
child: AnimatedContainer(
height: _isAppbar ? 55.0 : 0.0,
duration: Duration(milliseconds: 200),
child: CustomAppBar(),
),
),
body: ListView.builder(
controller: _scrollController,
itemCount: 20,
itemBuilder: (BuildContext context, int index) {
return container();
},
),
),
);
}
}
Widget container() {
return Container(
height: 80.0,
color: Colors.pink,
margin: EdgeInsets.all(8.0),
width: 100,
child: Center(
child: Text(
'Container',
style: TextStyle(
fontSize: 18.0,
),
)),
);
}
class CustomAppBar extends StatefulWidget {
#override
AppBarView createState() => new AppBarView();
}
class AppBarView extends State<CustomAppBar> {
#override
Widget build(BuildContext context) {
return AppBar(
backgroundColor: Colors.white,
leading: InkWell(
onTap: () => {},
child: new Padding(
padding: const EdgeInsets.all(8.0),
child: CircleAvatar(
backgroundColor: Colors.white,
child: ClipOval(
child: Image.network(
'https://images.squarespace-cdn.com/content/5aee389b3c3a531e6245ae76/1530965251082-9L40PL9QH6PATNQ93LUK/linkedinPortraits_DwayneBrown08.jpg?format=1000w&content-type=image%2Fjpeg'),
),
),
),
),
actions: <Widget>[
IconButton(
alignment: Alignment.centerLeft,
icon: Icon(
Icons.search,
color: Colors.black,
),
onPressed: () {},
),
],
title: Container(
alignment: Alignment.centerLeft,
child: Text("Custom Appbar", style: TextStyle(color: Colors.black),)
),
);
}
}
To get this functionality to work, you will need to use the CustomScrollView widget instead of NestedScrollView. Google Documentation
Here is a working example:
class MyHomeState extends State<MyHome> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
const SliverAppBar(
pinned: false,
snap: false,
floating: true,
flexibleSpace: FlexibleSpaceBar(
title: Text('Demo'),
),
),
SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200.0,
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
childAspectRatio: 4.0,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
alignment: Alignment.center,
color: Colors.teal[100 * (index % 9)],
child: Text('grid item $index'),
);
},
childCount: 50,
),
),
],
)
);
}
}
Example of this running here
I was able to make the floating Appbar with Tabbar similar to that of WhatsApp by using SliverAppbar with NestedScrollView. Do remember to add floatHeaderSlivers: true, in NestedScrollView. Link to sample code
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: CustomSliverAppbar(),
);
}
}
class CustomSliverAppbar extends StatefulWidget {
#override
_CustomSliverAppbarState createState() => _CustomSliverAppbarState();
}
class _CustomSliverAppbarState extends State<CustomSliverAppbar>
with SingleTickerProviderStateMixin {
TabController _tabController;
#override
void initState() {
_tabController = TabController(
initialIndex: 0,
length: 2,
vsync: this,
);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: NestedScrollView(
floatHeaderSlivers: true,
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
title: Text(
"WhatsApp type sliver appbar",
),
centerTitle: true,
pinned: true,
floating: true,
bottom: TabBar(
indicatorColor: Colors.black,
labelPadding: const EdgeInsets.only(
bottom: 16,
),
controller: _tabController,
tabs: [
Text("TAB A"),
Text("TAB B"),
]),
),
];
},
body: TabBarView(
controller: _tabController,
children: [
TabA(),
const Center(
child: Text('Display Tab 2',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
),
],
),
),
);
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
}
class TabA extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scrollbar(
child: ListView.separated(
separatorBuilder: (context, child) => Divider(
height: 1,
),
padding: EdgeInsets.all(0.0),
itemCount: 30,
itemBuilder: (context, i) {
return Container(
height: 100,
width: double.infinity,
color: Colors.primaries[Random().nextInt(Colors.primaries.length)],
);
},
),
);
}
}
I can't distinguish new Firebase Cloud Messaging notifications from older ones in my Flutter UI. How can I fix this? Thanks.
EXAMPLE: 3 users comment on an author's post that already has 2 "seen" comments. The author's post gets a +3 red marble on it. When the author clicks on this post in the Notifications tab, the user is routed to its comments, that +3 reduces to 0, and the bottom tab marble reduces to 3 (6-3=3) as well.
Dummy UI Code and Pics below.
1. A blank "home page;"
2. a dummy notifications page with posts that have been replied to
3. On clicking a post, a comments page.
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
routes: <String, WidgetBuilder>{
"/comment": (BuildContext context) => new commentFoundation(),
},
home: MainScreen());
}
}
//The content of the notification tab.
class Notification extends StatefulWidget {
#override
_NotificationState createState() => new _NotificationState();
}
class _NotificationState extends State<Notification> {
#override
Widget build(BuildContext context) {
return _buildPostList();
}
Widget _buildPostList() {
return new Scaffold(
backgroundColor: Colors.blue,
body: new Scrollbar(
child: new ListView.builder(
itemBuilder: (BuildContext context, int index) {
return new Container(
child: new Stack(children: <Widget>[
new Column(
children: <Widget>[
Container(
margin: EdgeInsets.only(top: 0.0, left: 12.0),
child: DummyPost()),
],
),
replyNotification(),
]));
},
itemCount: 2,
)));
}
Widget replyNotification() {
return new Positioned(
left: 175,
top: 205.5,
child: new Container(
padding: EdgeInsets.only(left: 3.0, right: 3.0),
decoration: new BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(30),
),
constraints: BoxConstraints(
minWidth: 30,
minHeight: 30,
),
child: Row(
children: <Widget>[
Container(
padding: EdgeInsets.only(top: 0.0),
child: new Text(
'+',
style: new TextStyle(
color: Colors.white,
fontSize: 26,
),
textAlign: TextAlign.center,
)),
Container(
padding: EdgeInsets.only(right: 0.0),
child: new Text(
'3',
style: new TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
)),
],
)),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => new _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Center(
child: new Container(
child: Text("HomePageStuff"),
));
}
}
//A dummy post in notififications tab
class DummyPost extends StatefulWidget {
#override
_DummyPostState createState() => new _DummyPostState();
}
class _DummyPostState extends State<DummyPost> {
bool monVal = false;
bool reportPressed = false;
Widget commentButton() {
return RaisedButton(
child: Text("ReplyButton"),
onPressed: () => Navigator.push(context,
MaterialPageRoute(builder: (context) => commentFoundation())),
);
}
#override
Widget build(BuildContext context) {
return postList();
}
Widget postList() {
return Column(children: <Widget>[
new GestureDetector(
//this type works
onTap: () => Navigator.pushNamed(context, '/comment'),
child: new Stack(children: <Widget>[
Column(children: <Widget>[
SizedBox(
height: 10.0,
),
Container(
margin: EdgeInsets.only(top: 35.0),
width: 390.0,
child: Card(
shape: RoundedRectangleBorder(
//15?
borderRadius: BorderRadius.circular(5.0),
),
color: Colors.white,
elevation: 2.0,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Flexible(
child: ListTile(
// The main post content
subtitle: Container(
height: 130.0,
margin: EdgeInsets.only(
top: 15.0, left: 15.0, right: 15.0),
child: Container(
child: Center(
child: Text(
"This is a dummy post",
textAlign: TextAlign.center,
style: TextStyle(
wordSpacing: 3.0,
color: Colors.black,
fontSize: 18.0),
))),
),
)),
//button row
new Container(
width: 400.0,
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: <Widget>[
Container(
margin: EdgeInsets.only(right: 0.0),
width: 190.0,
height: 40.0,
child: commentButton(),
),
// Text to indicate comment numbers
],
)),
],
))),
]),
]))
]);
}
}
//the entire comment page.
class commentFoundation extends StatefulWidget {
#override
_commentFoundationState createState() => new _commentFoundationState();
}
class _commentFoundationState extends State<commentFoundation> {
ScrollController _scrollController = new ScrollController();
#override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: Colors.green,
appBar: _buildAppBar(),
body: new Stack(children: <Widget>[
ListView.builder(
shrinkWrap: true,
controller: _scrollController,
itemCount: 5,
itemBuilder: (BuildContext context, int index) {
return new individualComment();
}),
]));
}
Widget _buildAppBar() {
return AppBar(
title: Text("REPLIES"),
automaticallyImplyLeading: true,
backgroundColor: Colors.white,
elevation: 0.0,
);
}
}
//Each individual comment you see when you click on a post
class individualComment extends StatefulWidget {
final String title;
const individualComment({Key key, this.title}) : super(key: key);
#override
_individualCommentState createState() => new _individualCommentState();
}
class _individualCommentState extends State<individualComment> {
#override
Widget build(BuildContext context) {
return new Stack(
children: <Widget>[
Column(children: <Widget>[
//this used to be an expanded
new Container(
child: Card(
elevation: 0.0,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new ListTile(
// The avatar
leading: Container(
child: Icon(
Icons.accessibility_new,
)),
// Title, location, and time
// The main post content
subtitle: Container(
margin: EdgeInsets.only(top: 10.0, bottom: 0.0),
child: Text(
"This is a reply",
style: TextStyle(
color: Colors.black,
fontSize: .0364609 *
MediaQuery.of(context).size.width),
),
),
),
],
))),
])
],
);
}
}
//*****This is just the tabbar.
class MainScreen extends StatefulWidget {
#override
_MainScreenstate createState() => new _MainScreenstate();
}
class _MainScreenstate extends State<MainScreen> with TickerProviderStateMixin {
String _lastSelected = '0';
final List<Widget> _bodyOptions = [
HomePage(),
Notification(),
];
int _selectedIndex = 0;
bool tab = true;
_selectedTab(int index) {
setState(() {
_lastSelected = '$index';
});
}
Widget __buildTabs() {
return Scaffold(
body: _bodyOptions.elementAt(int.parse(_lastSelected)),
//around the FAB
backgroundColor: const Color(0xFFF4F4F4),
bottomNavigationBar: new Stack(
children: <Widget>[
FABBottomAppBar(
height: 45.0,
centerItemText: '',
color: Colors.black,
selectedColor: Colors.blue,
notchedShape: CircularNotchedRectangle(),
onTabSelected: _selectedTab,
items: [
FABBottomAppBarItem(iconData: Icons.search, text: 'Home'),
FABBottomAppBarItem(
iconData: Icons.notifications, text: 'Notifications'),
],
),
//the red "notification" marble
new Positioned(
right: 20,
top: 0.5,
child: new Container(
padding: EdgeInsets.all(1),
decoration: new BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(12),
),
constraints: BoxConstraints(
minWidth: 20,
minHeight: 20,
),
child: new Text(
'6',
style: new TextStyle(
color: Colors.white,
fontSize: 14,
),
textAlign: TextAlign.center,
),
),
)
],
));
}
#override
Widget build(BuildContext context) {
return __buildTabs();
}
}
//Everything below is boiler plate tabbar code.
class FABBottomAppBarItem {
FABBottomAppBarItem({this.iconData, this.text});
IconData iconData;
String text;
}
class FABBottomAppBar extends StatefulWidget {
FABBottomAppBar({
this.items,
this.centerItemText,
this.height: 60.0,
this.iconSize: 24.0,
this.backgroundColor,
this.color,
this.selectedColor,
this.notchedShape,
this.onTabSelected,
}) {
assert(this.items.length == 2 || this.items.length == 4);
}
final List<FABBottomAppBarItem> items;
final String centerItemText;
final double height;
final double iconSize;
final Color backgroundColor;
final Color color;
final Color selectedColor;
final NotchedShape notchedShape;
final ValueChanged<int> onTabSelected;
#override
State<StatefulWidget> createState() => FABBottomAppBarState();
}
class FABBottomAppBarState extends State<FABBottomAppBar> {
int _selectedIndex = 0;
_updateIndex(int index) {
widget.onTabSelected(index);
setState(() {
_selectedIndex = index;
});
return _selectedIndex;
}
#override
Widget build(BuildContext context) {
List<Widget> items = List.generate(widget.items.length, (int index) {
return _buildTabItem(
item: widget.items[index],
index: index,
onPressed: _updateIndex,
);
});
items.insert(items.length >> 1, _buildMiddleTabItem());
return BottomAppBar(
elevation: 0.0,
notchMargin: 2.0,
shape: CircularNotchedRectangle(),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: items,
),
//color of bottom
color: Colors.white,
);
}
Widget _buildMiddleTabItem() {
return Expanded(
child: SizedBox(
height: widget.height,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SizedBox(height: widget.iconSize),
Text(
widget.centerItemText ?? '',
style: TextStyle(color: widget.color),
),
],
),
),
);
}
Widget _buildTabItem({
FABBottomAppBarItem item,
int index,
ValueChanged<int> onPressed,
}) {
Color color = _selectedIndex == index ? widget.selectedColor : widget.color;
return Expanded(
child: SizedBox(
height: widget.height,
child: Material(
type: MaterialType.transparency,
child: InkWell(
onTap: () => onPressed(index),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(item.iconData, color: color, size: widget.iconSize),
Text(
item.text,
style: TextStyle(color: color),
)
],
),
),
),
),
);
}
}
This seems to be a broad question and I'm unsure if you're asking for suggestions on how you can implement tracking read and unread notifications, or troubleshooting help on your existing code. I suggest checking tips posted on this thread on how you can manage notification counters.