Draw outside listview bounds in Flutter - flutter

I want to transform my item that it is bigger than the listview itself. (intention for focused navigation)
My List:
Container(
height: 100,
child: ListView.builder(
itemBuilder: (context, index) => HomeItem(title: '$index'),
scrollDirection: Axis.horizontal,
),
),
My Item:
class HomeItem extends StatelessWidget {
final String title;
final bool expand;
const HomeItem({
#required this.title,
this.expand = false,
});
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: ThemeDimens.padding8),
child: Transform.scale(
scale: expand ? 1.5 : 1,
child: AnimatedContainer(
width: 50,
height: 100,
color: expand ? ThemeColors.accent : ThemeColors.primary,
duration: ThemeDurations.shortAnimationDuration(),
child: Center(
child: Text(title),
),
),
),
);
}
}
Current behaviour
Expected behaviour

If you try to use OverflowBox or Transform, content of an item will still clip and won't be drawn outside of its bounding box. But it's possible to use Overlay to draw an element on top of list and position it on a specific list item, though it's a bit complicated.
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
final elements = List.generate(12, (i) => i);
int selectedIndex;
OverlayEntry overlayEntry;
List<LayerLink> layerLinks;
#override
void initState() {
super.initState();
// Creating a layer link for each list cell
layerLinks = List.generate(elements.length, (i) => LayerLink());
}
void createOverlayEntry(int i, BuildContext context) {
// Removing an overlay entry, if there was one
overlayEntry?.remove();
final renderBox = context.findRenderObject() as RenderBox;
final size = renderBox.size;
final offset = renderBox.localToGlobal(Offset.zero);
// Creating a new overlay entry linked to specific list element
overlayEntry = OverlayEntry(
builder: (context) => Positioned(
left: 0,
top: 0,
child: CompositedTransformFollower(
link: layerLinks[i],
showWhenUnlinked: false,
offset: Offset(-20, 0),
child: Material(
color: Colors.yellow,
child: InkWell(
onTap: () {
setState(() {
selectedIndex = null;
});
overlayEntry?.remove();
overlayEntry = null;
},
child: Container(
alignment: Alignment.center,
width: 70,
height: elementHeight,
child: Text('$i')
),
)
),
)
)
);
// Inserting an entry
Overlay.of(context).insert(overlayEntry);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
height: elementHeight,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: elements.length,
itemBuilder: (c, i) {
return CompositedTransformTarget(
link: layerLinks[i],
child: Material(
color: Colors.red,
child: InkWell(
onTap: () {
setState(() {
selectedIndex = i;
});
createOverlayEntry(i, context);
},
child: Container(
alignment: Alignment.center,
width: 30,
child: Text('${elements[i]}'),
),
),
),
);
},
separatorBuilder: (c, i) {
return Container(width: 10, height: 10);
},
),
),
);
}
}

Related

CupertinoSliverRefreshControl with horizontal ListView

