I have 4 cards to take user inputs in a page, this 4 card widgets is showing in the listView, when user tap AddPhoto card, user can add photo from camera or gallery successfully, but after user added photo and scrolling page to give in inputs to the other cards, when AddPhoto card disappears because of sliding, after user turn back to the initial point of the page, the picture that user added is showing nothing.
How can I solve it?
class AddPhotoCard extends StatefulWidget {
AddPhotoCard({this.subCategoryCardId,this.subCategoryId});
final int subCategoryId;
final int subCategoryCardId;
#override
_AddPhotoCardState createState() => _AddPhotoCardState();
}
class _AddPhotoCardState extends State<AddPhotoCard> {
String path;
#override
Widget build(BuildContext context) {
return AnimatedPadding(
duration: Duration(milliseconds: 500),
padding: path==null?EdgeInsets.only(top: 7.5,left: 30,right: 30,bottom:7.5):
EdgeInsets.only(top: 7.5,left: 7.5,right: 7.5,bottom:7.5),
child: GestureDetector(
onTap: (){
_showOptions(context);
},
child: AnimatedContainer(
duration: Duration(milliseconds:500),
height: path==null?200:400,
child: Container(
decoration: BoxDecoration(
border: Border.all(style: BorderStyle.solid, width: 1),
borderRadius: BorderRadius.circular(30),
color:categoryModels[widget.subCategoryId].subCategoryModels[widget.subCategoryCardId].categoryColor.withOpacity(0.5),
),
child: Padding(
padding:EdgeInsets.all(5.0),
child: Column(
children: [
Expanded(
child: AspectRatio(
aspectRatio: 600/600,
child: ClipRRect(
borderRadius: BorderRadius.circular(30),
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.fitWidth,
alignment: FractionalOffset.center,
image: path==null?AssetImage("images/stickerForRecipeScreen.png"):FileImage(File(path)),
)
),
),
),
),
),
path==null?Text(
"${categoryModels[widget.subCategoryId].subCategoryModels[widget.subCategoryCardId]
.subCategoryName} tarifiniz için bir resim çekin",
style: TextStyle(
color: Colors.black,
fontFamily: "OpenSans",
fontWeight: FontWeight.bold,
fontSize: 20),): SizedBox.shrink(),
path==null?Icon(
Icons.camera_alt_rounded,
color: Colors.black,size: 70,): SizedBox.shrink(),
path==null?SizedBox(height: 20): SizedBox.shrink(),
],
),
),
),
),
),
);
}
}
This problem occurs because the ListView automatically destroys any widgets that are "scrolled away". You need to keep them alive or increase the cache:
Solution 1: Add this to your ListView:
cacheExtent: 4 //the number of widgets you have and want to keepAlive
Solution 2: Make your widgets a keepAlive:
//add this to the AddPhoto build method
super.build(context);
//add this to the end of the AddPhoto state, outside the build method
#override
bool get wantKeepAlive => true;
//add this then to your ListView
addAutomaticKeepAlives: true
Related
Today I have a question, not providing any code.
I would like to create a tiktok like experience for scrolling through my appfeed in flutter. However I don't want to scroll an entire page when I swipe, only to the next widget in the ListView/PageView. I am only able to swipe an entire page with pageview and I'm only able to scroll normally on Listview. Is there any solution for my request? I hope I clarified enough what I mean. Instagram offers such an experience on its Search. Is there any possibility how one could realize something like that?
Please help.
Use ListView.builder inside the Container with the 500px height and the ListView.builder will have the children posts thus you stay inside the same feed with the ability to swipe thru posts vertically or horizontally.
Check the following link for a tutorial : https://www.youtube.com/watch?v=baA_J5tUtEU
You can change the scrolling by setting scrollDirection: Axis.horizontal or Axis.vertical inside the ListView.buidler
Hope this answers your question.
So what you want to do is to use a Stack widget and then put the pageview, just as you would if you were creating an onboarding screen with flutter, something like this
import 'package:flutter/material.dart';
class OnBoarding extends StatefulWidget {
#override
_OnBoardingState createState() => _OnBoardingState();
}
class _OnBoardingState extends State<OnBoarding> {
PageController? controller;
int currentIndex = 0;
#override
void initState() {
controller = PageController();
super.initState();
}
#override
void dispose() {
controller!.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Stack(
children: [
PageView(
scrollDirection: Axis.vertical,
onPageChanged: onchahged,
controller: controller,
children: [
Container(
child: Image.network(
'https://picsum.photos/200/300',
fit: BoxFit.fill,
),
),
Container(
child: Image.network(
'https://picsum.photos/200/300',
fit: BoxFit.fill,
),
),
Container(
child: Image.network(
'https://picsum.photos/200/300',
fit: BoxFit.fill,
),
),
],
),
Positioned(
bottom: 30,
right: 10,
child: Column(
children: [
Icon(
Icons.ac_unit,
size: 30,
color: Colors.white,
),
SizedBox(
height: 10,
),
Icon(
Icons.image,
size: 30,
color: Colors.white,
),
SizedBox(
height: 10,
),
Icon(
Icons.person,
size: 30,
color: Colors.white,
),
SizedBox(
height: 10,
),
Icon(
Icons.person_add,
size: 30,
color: Colors.white,
),
],
),
)
],
),
);
}
onchahged(int index) {
setState(() {
currentIndex = index;
});
}
}
Here's what it looks like
For the specific Widget you must to wrap it with a GestureDetector and prevent from the current widget being scrollable.
An example:
GestureDetector(
onHorizontalDragUpdate: (_) {},
child: WidgetToNotBeingScrollableInTheHorizontal(),
);
Let's say for example when I press on the Like button (from the Like_button package) it will set its boolean to true, meaning that it will turn red. But when I pop this page out of the navigation and go back to it, how do I get it to show that I have previously pressed the button already? (right now what it does is it shows the same state as if i didn't press on it before) I want to make it into like 'favourite article' system.
import 'package:flutter/material.dart';
import 'package:like_button/like_button.dart';
class Article extends StatefulWidget {
#override
_ArticleState createState() => _ArticleState();
}
class _ArticleState extends State<Article> {
bool isLiked = false;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.green[50],
body: SingleChildScrollView(
GestureDetector(
onTap: () {
Navigator.pushNamed(context, '/N1');
},
child: Container(
margin: const EdgeInsets.symmetric(vertical:10),
child: Padding(
padding: EdgeInsets.fromLTRB(30, 0, 30, 0),
child: Card(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(25.0)),
child: Column(
children: [
Stack(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(25.0),
child: Image.asset('assets/CoverImage1.jpeg')),
Positioned(
top: 15,
right: 15,
child: Container(
width:50,
height:50,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(15.0)),
border: Border.all(color: Color.fromRGBO(141, 141, 141, 1.0).withAlpha(40)),
),
child: Center(
child: LikeButton(
size: 25,
isLiked: isLiked,
likeBuilder: (isLiked) {
final color = isLiked ? Colors.red : Colors.grey;
return Icon(Icons.favorite, color:color, size:25);
},
onTap: (isLiked) async {
this.isLiked = !isLiked;
return !isLiked;
}
),
),
),
),
],
),
you can store the value of variable with shared preferences.
you can check the documentation here: https://pub.dev/packages/shared_preferences/example
try these steps:
store value with something like prefs.setBool()
get the value with getBool in initState(). initState() always perform before UI widget is built.
with these steps enables the app to get the value even when apps is closed
I'm developing a parallax-style header/background block in my flutter application, which scrolls upwards at around 1/3 the speed of the foreground content. All parts in the foreground are within the same customScrollView and the background header is in a positioned container at the top of the stack.
I'm using a listener on the customscrollview to update a y-offset integer, and then using that integer to update the top position on the element inside my stack.
While this works as expected, the issue I'm facing is a large amount of repainting takes place on scroll, which in the future may impact performance. I'm sure there may be a more efficient way to achieve this - such as placing the entire background in a separate child widget and passing the controller down to it from the parent widget - however I am struggling to find any information on doing so, or if this is the correct approach.
Can someone point me in the right direction for refactoring this in such a way as to disconnect the scrolling background from the foreground, so that the foreground doesn't repaint constantly?
class ScrollingWidgetList extends StatefulWidget {
ScrollingWidgetList();
#override
State<StatefulWidget> createState() {
return _ScrollingWidgetList();
}
}
class _ScrollingWidgetList extends State<ScrollingWidgetList> {
ScrollController _controller;
double _offsetY = 0.0;
_scrollListener() {
setState(() {
_offsetY = _controller.offset;
});
}
#override
void initState() {
_controller = ScrollController();
_controller.addListener(_scrollListener);
super.initState();
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Positioned(
top: -(_offsetY / 3),
child: ConstrainedBox(
constraints: new BoxConstraints(
maxHeight: 300.0,
minHeight: MediaQuery.of(context).size.width * 0.35),
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomLeft,
colors: [
Theme.of(context).primaryColorDark,
Colors.blueGrey[900].withOpacity(0.8)
],
)),
height: MediaQuery.of(context).size.width * 0.35)),
width: MediaQuery.of(context).size.width,
),
CustomScrollView(controller: _controller, slivers: [
SliverList(
delegate: SliverChildListDelegate([
Padding(
padding: const EdgeInsets.only(top: 16.0, bottom: 8.0),
child: ListTile(
title: Padding(
padding: const EdgeInsets.only(top: 6.0),
child: Text('Header text',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white)),
),
subtitle: Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text('Subtitle text',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white)),
),
))
])),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return FakeItem(
executing: false,
delay: index.isOdd,
complete: false,
cancelled: false);
},
childCount: 30,
)),
])
],
);
}
}
A great solution was added by #pskink in the comments, however they seemed to have removed it. For anyone searching for an elegant solution, this is the basics of what was settled on.
You can see in the below code there is two layouts that are being handled by CustomMultiChildLayout. Hopefully this helps anyone searching for a similar solution
class ScrollList extends StatelessWidget {
final ScrollController _controller = ScrollController();
#override
Widget build(BuildContext context) {
return CustomMultiChildLayout(
delegate: ScrollingChildComponentDelegate(_controller),
children: <Widget>[
// background element layout
LayoutId(
id: 'background',
child: DecoratedBox(
decoration: BoxDecoration(
// box decoration
),
),
),
// foreground element layout
LayoutId(
id: 'scrollview',
child: CustomScrollView(
controller: _controller,
physics: AlwaysScrollableScrollPhysics(),
slivers: [
SliverToBoxAdapter(
child: ListTile(
title: Text('TitleText'),
),
subtitle: Text('SubtitleText'),
)),
),
SliverList(
delegate: SliverChildBuilderDelegate(itemBuilder,
childCount: 100),
),
],
)),
],
);
}
}
// itembuilder for child components
Widget itemBuilder(BuildContext context, int index) {
return Card(
margin: EdgeInsets.all(6),
child: ClipPath(
clipper: ShapeBorderClipper(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10))),
child: Container(
// child element content
)));
}
// controller for the animation
class ScrollingChildComponentDelegate extends MultiChildLayoutDelegate {
final ScrollController _controller;
ScrollingChildComponentDelegate(this._controller) : super(relayout: _controller);
#override
void performLayout(Size size) {
positionChild('background', Offset(0, -_controller.offset / 3));
layoutChild('background',
BoxConstraints.tightFor(width: size.width, height: size.height * 0.2));
positionChild('scrollview', Offset.zero);
layoutChild('scrollview', BoxConstraints.tight(size));
}
#override
bool shouldRelayout(covariant MultiChildLayoutDelegate oldDelegate) => true;
}
Currently, I have a String with 12 words which is extracted from an Argument from another page:
Widget build(BuildContext context) {
final String seed = ModalRoute.of(context).settings.arguments;
I then split the String and shuffle them into an Array which then display as MaterialButton in a GridView. I need that when the user click on a MaterialButton it will add the splitted word into another String Array which then display in a Container above the GridView. The GridView is built by using a for loop to add MaterialButton to <Widget>[] which call a setState() to change a bool value which in turn change the appearance of the button and add the word into the Container. At first I had trouble when clicking on a button, the setState() is called thus refreshing the whole page and reshuffle the word, but after finding Update a part of the UI on Flutter I wrap my MaterialButton in a StatefulBuilder and preventing it from rebuilding the whole page. But that also stop the Text in the container above from updating. What can I do so that when the user click a button, it change it appearance and also update the Text in the Container above?
Here is my code so far:
import 'package:flutter/material.dart';
import 'package:myapp/color_utils.dart';
class SeedConfirmPage extends StatefulWidget {
SeedConfirmPage({Key key, this.title}) : super(key: key);
final String title;
#override
_SeedConfirmPageState createState() => _SeedConfirmPageState();
}
class _SeedConfirmPageState extends State<SeedConfirmPage> {
List<bool> clicked = [
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
];
#override
Widget build(BuildContext context) {
final String seed = ModalRoute.of(context).settings.arguments;
List<String> seedArray = seed.split(" ")..shuffle();
final seedChip = <Widget>[];
List<String> seedText = [];
for (var i = 0; i < seedArray.length; i++) {
seedChip.add(StatefulBuilder(builder: (BuildContext context, setState) {
return MaterialButton(
color: clicked[i] ? colorBlack : Colors.white,
textColor: clicked[i] ? Colors.white : colorBlack,
minWidth: MediaQuery.of(context).size.width,
height: 32,
child: Text(
seedArray[i],
style: TextStyle(fontSize: 12),
),
onPressed: () {
setState(() {
clicked[i] = !clicked[i];
if (clicked[i]) {
seedText.add(seedArray[i]);
} else {
seedText.remove(seedArray[i]);
}
});
},
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
);
}));
}
return Scaffold(
appBar: AppBar(
backgroundColor: splashBG,
leading: BackButton(color: Colors.white),
title: Text("Back"),
),
backgroundColor: splashBG,
body: Container(
padding: EdgeInsets.only(
left: 16,
right: 16,
),
child: Column(
children: <Widget>[
Flexible(
child: Container(
width: MediaQuery.of(context).size.width / 2,
child: Image(
image: AssetImage('assets/images/logo_1.png'),
),
),
),
Container(
height: 120,
padding: EdgeInsets.all(32.0),
margin: EdgeInsets.only(top: 28, bottom: 28),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8), color: colorBlack),
child: Center(
child: Text(
seedText.toString(),
style: TextStyle(color: Colors.white),
),
),
),
Container(
width: MediaQuery.of(context).size.width,
height: 120,
child: GridView.count(
crossAxisCount: 4,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
childAspectRatio: MediaQuery.of(context).size.width /
(MediaQuery.of(context).size.height / 5),
children: seedChip,
),
),
],
),
),
);
}
}
I know this is not optimal to check the state of the button to know whether it is click or not. So a side question is what can I do to check the if the button has been clicked yet?
EDIT
Here is the screenshot of what I am trying to archive:
Before user press:
After user press:
You can do it in the same ways you changed button colors. In MaterialButton add this to child
child: clicked[i] ?
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(seedArray[i],style: TextStyle(fontSize: 12),),
Text('x',style: TextStyle(fontSize: 12),),
]) :
Text(seedArray[i],style: TextStyle(fontSize: 12),),
How can I make a scrolling view in Flutter in which a left and bottom portion of the screen is fixed (axes), then the rest of the screen can be scrolled horizontally to the right, or vertically upward. (imagine scrolling a graph with two axes .. see image)
Very interesting question. After looking through the docs I couldn't find a widget that would fit this scenario. So I decided to search a bit on pub.dev for a plugin that could make this happen.
Found it: https://pub.dev/packages/bidirectional_scroll_view
The plugin does a fairly good job of scrolling content on both axis, but to get what you are looking for ("fixed portion of on left and bottom") you are gonna have to structure your page accordingly. I decided to go with Stack and Align widgets, here is what it looks like:
See the full code on a working DartPad: https://dartpad.dev/10573c0e9bfa7f1f8212326b795d8628
Or take a look at the code bellow (don't forget to include bidirectional_scroll_view in your project):
void main() {
runApp(new MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
BidirectionalScrollViewPlugin _plugin;
double fixedAxisSpace = 100.0;
double biDirectContentWidth = 4096.0;
double biDirectContentHeight = 4096.0;
#override
void initState() {
super.initState();
_plugin = new BidirectionalScrollViewPlugin(
child: _buildWidgets(),
velocityFactor: 0.0,
);
}
void _snapToZeroZero(BuildContext context){
double yOffset = biDirectContentHeight + fixedAxisSpace - context.size.height;
_plugin.offset = new Offset(0, yOffset);
}
#override
Widget build(BuildContext context) {
final btnSnapToZeroZero = Padding(
padding: EdgeInsets.all(10.0),
child:FlatButton(
color: Colors.black,
shape: RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(12.0),
),
onPressed: () { _snapToZeroZero(context); },
child: Text(
"Snap to 0.0",
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white),
),
)
);
return new MaterialApp(
debugShowCheckedModeBanner: false,
home: new Scaffold(
body: Stack(
children: <Widget>[
_plugin, // BidirectionalScrollViewPlugin, goes 1st because we want it to sit on the bottom layer
Align( // Y Axis goes over _plugin, it is aligned to topLeft
alignment: Alignment.topLeft,
child: Column(
children: <Widget> [
Expanded(
child: Container(
width: fixedAxisSpace,
decoration: BoxDecoration(
color: Colors.white, // change to Colors.white70 too se what is going on "behind the scene"
border: Border(
right: BorderSide(width: 1.0, color: Colors.black),
),
),
child: Center(child: VerticalTextWidget("FIXED _ Y AXIS", 22))
),
),
SizedBox(height: fixedAxisSpace),
]
),
),
Align( // X Axis goes over _plugin and Y Axis, it is aligned to bottomLeft
alignment: Alignment.bottomLeft,
child: Row(
children: <Widget> [
SizedBox(width: fixedAxisSpace),
Expanded(
child: Container(
height: fixedAxisSpace,
decoration: BoxDecoration(
color: Colors.white, // change to Colors.white70 too se what is going on "behind the scene"
border: Border(
top: BorderSide(width: 1.0, color: Colors.black),
),
),
child: Center(child: Text("FIXED | X AXIS", style: TextStyle(fontSize: 22)))
),
),
]
),
),
Align( // this little square is optional, I use it to put a handy little button over everything else at the bottom left corner.
alignment: Alignment.bottomLeft,
child: Container(
color: Colors.white, // change to Colors.white70 too se what is going on "behind the scene"
height: fixedAxisSpace,
width: fixedAxisSpace,
child: btnSnapToZeroZero
),
),
],
)
)
);
}
// put your large bidirectional content here
Widget _buildWidgets() {
return new Padding(
padding: EdgeInsets.fromLTRB(100, 0, 0, 100),
child: SizedBox(
width: biDirectContentWidth,
height: biDirectContentHeight,
child: Image.network(
'https://i.stack.imgur.com/j1ItQ.png?s=328&g=1',
repeat: ImageRepeat.repeat,
alignment: Alignment.bottomLeft
),
)
);
}
}
VerticalTextWidget:
class VerticalTextWidget extends StatelessWidget {
final String text;
final double size;
const VerticalTextWidget(this.text, this.size);
#override
Widget build(BuildContext context) {
return Wrap(
direction: Axis.vertical,
alignment: WrapAlignment.center,
children: text.split("").map((string) => Text(string, style: TextStyle(fontSize: size))).toList(),
);
}
}