Flutter PageView Scroll but only for widgets and not entire pages - flutter

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(),
);

Related

Parallax-style header scrolling performance in flutter

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;
}

Why does my image disappear when I scroll down?

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

Propagate click behind a widget

I have a Stack with two widgets inside.
I'm trying to detect the click on the bottom widget of the Stack, which is behind the top one.
I am using HitTestBehavior.translucent, but it only work if the GestureDetector doesn't have any child.
This is a simplified version of what i need in my app. I have a Stack which contains many tiny Cards on the screen, and there's one canvas on top of them all. Despite the canvas, I need to be able to tap on the Cards to change its content. I thought using a translucent behavior would solve my problem but it does not.
EDIT : Also, the first GestureDetector will always be in a Positioned widget.
class TestPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: SizedBox(
height: 800,
width: 400,
child: Stack(
children: [
/// 2. ... and trigger the onTap function of this widget
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
print('TAP BOTTOM');
},
child: Container(
height: 500,
width: 400,
color: Colors.deepOrange,
),
),
/// 1. I'm Trying to clic here...
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: null,
child: Container(
height: 300,
width: 400,
color: Colors.deepPurple,
),
),
],
),
),
// ),
),
);
}
}
I have a sample code with which you can achieve this:
​class TestPage extends StatelessWidget {
  #override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SizedBox(
          height: 800,
          width: 400,
          child: Stack(
            children: [
              /// 2. ... and trigger the onTap function of this widget (WIDGET_2)
              GestureDetector(
                behavior: HitTestBehavior.opaque,
                onTap: () {
                  print('TAP BOTTOM');
                },
                child: Container(
                  height: 500,
                  width: 400,
                  color: Colors.deepOrange,
                ),
              ),
              /// Able to tap bottom
              IgnorePointer(
                ignoring: true,
                child: Container(
                  height: 300,
                  width: 400,
                  color: Colors.deepPurple,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
Also sorry for posting late here.
The answer was given by VrajGohil on this issue :
https://github.com/flutter/flutter/issues/77596
Here is his solution :
class TestPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: SizedBox(
height: 800,
width: 400,
child: Stack(
children: [
/// 2. ... and trigger the onTap function of this widget (WIDGET_2)
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
print('TAP BOTTOM');
},
child: Container(
height: 500,
width: 400,
color: Colors.deepOrange,
),
),
/// Able to tap bottom
IgnorePointer(
ignoring: true,
child: Container(
height: 300,
width: 400,
color: Colors.deepPurple,
),
),
],
),
),
),
);
}
}

Image not covering full device dimensions in flutter

I am trying to add an image covering the device width and height, also this image needs have text over it. At first when adding the image I used fit: BoxFit.fill on the Image.dart file so that the image covers the whole screen and it worked. Then for having the text over the image I added Stack widget wrapping the image and the text, as soon as I did this, the the text was over the image but now the image was not covering the full screen height. Why is this problem happening?
// main.dart
void main() {
runApp(MaterialApp(home: MyApp()));
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: ImageContainer(),
);
}
}
// ImageContainer.dart
class ImageContainer extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Stack(
children: [
MainImage("assets/images/startwars.jpg"),
Positioned(
bottom: 0,
left: 0,
child: Container(
child: Text(
"someText",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.white),
)
),
)
],
)
],
),
);
}
}
// Image.dart
class MainImage extends StatelessWidget {
final String _assetPath;
MainImage(this._assetPath);
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(color: Colors.red,),
child: Expanded(
child: Image.asset(
_assetPath,
fit: BoxFit.fill,
),
),
);
}
}
If you have any questions please let me know in the comments;)
Set your Stack fit to expand:
Stack(
fit: StackFit.expand,
children: [],
);
I fixed this problem by adding constraints to the container in the Image.dart file, all I did was adding this line. After lots and lots of investigation I realized that adding MediaQuery to the height tells the app to cover the screen's height, this can be also done with width.
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(color: Colors.red,),
constraints: BoxConstraints.expand(height: MediaQuery.of(context).size.height), // add this line
child: Expanded(
child: Image.asset(
_assetPath,
fit: BoxFit.fill,
),
),
);
}
You can set Stack fit ..
Stack(
fit: StackFit.expand, ... )
An also you can add Container ( child:Image( ... ), width: double.infinity, heigh:...)

How to edit an item using GridView widget in flutter?

, I'm working at GridView in flutter but I need to just change layout for one item or some items not the all, like (height or width..etc) .
childAspectRatio option it also change all items so this is the issue I need to change specific items that I choose so how I can do that ? .
code :
class StaggeredGridExample extends StatefulWidget {
#override
_StaggeredGridExampleState createState() => _StaggeredGridExampleState();
}
class _StaggeredGridExampleState extends State<StaggeredGridExample> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: GridView.count(
crossAxisCount: 2,
childAspectRatio: 16/9,
children: [
Container(
color: Colors.green,
child: Icon(Icons.translate),
),
Container(
color: Colors.blueGrey,
child: Icon(Icons.shop),
),
Container(
color: Colors.deepOrangeAccent,
child: Icon(Icons.date_range),
),
Container(
color: Colors.cyan,
child: Icon(Icons.radio),
),
Container(
color: Colors.brown,
child: Icon(Icons.favorite_border),
),
],
));
}
}
Thanks !
gridview requires all items to be the same size. If you want to customize and use different sizes, you need to use a plugin.
url link : https://pub.dev/packages/flutter_staggered_grid_view