I have horizontal ListView.builder and CupertinoSliverRefreshControl, so when it reaches the end, I want to display Loading indicator, but for some reason I am getting error
Null check operator used on a null value
The relevant error-causing widget was
CustomScrollView
lib/sliver_loading.dart:19
The most unclear part is that CupertinoSliverRefreshControl works fine with Vertical ListView.builder, but when I change Axis on horizontal it rises this above error.
Here is a code :
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(15),
child: CustomScrollView(
scrollDirection: Axis.horizontal, // Here is when Error rise
slivers: [
SliverToBoxAdapter(
child: SizedBox(
height: 200,
child: ListView.builder(
scrollDirection: Axis.horizontal,
primary: false,
shrinkWrap: true,
itemCount: 4,
itemBuilder: (context, index) {
return Container(
width: 100,
height: 200,
color: colors[index],
);
},
),
),
),
CupertinoSliverRefreshControl(
onRefresh: () async {
await Future.delayed(Duration(seconds: 3));
print('loaded');
},
),
],
),
),
);
}
Can anyone explain me, why is this happening and what are the solutions?
There is a workaround with current snippet instead of using CupertinoSliverRefreshControl return row with loading widget for last item. Also wrap Container with Center.
itemBuilder: (context, index) {
return index == 13 // items length-1
? Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
margin: EdgeInsets.all(20),
width: 100,
height: 200,
color: Colors.cyanAccent,
),
CircularProgressIndicator(),
],
)
:Center( child: Container(
margin: EdgeInsets.all(20),
width: 100,
height: 200,
color: Colors.amber,
));
},
If you do use ListView, you can use ScrollController with listener and get position to load data using controller.position.maxScrollExtent* .9 ;load more on 90% scroll.
Also, using the same directional multi-scrollabe widgets is not necessary. We can skip using ListView and use SliverList. While the width is fixed, we can compare the items' length and current scroll position to using the controller.
final ScrollController controller = ScrollController();
#override
void initState() {
super.initState();
controller.addListener(() {
print(controller.offset);
//14 total item , I am using 90%
if (controller.offset > 100 * 14 * .9) {
// you may encounter multiple call use another flag or null to handle this
print("load more");
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(15),
child: CustomScrollView(
scrollDirection: Axis.horizontal,
controller: controller,
slivers: [
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => index == 13 // items length-1
? Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
margin: EdgeInsets.all(20),
width: 100,
height: 200,
color: Colors.cyanAccent,
),
CircularProgressIndicator(),
],
)
: Center(
child: Container(
margin: EdgeInsets.all(20),
width: 100,
height: 200,
color: Colors.amber,
)),
childCount: 14,
),
),
],
),
),
);
}
}
Okay, so here is a way how I solved this problem. Since CupertinoSliverRefreshControl does not work with horizontal ListView.builder, I decided to use CupertinoActivityIndicator and CupertinoActivityIndicator.partiallyRevealed.
When ListView reaches to the end, I am calculating distance between ListView.builder() and int distance and updating double progress for CupertinoActivityIndicator.partiallyRevealed, next when progress reaches 1.0 I just replace CupertinoActivityIndicator.partiallyRevealed with CupertinoActivityIndicator changing bool isActive value to true.
Finally it works like CupertinoSliverRefreshControl, just without slivers :).
Code Example
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class HorizontalLoader extends StatefulWidget {
const HorizontalLoader({Key? key}) : super(key: key);
static final colors = [
Colors.red,
Colors.indigoAccent,
Colors.purple,
Colors.amberAccent,
Colors.orange,
Colors.purple,
Colors.cyanAccent,
Colors.red,
Colors.indigoAccent,
Colors.purple,
];
#override
State<HorizontalLoader> createState() => _HorizontalLoaderState();
}
class _HorizontalLoaderState extends State<HorizontalLoader> {
int distance = 70; // offset
bool isActive = false;
double progress = 0.0;
// Base logic. you can also use this logic with ScrollController()
bool _handleNotification(ScrollNotification notify) {
double outRangeLoading = distance + notify.metrics.maxScrollExtent;
double currentPixel = notify.metrics.pixels;
if (notify.metrics.extentAfter <= 0.0) {
if (currentPixel >= outRangeLoading) {
networkLoader();
}
calculateProgress(outRangeLoading, currentPixel);
}
return true;
}
// Some math
void calculateProgress(outRangeLoading, currentPixel) {
double current, currentAsPrecent;
current = outRangeLoading - currentPixel;
currentAsPrecent = (100 * current) / distance;
setState(() {
progress = (100 - currentAsPrecent) * 0.01;
});
}
// To simulate loading data from Network
void networkLoader() async {
isActive = true;
await Future.delayed(Duration(seconds: 3));
isActive = false;
setState(() {
progress = 0.0;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.only(top: 200, bottom: 200),
child: Stack(
children: [
Positioned(
right: 15,
top: 210,
child: isActive
? CupertinoActivityIndicator()
: CupertinoActivityIndicator.partiallyRevealed(
progress: progress,
),
),
NotificationListener<ScrollNotification>(
onNotification: _handleNotification,
child: ListView.builder(
scrollDirection: Axis.horizontal,
physics: const BouncingScrollPhysics(),
itemCount: HorizontalLoader.colors.length + 1,
itemBuilder: (context, index) {
if (index == HorizontalLoader.colors.length) {
return isActive ? SizedBox(width: 50) : SizedBox();
}
return Container(
width: 100,
height: 100,
color: HorizontalLoader.colors[index],
);
},
),
),
],
),
),
);
}
}

How to change opacity of text and angle of icon while using slider button in flutter

I want that as I move my slider button towards right, the opacity of text decreases and arrow icon rotates exactly oppposite, i.e. it strts rotating and at last last it should point backwards. I want to use opacity and Transform.rotate widgets, but how do I keep updating the value of dx ,so I can divide it with total width of container and use the fraction for calculation.
If there is another way, please do tell me.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:passenger_flutter_app/utils/colors.dart';
import 'package:passenger_flutter_app/widgets/custom_sliding_button.dart';
class CommonSwipeButton extends StatelessWidget {
final String? buttonText1;
final String buttonText2;
final VoidCallback buttonCallBack2;
final bool isInfo;
final VoidCallback? buttonCallBack1;
final Widget itemWidget;
CommonSwipeButton(
{this.buttonCallBack1,
required this.buttonCallBack2,
this.isInfo = false,
this.buttonText1,
required this.buttonText2,
required this.itemWidget});
#override
Widget build(BuildContext context) {
return Container(
child: Column(
//crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Padding(padding: const EdgeInsets.only(left: 16.0, right: 16.0, bottom: 16.0, top: 16), child: itemWidget),
Padding(
padding:
const EdgeInsets.only(bottom: 16.0, left: 16.0, right: 16.0),
child: Align(
alignment: Alignment.center,
child: SizedBox(
width: MediaQuery.of(context).size.width,
height: 44,
child: CustomSlidingButton(
//text: buttonText2,
),
),
),
)
],
),
);
}
}
/*
class SwipeButton extends StatefulWidget {
final ValueChanged<double>? valueChanged;
final String? text;
final Function? callBack;
SwipeButton({this.valueChanged, this.text, this.callBack});
#override
SwipeButtonState createState() {
return new SwipeButtonState();
}
}
class SwipeButtonState extends State<SwipeButton> {
ValueNotifier<double> valueListener = ValueNotifier(.0);
GlobalKey swipeKey = GlobalKey();
ValueNotifier<double> x=ValueNotifier<double>(0);
ValueNotifier<bool> isVisible = ValueNotifier<bool>(true);
#override
void initState() {
valueListener.addListener(notifyParent);
super.initState();
}
void notifyParent() {
if (widget.valueChanged != null) {
widget.valueChanged!(valueListener.value);
}
}
void getPos(double totalSize) {
RenderBox box = swipeKey.currentContext?.findRenderObject() as RenderBox;
Offset position = box.localToGlobal(Offset.zero); //this is global position
x.value = position.dx;
print(x);
if(x.value>355) {
print("Reached");
isVisible.value=false;
}
}
#override
Widget build(BuildContext context) {
return Container(
color: colorPrimary,
height: 40.0,
padding: EdgeInsets.symmetric(horizontal: 10.0),
child: Stack(
children: [
Center(
child: Padding(
padding: const EdgeInsets.only(left: 10.0),
child: Text(
"${widget.text}",
style: TextStyle(
color: Colors.white,
fontSize: 17,
),
),
),
),
Builder(
builder: (context) {
final handle = GestureDetector(
onHorizontalDragUpdate: (details) {
valueListener.value = (valueListener.value +
details.delta.dx / context.size!.width)
.clamp(.0, 1.0);
getPos(context.size!.width-5);
print(context.size?.width);
},
child: ValueListenableBuilder(
valueListenable: isVisible,
builder: (BuildContext context, bool val, Widget? child) {
return Container(
key: swipeKey,
height: 25.0,
width: 25.0,
color: val ? Colors.white : colorPrimary,
child: Center(
child: ValueListenableBuilder(
valueListenable: x,
builder: (BuildContext context, double d, Widget? child) {
return Transform.rotate(
angle: -pi*(d/350),
child: Icon(
Icons.arrow_forward,
color: Colors.orange,
size: 12,
),
);
},
),
),
);
},
),
);
return AnimatedBuilder(
animation: valueListener,
builder: (context, child) {
return Align(
alignment: Alignment(valueListener.value * 2 - 1, 0),
child: child,
);
},
child: handle,
);
},
),
],
),
);
}
}*/
You can use Slider widget from Flutter framework and update a local variable in the onChange function:
Slider(
value: _currentSliderValue,
max: 100, //or any max value you need
onChanged: (double value) {
setState(() {
_value = value;
});
},
);
And the _value variable you will use in Opacity and Transform widgets.

Move an item from one list to another with animation in Flutter

I have two vertical lists, one on the left side and the other one on the right, let's call them "Selected List" and "Unselected List".
I want the items in Unselected List to Animate from left side to the right side of the screen and add to Selected List.
the other items should fill the empty space in Unselected List and items in Selected List should free up the space for new item.
Here's the Ui
My Code:
class AddToFave extends StatefulWidget {
const AddToFave({Key? key}) : super(key: key);
#override
_AddToFaveState createState() => _AddToFaveState();
}
class _AddToFaveState extends State<AddToFave> {
List<String> unselected = [ '1','2','3','4','5','6','7','8','9','10'];
List<String> selected = [];
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
width: MediaQuery.of(context).size.width / 5,
height: MediaQuery.of(context).size.height,
child: ListView.builder(
itemCount: selected.length,
itemBuilder: (context, index) {
return InkWell(
onTap: () {
unselected.add(selected[index]);
selected.removeAt(index);
setState(() {});
},
child: Container(
width: MediaQuery.of(context).size.width / 5,
height: MediaQuery.of(context).size.width / 5,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(
MediaQuery.of(context).size.width / 5)),
child: Center(
child: Text(
selected[index],
style: TextStyle(color: Colors.white),
)),
),
);
}),
),
Container(
width: MediaQuery.of(context).size.width / 5,
height: MediaQuery.of(context).size.height,
child: ListView.builder(
itemCount: unselected.length,
itemBuilder: (context, index) {
return InkWell(
onTap: () {
selected.add(unselected[index]);
unselected.removeAt(index);
setState(() {});
},
child: Container(
width: MediaQuery.of(context).size.width / 5,
height: MediaQuery.of(context).size.width / 5,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(
MediaQuery.of(context).size.width / 5)),
child: Center(
child: Text(
unselected[index],
style: TextStyle(color: Colors.white),
)),
),
);
}),
),
],
),
),
);
}
}
Thank you in advance.
This task can be broken into 2 parts.
First, use an AnimatedList instead of a regular ListView, so that when an item is removed, you can control its "exit animation" and shrink its size, thus making other items slowly move upwards to fill in its spot.
Secondly, while the item is being removed from the first list, make an OverlayEntry and animate its position, to create an illusion of the item flying. Once the flying is finished, we can remove the overlay and insert the item in the actual destination list.
Full source code for you to use, as a starting point:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: TwoAnimatedListDemo(),
);
}
}
class TwoAnimatedListDemo extends StatefulWidget {
const TwoAnimatedListDemo({Key? key}) : super(key: key);
#override
_TwoAnimatedListDemoState createState() => _TwoAnimatedListDemoState();
}
class _TwoAnimatedListDemoState extends State<TwoAnimatedListDemo> {
final List<String> _unselected = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
final List<String> _selected = [];
final _unselectedListKey = GlobalKey<AnimatedListState>();
final _selectedListKey = GlobalKey<AnimatedListState>();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Two Animated List Demo'),
),
body: Row(
children: [
SizedBox(
width: 56,
child: AnimatedList(
key: _unselectedListKey,
initialItemCount: _unselected.length,
itemBuilder: (context, index, animation) {
return InkWell(
onTap: () => _moveItem(
fromIndex: index,
fromList: _unselected,
fromKey: _unselectedListKey,
toList: _selected,
toKey: _selectedListKey,
),
child: Item(text: _unselected[index]),
);
},
),
),
Spacer(),
SizedBox(
width: 56,
child: AnimatedList(
key: _selectedListKey,
initialItemCount: _selected.length,
itemBuilder: (context, index, animation) {
return InkWell(
onTap: () => _moveItem(
fromIndex: index,
fromList: _selected,
fromKey: _selectedListKey,
toList: _unselected,
toKey: _unselectedListKey,
),
child: Item(text: _selected[index]),
);
},
),
),
],
),
);
}
int _flyingCount = 0;
_moveItem({
required int fromIndex,
required List fromList,
required GlobalKey<AnimatedListState> fromKey,
required List toList,
required GlobalKey<AnimatedListState> toKey,
Duration duration = const Duration(milliseconds: 300),
}) {
final globalKey = GlobalKey();
final item = fromList.removeAt(fromIndex);
fromKey.currentState!.removeItem(
fromIndex,
(context, animation) {
return SizeTransition(
sizeFactor: animation,
child: Opacity(
key: globalKey,
opacity: 0.0,
child: Item(text: item),
),
);
},
duration: duration,
);
_flyingCount++;
WidgetsBinding.instance!.addPostFrameCallback((timeStamp) async {
// Find the starting position of the moving item, which is exactly the
// gap its leaving behind, in the original list.
final box1 = globalKey.currentContext!.findRenderObject() as RenderBox;
final pos1 = box1.localToGlobal(Offset.zero);
// Find the destination position of the moving item, which is at the
// end of the destination list.
final box2 = toKey.currentContext!.findRenderObject() as RenderBox;
final box2height = box1.size.height * (toList.length + _flyingCount - 1);
final pos2 = box2.localToGlobal(Offset(0, box2height));
// Insert an overlay to "fly over" the item between two lists.
final entry = OverlayEntry(builder: (BuildContext context) {
return TweenAnimationBuilder(
tween: Tween<Offset>(begin: pos1, end: pos2),
duration: duration,
builder: (_, Offset value, child) {
return Positioned(
left: value.dx,
top: value.dy,
child: Item(text: item),
);
},
);
});
Overlay.of(context)!.insert(entry);
await Future.delayed(duration);
entry.remove();
toList.add(item);
toKey.currentState!.insertItem(toList.length - 1);
_flyingCount--;
});
}
}
class Item extends StatelessWidget {
final String text;
const Item({Key? key, required this.text}) : super(key: key);
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(4.0),
child: CircleAvatar(
child: Text(text),
radius: 24,
),
);
}
}

Flutter NestedScrollView header bounce with TabBar and TabbarView

Header pull to refresh can be pulled up when I want to drag it up.
Tabbar sticky.
ListView can swipe to another page.
100% Custom Widget. It supports to change the header value and also support swipe opeartions.
It is not actually a TabBar Widget but it Works exactly like that
import 'package:flutter/material.dart';
import 'dart:math';
class NestedScrolls extends StatefulWidget {
static const listHeader = ['Pakistan', 'China','Iran','Turkey'];
#override
_NestedScrollsState createState() => _NestedScrollsState();
}
class _NestedScrollsState extends State<NestedScrolls> {
var position=0;
var topHeader;
Widget? applyWidget() {
switch(position){
case 0:
setState(() {
topHeader = NestedScrolls.listHeader[0];
});
// return widget if user click over pakistan in tab bar
return grid();
case 1:
setState(() {
topHeader = NestedScrolls.listHeader[1];
});
return list();
case 2:
setState(() {
topHeader = NestedScrolls.listHeader[2];
});
return Container(color: Colors.blue,
child: Center(child: Text(topHeader),),);
case 3:
setState(() {
topHeader = NestedScrolls.listHeader[3];
});
return Container(color: Colors.orange,
child: Center(child: Text(topHeader),),);
}
}
#override
void initState() {
// TODO: implement initState
super.initState();
//initial header name when activity start first time
topHeader = NestedScrolls.listHeader[0];
}
#override
Widget build(BuildContext context) {
topHeader = topHeader;
return Scaffold(
// Persistent AppBar that never scrolls
appBar: AppBar(
title: Text('AppBar'),
elevation: 0.0,
),
body:
Column(
children: <Widget>[
///header
Container(
alignment: Alignment.center,
color: Colors.blueGrey,
height: 90,
child: Text(NestedScrolls.listHeader[position]),
),
/// tabBar
Container(
height: 60,
width: MediaQuery.of(context).size.width,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: NestedScrolls.listHeader.length,
itemBuilder: (con, index) {
return GestureDetector(
onTap: () => setState(() {
position=index;
topHeader = NestedScrolls.listHeader[index];
}),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 2.0, horizontal: 10),
child: Container(alignment: Alignment.center,
width: 100,
color: topHeader==NestedScrolls.listHeader[index]?Colors.black26:Colors.transparent,
child: Text(NestedScrolls.listHeader[index])),
),
);
}),
),
///Widget
Expanded(
child: GestureDetector(
// onHorizontalDragStart: (left){
// print('left : ${left.localPosition.direction}');
// // left.globalPosition.dx
//
// },
onHorizontalDragEnd: (start){
print('start : ${start.velocity.pixelsPerSecond.dx}');
if((start.velocity.pixelsPerSecond.dx)<-700){
if(position<NestedScrolls.listHeader.length-1 && position>=0)
setState(() {
position=position+1;
});
}else{}
if((start.velocity.pixelsPerSecond.dx)>900){
if(position<=NestedScrolls.listHeader.length-1 && position>0)
setState(() {
position=position-1;
});
}
print(position);
},
child: applyWidget()),
),
],
),
);
}
list() {
return SingleChildScrollView(scrollDirection: Axis.vertical,
child: Container(
child: Column(children: [
for(var color in Colors.primaries)
Container(color: color, height: 100.0)
],),
),
);
}
grid() {
return GridView.count(
padding: EdgeInsets.zero,
crossAxisCount: 3,
children: Colors.primaries.map((color) {
return Container(color: color, height: 100.0);
}).toList(),
);
}
}

Create image slider with pageView

i tried create a image slider like this image
i tried create this slider with PageView but a have a problem with pageview
and my code :
class SlideMusic extends StatefulWidget{
final Size _size;
final List<String> _listArts;
double itemExtent;
SlideMusic(this._size,this._listArts){
itemExtent = _size.width / 3;
}
#override
SlideMusicState createState() => SlideMusicState();
}
class SlideMusicState extends State<SlideMusic> {
double currentPage = 0;
PageController _pageController = PageController(
viewportFraction: 0.3333,
);
#override
void setState(fn) {
super.setState(fn);
_pageController.addListener((){
currentPage = _pageController.page;
});
}
#override
Widget build(BuildContext context) {
return SizedBox(
width: widget._size.width,
height: widget.itemExtent,
child: Container(
color: Colors.green,
child: NotificationListener<ScrollNotification>(
onNotification: _onNotification,
child: PageView.builder(
controller: _pageController,
physics: BouncingScrollPhysics(),
itemCount: widget._listArts.length,
itemBuilder: (context, index){
return itemArt(widget._listArts[index], index);
},
),
)
)
);
}
bool _onNotification(ScrollNotification notification){
setState(() {
currentPage = _pageController.page;
print(currentPage);
});
}
double itemOffset(int index){
return (index - currentPage ).abs()* widget.itemExtent;
}
Widget itemArt(String image , int index){
Offset offset = Offset.zero;
if(index > currentPage.round())
offset = Offset(-itemOffset(index)/2,0);
else
offset = Offset(itemOffset(index)/2,0);
return Align(
alignment: Alignment.center,
child: Transform.translate(
offset: offset,
child: Container(
color: Colors.lightBlue,
child: LayoutBuilder(
builder: (context,constrat){
return SizedBox(
width: constrat.maxWidth - itemOffset(index)/3.5,
height: constrat.maxHeight - itemOffset(index) /3.5,
child: Center(
child: Image.asset(image)
),
);
},
)
),
)
);
}
}
Check out the Picture slider widget made with carousel_slider.
Don't mind the jankyness of GIF.
Code:
class PictureSlideShow extends StatefulWidget {
#override
_PictureSlideShowState createState() => _PictureSlideShowState();
}
class _PictureSlideShowState extends State<PictureSlideShow> {
#override
Widget build(BuildContext context) {
return Center(
child: CarouselSlider(
height: 200.0,
enlargeCenterPage: true,
items: [1, 2, 3, 4, 5].map((i) {
return Builder(
builder: (BuildContext context) {
return Container(
width: 400,
height: 400,
margin: EdgeInsets.all(0.5),
decoration:
BoxDecoration(color: Colors.lightBlue[100 * (i % 5)]),
child: Center(
child: Text(
'text $i',
style: TextStyle(fontSize: 16.0),
),
),
);
},
);
}).toList(),
),
);
}
}
This is a similiar design to what you wanted.