How to scroll or jump to position of PageView.builder or PageController in Flutter? - scroll

Issue: Unable to scroll to POSITION after loading the Pageviews using PageController *
like ViewPager scroll to specific page in Android
Widget _buildCarousel(BuildContext context, int selectedIndex) {
PageController controller = PageController(viewportFraction: 1, keepPage: true);
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SizedBox(
// you may want to use an aspect ratio here for tablet support
height: 400.0,
width: 240,
child: PageView.builder(
itemCount: assetImageList.length,
controller: controller,
itemBuilder: (BuildContext context, int itemIndex) {
return _buildCarouselItem(context, selectedIndex, itemIndex);
},
),
)
],
);
}

Finally found the answer. Just set the initialPage: mSelectedPosition attribute like:
child: PageView.builder(
itemCount: mTemplateModelList.length,
controller: PageController(initialPage: mSelectedPosition, keepPage: true, viewportFraction: 1),
itemBuilder: (BuildContext context, int itemIndex) {
return _buildCarouselItem(context, selectedIndex, itemIndex);
},
),
OR if you want to scroll the page after the button is clicked then, you can use jumpTo() method using PageController which is clearly mentioned below by another user: #android.

Currently there's 2 options to handle your request:
PageView.builder(
controller: _pageController,
itemCount: _list.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
_pageController.jumpToPage(index); // for regular jump
_pageController.animateToPage(_position, curve: Curves.decelerate, duration: Duration(milliseconds: 300)); // for animated jump. Requires a curve and a duration
},
child: Container();
);
}
),

You can use jumpTo() method to scroll position for PageView.
I have create one changePageViewPostion() method in below example:
import 'package:flutter/material.dart';
class MyPageView extends StatefulWidget {
createState() {
return StateKeeper();
}
}
class StateKeeper extends State<MyPageView> {
PageController controller = PageController(viewportFraction: 1, keepPage: true);
var currentPageValue = 0.0;
var mItemCount = 10;
#override
void initState() {
// TODO: implement initState
super.initState();
controller.addListener(() {
setState(() {
currentPageValue = controller.page;
});
});
}
void changePageViewPostion(int whichPage) {
if(controller != null){
whichPage = whichPage + 1; // because position will start from 0
double jumpPosition = MediaQuery.of(context).size.width / 2;
double orgPosition = MediaQuery.of(context).size.width / 2;
for(int i=0; i<mItemCount; i++){
controller.jumpTo(jumpPosition);
if(i==whichPage){
break;
}
jumpPosition = jumpPosition + orgPosition;
}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('PageView position change'),
),
body: PageView.builder(
controller: controller,
itemBuilder: (context, position) {
return Container(
color: position % 2 == 0 ? Colors.blue : Colors.pink,
child: Column(
children: <Widget>[
Center(
child: Text(
"Page " + (position + 1).toString(),
style: TextStyle(color: Colors.white, fontSize: 22.0),
),
),
Align(
alignment: FractionalOffset.bottomCenter,
child: Padding(padding: EdgeInsets.only(bottom: 20),
child: FloatingActionButton(
elevation: 0.0,
child: new Icon(Icons.check),
backgroundColor: new Color(0xFFE57373),
onPressed: (){
changePageViewPostion(5);
}
),),
),
],
),
);
},
itemCount: mItemCount,
)
);
}
}
We can get current position with controller as below:
controller.addListener(() {
setState(() {
currentPageValue = controller.page.toInt();
print((currentPageValue + 1).toString());
});
});
Hope it helps :)

If you just want to scroll to the next page using a button, you can simply use the following method.
//Create a PageController variable
late PageController _pageController;
//Initialize the variable in the init method.
#override
void initState() {
_pageController = PageController(
initialPage: _activePage, keepPage: true, viewportFraction: 1);
super.initState();
}
//Use this nextPage() method in the onPressed() method.
onPressed: () {
setState(() {
_activePage < 2
? _activePage++
: Navigator.pushReplacementNamed(
context, LoginScreen.id);
});
_pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.decelerate,
);
}

Related

Flutter AnimatedList still janky?

I'm currently using an AnimatedList in my Flutter app and having problems with the way removed list items are animated out. The animation itself works as expected but once the removed item finishes animating, it just disappears causing the other widgets to jump into its place. I had expected the other items to transition into the place of the removed item ...
I tried wrapping my list items with a ScaleTransition but that didn't help - the other list items still do not react to the removed item until it has finished the animation.
This kind of defies the purpose of AnimatedList, right? Or did I do something wrong? The "Widget of the week" video about AnimatedList clearly shows that list items react to newly inserted items by changing their position ...
Here is my code:
#override
Widget build(BuildContext context) {
return AnimatedList(
padding: EdgeInsets.only(top: REGULAR_DIM,
bottom: REGULAR_DIM + kBottomNavigationBarHeight),
initialItemCount: data.length,
itemBuilder: (context, index, animation) {
return MyCustomWidget(
data: data[index],
animation: animation,
disabled: false
);
},
);
}
class MyCustomWidget extends AnimatedWidget {
final MyModel data;
final bool disabled;
MyCustomWidget({
#required this.data,
#required Animation<double> animation,
this.disabled = false
}) : super(listenable: animation);
Animation<double> get animation => listenable;
#override
Widget build(BuildContext context) {
final content = ... ;
return ScaleTransition(
scale: CurvedAnimation(
parent: animation,
curve: Interval(0, 0.25)
).drive(Tween(begin: 0, end: 1)),
child: FadeTransition(
opacity: animation,
child: SlideTransition(
position: animation.drive(
Tween(begin: Offset(-1, 0), end: Offset(0, 0))
.chain(CurveTween(curve: Curves.easeOutCubic))),
child: content,
),
),
);
}
}
And then somewhere in the MyCustomWidget I invoke this function:
void _remove(BuildContext context) async {
final animatedList = AnimatedList.of(context);
// obtain myModel asynchronously
myModel.removeData(data);
animatedList.removeItem(index, (context, animation) => MyCustomWidget(
data: data,
animation: animation,
disabled: true,
), duration: Duration(milliseconds: 350));
}
The key is to trigger two Transitions one SlideTranstion() and another SizeTransition to eliminate to jump when the item is removed
here is some sample code
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(title: Text('Update AnimatedList data')),
body: BodyWidget(),
),
);
}
}
class BodyWidget extends StatefulWidget {
#override
BodyWidgetState createState() {
return new BodyWidgetState();
}
}
class BodyWidgetState extends State<BodyWidget>
with SingleTickerProviderStateMixin {
// the GlobalKey is needed to animate the list
final GlobalKey<AnimatedListState> _listKey = GlobalKey(); // backing data
List<String> _data = ['Horse', 'Cow', 'Camel', 'Sheep', 'Goat'];
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
SizedBox(
height: 400,
child: AnimatedList(
key: _listKey,
initialItemCount: _data.length,
itemBuilder: (context, index, animation) {
return _buildItem(
_data[index],
animation,
);
},
),
),
RaisedButton(
child: Text(
'Insert single item',
style: TextStyle(fontSize: 20),
),
onPressed: () {
_onButtonPress();
},
),
RaisedButton(
child: Text(
'Remove single item',
style: TextStyle(fontSize: 20),
),
onPressed: () {
_removeSingleItems();
},
),
],
);
}
Widget _buildItem(String item, Animation<double> animation, {direction: 0}) {
return (direction == 0)
? SizeTransition(
sizeFactor: animation,
child: Card(
color: Colors.amber,
child: ListTile(
title: Text(
item,
style: TextStyle(fontSize: 20),
),
),
),
)
: Stack(
children: [
SizeTransition(
sizeFactor: animation,
child: Card(
color: Colors.transparent,
child: ListTile(
title: Text(
item,
style: TextStyle(fontSize: 20),
),
),
),
),
Align(
alignment: Alignment.topCenter,
heightFactor: 0,
child: SlideTransition(
position: animation
.drive(Tween(begin: Offset(-1, 0), end: Offset(0, 0))),
child: Card(
color: Colors.red,
child: ListTile(
title: Text(
item,
style: TextStyle(fontSize: 20),
),
),
),
),
),
],
);
}
void _onButtonPress() {
_insertSingleItem();
}
void _insertSingleItem() {
String item = "Pig";
int insertIndex = 2;
_data.insert(insertIndex, item);
_listKey.currentState.insertItem(insertIndex);
}
void _removeSingleItems() {
int removeIndex = 2;
String removedItem = _data.removeAt(removeIndex);
// This builder is just so that the animation has something
// to work with before it disappears from view since the
// original has already been deleted.
AnimatedListRemovedItemBuilder builder = (context, animation) {
// A method to build the Card widget.
return _buildItem(removedItem, animation, direction: 1);
};
_listKey.currentState.removeItem(removeIndex, builder);
}
void _updateSingleItem() {
final newValue = 'I like sheep';
final index = 3;
setState(() {
_data[index] = newValue;
});
}
}
enter code here
You need to test the performance with the release version of your app.

How can I select and upload Multiple images in Flutter

I tried to find how to select and upload multiple images in flutter but most of the plugins are not working or I did not understand them well. I found little application but it selects and upload only one picture. How to change this code that user can select and upload multiple pictures or is there any other alternatives. Please write in details, i am freshman in coding. Thanks in advance.
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:image_picker/image_picker.dart';
import 'package:mime/mime.dart';
import 'dart:convert';
import 'package:http_parser/http_parser.dart';
import 'package:toast/toast.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Image Upload Demo',
theme: ThemeData(primarySwatch: Colors.pink),
home: ImageInput());
}
}
class ImageInput extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _ImageInput();
}
}
class _ImageInput extends State<ImageInput> {
// To store the file provided by the image_picker
File _imageFile;
// To track the file uploading state
bool _isUploading = false;
String baseUrl = 'http://YOUR_IPV4_ADDRESS/flutterdemoapi/api.php';
void _getImage(BuildContext context, ImageSource source) async {
File image = await ImagePicker.pickImage(source: source);
setState(() {
_imageFile = image;
});
// Closes the bottom sheet
Navigator.pop(context);
}
Future<Map<String, dynamic>> _uploadImage(File image) async {
setState(() {
_isUploading = true;
});
// Find the mime type of the selected file by looking at the header bytes of the file
final mimeTypeData =
lookupMimeType(image.path, headerBytes: [0xFF, 0xD8]).split('/');
// Intilize the multipart request
final imageUploadRequest =
http.MultipartRequest('POST', Uri.parse(baseUrl));
// Attach the file in the request
final file = await http.MultipartFile.fromPath('image', image.path,
contentType: MediaType(mimeTypeData[0], mimeTypeData[1]));
// Explicitly pass the extension of the image with request body
// Since image_picker has some bugs due which it mixes up
// image extension with file name like this filenamejpge
// Which creates some problem at the server side to manage
// or verify the file extension
imageUploadRequest.fields['ext'] = mimeTypeData[1];
imageUploadRequest.files.add(file);
try {
final streamedResponse = await imageUploadRequest.send();
final response = await http.Response.fromStream(streamedResponse);
if (response.statusCode != 200) {
return null;
}
final Map<String, dynamic> responseData = json.decode(response.body);
_resetState();
return responseData;
} catch (e) {
print(e);
return null;
}
}
void _startUploading() async {
final Map<String, dynamic> response = await _uploadImage(_imageFile);
print(response);
// Check if any error occured
if (response == null || response.containsKey("error")) {
Toast.show("Image Upload Failed!!!", context,
duration: Toast.LENGTH_LONG, gravity: Toast.BOTTOM);
} else {
Toast.show("Image Uploaded Successfully!!!", context,
duration: Toast.LENGTH_LONG, gravity: Toast.BOTTOM);
}
}
void _resetState() {
setState(() {
_isUploading = false;
_imageFile = null;
});
}
void _openImagePickerModal(BuildContext context) {
final flatButtonColor = Theme.of(context).primaryColor;
print('Image Picker Modal Called');
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return Container(
height: 150.0,
padding: EdgeInsets.all(10.0),
child: Column(
children: <Widget>[
Text(
'Pick an image',
style: TextStyle(fontWeight: FontWeight.bold),
),
SizedBox(
height: 10.0,
),
FlatButton(
textColor: flatButtonColor,
child: Text('Use Camera'),
onPressed: () {
_getImage(context, ImageSource.camera);
},
),
FlatButton(
textColor: flatButtonColor,
child: Text('Use Gallery'),
onPressed: () {
_getImage(context, ImageSource.gallery);
},
),
],
),
);
});
}
Widget _buildUploadBtn() {
Widget btnWidget = Container();
if (_isUploading) {
// File is being uploaded then show a progress indicator
btnWidget = Container(
margin: EdgeInsets.only(top: 10.0),
child: CircularProgressIndicator());
} else if (!_isUploading && _imageFile != null) {
// If image is picked by the user then show a upload btn
btnWidget = Container(
margin: EdgeInsets.only(top: 10.0),
child: RaisedButton(
child: Text('Upload'),
onPressed: () {
_startUploading();
},
color: Colors.pinkAccent,
textColor: Colors.white,
),
);
}
return btnWidget;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Image Upload Demo'),
),
body: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 40.0, left: 10.0, right: 10.0),
child: OutlineButton(
onPressed: () => _openImagePickerModal(context),
borderSide:
BorderSide(color: Theme.of(context).accentColor, width: 1.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.camera_alt),
SizedBox(
width: 5.0,
),
Text('Add Image'),
],
),
),
),
_imageFile == null
? Text('Please pick an image')
: Image.file(
_imageFile,
fit: BoxFit.cover,
height: 300.0,
alignment: Alignment.topCenter,
width: MediaQuery.of(context).size.width,
),
_buildUploadBtn(),
],
),
);
}
}
I used that package:
dependencies:
multi_image_picker: ^4.6.7
Ex:
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:multi_image_picker/multi_image_picker.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
List<Asset> images = List<Asset>();
String _error;
#override
void initState() {
super.initState();
}
Widget buildGridView() {
if (images != null)
return GridView.count(
crossAxisCount: 3,
children: List.generate(images.length, (index) {
Asset asset = images[index];
return AssetThumb(
asset: asset,
width: 300,
height: 300,
);
}),
);
else
return Container(color: Colors.white);
}
Future<void> loadAssets() async {
setState(() {
images = List<Asset>();
});
List<Asset> resultList;
String error;
try {
resultList = await MultiImagePicker.pickImages(
maxImages: 300,
);
} on Exception catch (e) {
error = e.toString();
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
images = resultList;
if (error == null) _error = 'No Error Dectected';
});
}
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: const Text('Plugin example app'),
),
body: Column(
children: <Widget>[
Center(child: Text('Error: $_error')),
RaisedButton(
child: Text("Pick images"),
onPressed: loadAssets,
),
Expanded(
child: buildGridView(),
)
],
),
),
);
}
}
Add dependecy of image_picker:
image_picker: ^0.8.4+3
Then make a method for selectImages():
final ImagePicker imagePicker = ImagePicker();
List<XFile>? imageFileList = [];
void selectImages() async {
final List<XFile>? selectedImages = await
imagePicker.pickMultiImage();
if (selectedImages!.isNotEmpty) {
imageFileList!.addAll(selectedImages);
}
print("Image List Length:" + imageFileList!.length.toString());
setState((){});
}
Create a builder for showing selected Images:
return Scaffold(
appBar: AppBar(
title: Text('Multiple Images'),
),
body: SafeArea(
child: Column(
children: [
ElevatedButton(
onPressed: () {
selectImages();
},
child: Text('Select Images'),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: GridView.builder(
itemCount: imageFileList!.length,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3),
itemBuilder: (BuildContext context, int index) {
return Image.file(File(imageFileList![index].path),
fit: BoxFit.cover,);
}),
),
),
],
),
));
Complete Source code available in github link...
https://github.com/NishaJain24/multi_image_picker

flutter how to make Hero like animation between widgets on the same page

I have a button with a text, and when I pressed the button, a text widget with the same text is added to the same page.
I'd like to add Hero like animation between them.
I guess what I need is SlideTransition, but I don't know how to slide from one widget position to another widget position.
Is it possible to do? What widget (or class) should I look into?
Here's the code I want to do (but doesn't work since Hero doesn't work on the same page widgets):
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<String> text = [];
String buttonTag = "0";
#override
Widget build(BuildContext context) {
List<Widget> textWidgets = [];
for (int i = 0; i < text.length; ++i) {
textWidgets.add(
Padding(
padding: const EdgeInsets.all(8.0),
child: Hero(tag: "${i}", child: Text(text[i])),
)
);
}
return SafeArea(
child: Scaffold(
body: Center(
child: Column(
children: <Widget>[
RaisedButton(
child: Hero(
tag: buttonTag,
child: Text("abcde${text.length}")),
onPressed: () {
setState(() {
text.add("abcde${text.length}");
buttonTag = "${text.length}";
});
},
)
] + textWidgets,
),
),
),
);
}
}
Not exactly the answer to the question but instead of Hero (if it's possible) you can use AnimatedList to get the same result.
Code snippet
import 'package:flutter/material.dart';
import 'dart:math';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
final List<Text> _textWidgets = [];
var rng = new Random();
_addItem() {
setState(() {
_listKey.currentState.insertItem(_textWidgets.length,
duration: const Duration(milliseconds: 500));
int id = rng.nextInt(100);
_textWidgets.add(Text('item $id'));
});
}
Widget _buildItem(
BuildContext context, Text item, Animation<double> animation) {
final offsetAnimation = Tween<Offset>(
begin: Offset(1.0, 0.0),
end: Offset(0.0, 0.0),
).animate(animation);
return SlideTransition(
position: offsetAnimation,
child: SizedBox(
height: 50.0,
child: Center(
child: item,
),
),
);
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Column(
children: <Widget>[
RaisedButton(
child: Text("Add Item"),
onPressed: () {
setState(() {
_addItem();
});
},
),
Expanded(
child: AnimatedList(
key: _listKey,
initialItemCount: _textWidgets.length,
itemBuilder: (context, index, animation) {
return _buildItem(context, _textWidgets[index], animation);
},
),
),
],
),
),
);
}
}
I think it's possible by this llibrary which named: LocalHero
Implement your self
The AnimationController, Tween, AnimatedBuilder are key components.
This is a sample and code about this.
Don't use AnimatedController.animate in AnimatedBuilder builder.
evaluate is enough. Because the builder function is called every ticker frame.
Use AnimationController.animate as class member field.
class _AuthorizedState extends State<_Authorized> with SingleTickerProviderStateMixin {
late final _menuAC = AnimationController(vsync: this, duration: 200.ms);
late final isFilterOpen = ValueNotifier(false)..addListener(_handleFilterOpenChanged);
late final filterColor =
ColorTween(begin: context.color.primaryContainer, end: context.color.secondaryContainer)
.animate(_menuAC);
late final filterBorderRadius = Tween<double>(begin: 12, end: 0).animate(_menuAC);
void _handleFilterOpenChanged() {
print(isFilterOpen.value);
if (isFilterOpen.value) {
_menuAC.forward();
} else {
_menuAC.reverse();
}
}
#override
void dispose() {
_menuAC.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return AppScaffold(
child: LayoutBuilder(
builder: (context, cons) => Stack(
children: [
AnimatedBuilder(
animation: _menuAC,
builder: (context, child) {
return Positioned(
bottom: Tween(begin: 16.0, end: 0.0).evaluate(_menuAC),
right: Tween(begin: 16.0, end: 0.0).evaluate(_menuAC),
child: ClipRRect(
borderRadius: BorderRadius.only(
topLeft: const Radius.circular(12.0),
topRight: const Radius.circular(12.0),
bottomLeft:
Radius.circular(Tween<double>(begin: 12, end: 0).evaluate(_menuAC)),
bottomRight:
Radius.circular(Tween<double>(begin: 12, end: 0).evaluate(_menuAC)),
),
child: Container(
width: Tween(begin: 56.0, end: cons.maxWidth).evaluate(_menuAC),
height: Tween(begin: 56.0, end: min(cons.maxHeight * 0.7, 500.0))
.evaluate(_menuAC),
decoration: BoxDecoration(
color: filterColor.value,
),
child: child,
),
),
);
},
child: Material(
elevation: 24,
color: Colors.transparent,
child: InkWell(
onTap: () {
isFilterOpen.value = !isFilterOpen.value;
},
child: const Center(child: Icon(MdiIcons.filter)),
),
),
),
],
),
),
);
}
}

Flutter ListView.builder - How to Jump to Certain Index Programmatically

i have a screen that build using MaterialApp, DefaultTabController, Scaffold and TabBarView.
in this screen, i have body content that retreive a list of element from sqllite using StreamBuilder. i get exact 100 elements ("finite list") to be shown using ListView.
my question, using ListView.builder, How we can jump to certain index when this screen opened ?
my main screen:
...
ScrollController controller = ScrollController();
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner : false,
home: DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
backgroundColor: Pigment.fromString(UIData.primaryColor),
elevation: 0,
centerTitle: true,
title: Text(translations.text("quran").toUpperCase()),
bottom: TabBar(
tabs: [
Text("Tab1"),
Text("Tab2"),
Text("Tab3")
],
),
leading: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: InkWell(
child: SizedBox(child: Image.asset("assets/images/home.png"), height: 10, width: 1,),
onTap: () => Navigator.of(context).pop(),
)
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _scrollToIndex,
tooltip: 'Testing Index Jump',
child: Text("GO"),
),
body:
TabBarView(
children: [
Stack(
children: <Widget>[
MyDraggableScrollBar.create(
scrollController: controller,
context: context,
heightScrollThumb: 25,
child: ListView(
controller: controller,
children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(30, 15, 30, 8),
child: Container(
alignment: Alignment.center,
height: 30,
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: TextField(
style: TextStyle(color: Colors.green),
decoration: new InputDecoration(
contentPadding: EdgeInsets.all(5),
border: InputBorder.none,
filled: true,
hintStyle: new TextStyle(color: Colors.green, fontSize: 14),
prefixIcon: Icon(FontAwesomeIcons.search,color: Colors.green,size: 17,),
hintText: translations.text("search-quran"),
fillColor: Colors.grey[300],
prefixStyle: TextStyle(color: Colors.green)
),
onChanged: (val) => quranBloc.searchSurah(val),
),
)
)
),
//surah list
streamBuilderQuranSurah(context)
],
)
) // MyDraggableScrollBar
],
),
Icon(Icons.directions_transit),
Icon(Icons.directions_bike),
],
)
)));
}
Widget streamBuilderQuranSurah(BuildContext ctx){
return StreamBuilder(
stream: quranBloc.chapterStream ,
builder: (BuildContext context, AsyncSnapshot<ChaptersModel> snapshot){
if(snapshot.hasData){
return ListView.builder(
controller: controller,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount:(snapshot.data.chapters?.length ?? 0),
itemBuilder: (BuildContext context, int index) {
var chapter =
snapshot.data.chapters?.elementAt(index);
return chapterDataCell(chapter);
},
);
}
else{
return SurahItemShimmer();
}
},
);
}
...
class MyDraggableScrollBar.dart :
import 'package:draggable_scrollbar/draggable_scrollbar.dart';
import 'package:flutter/material.dart';
class MyDraggableScrollBar {
static Widget create({
#required BuildContext context,
#required ScrollController scrollController,
#required double heightScrollThumb,
#required Widget child,
}) {
return DraggableScrollbar(
alwaysVisibleScrollThumb: true,
scrollbarTimeToFade: Duration(seconds: 3),
controller: scrollController,
heightScrollThumb: heightScrollThumb,
backgroundColor: Colors.green,
scrollThumbBuilder: (
Color backgroundColor,
Animation<double> thumbAnimation,
Animation<double> labelAnimation,
double height, {
Text labelText,
BoxConstraints labelConstraints,
}) {
return InkWell(
onTap: () {},
child: Container(
height: height,
width: 7,
color: backgroundColor,
),
);
},
child: child,
);
}
}
i have tried find other solutions but seems not working, for example indexed_list_view that only support infinite list
and it seems flutter still not have feature for this, see this issue
Any Idea ?
You can use https://pub.dev/packages/scrollable_positioned_list. You can pass the initial index to the widget.
ScrollablePositionedList.builder(
initialScrollIndex: 12, //you can pass the desired index here//
itemCount: 500,
itemBuilder: (context, index) => Text('Item $index'),
itemScrollController: itemScrollController,
itemPositionsListener: itemPositionsListener,
);
General Solution:
To store anything which can be represented as a number/string/list of strings, Flutter provides a powerful easy-to-use plugin which stores the values needed to be stored along with a key. So the next time you need you'll need to retrieve or even update that value all that you'll need is that key.
To get started, add the shared_preferences plugin to the pubspec.yaml file,
dependencies:
flutter:
sdk: flutter
shared_preferences: "<newest version>"
Run flutter pub get from the terminal or if your using IntelliJ just click on Packages get(You'll find it somewhere around the top-right corner of your screen while viewing the pubspec.yaml file)
Once the above command is successfully executed, import the below file in your main.dart or concerned file.
import 'package:shared_preferences/shared_preferences.dart';
Now just attach a ScrollController to your ListView.builder() widget and make sure that the final/last offset is stored along with a specific key using shared_preferences whenever the user leaves the app in any way and is set when the initState of your concerned widget is called.
In order to know to detect changes in the state of our app and to act with accordance to it, we'll be inheriting WidgetsBindingObserver to our class.
Steps to follow:
Extend the WidgetsBindingObserver class along with the State class of your StatefulWidget.
Define a async function resumeController() as a function member of the above class.
Future<void> resumeController() async{
_sharedPreferences = await SharedPreferences.getInstance().then((_sharedPreferences){
if(_sharedPreferences.getKeys().contains("scroll-offset-0")) _scrollController= ScrollController(initialScrollOffset:_sharedPreferences.getDouble("scroll-offset-0"));
else _sharedPreferences.setDouble("scroll-offset-0", 0);
setState((){});
return _sharedPreferences;
});
Declare two variables one to store and pass the scrollcontroller and the other to store and use the instance of SharedPreferences.
ScrollController _scrollController;
SharedPreferences _sharedPreferences;
Call resumeController() and pass your class to the addObserver method of the instance object in WidgetsBinding class.
resumeController();
WidgetsBinding.instance.addObserver(this);
Simply paste this code in the class definition (outside other member functions)
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_scrollController.dispose();
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if(state==AppLifecycleState.paused || state==AppLifecycleState.inactive || state==AppLifecycleState.suspending)
_sharedPreferences.setDouble("scroll-offset-0", _scrollController.offset);
super.didChangeAppLifecycleState(state);
}
Pass the ScrollController() to the concerned Scrollable.
Working Example:
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> with WidgetsBindingObserver{
//[...]
ScrollController _scrollController;
SharedPreferences _sharedPreferences;
Future<void> resumeController() async{
_sharedPreferences = await SharedPreferences.getInstance().then((_sharedPreferences){
if(_sharedPreferences.getKeys().contains("scroll-offset-0")) _scrollController= ScrollController(initialScrollOffset:_sharedPreferences.getDouble("scroll-offset-0"));
else _sharedPreferences.setDouble("scroll-offset-0", 0);
setState((){});
return _sharedPreferences;
});
}
#override
void initState() {
resumeController();
WidgetsBinding.instance.addObserver(this);
super.initState();
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_scrollController.dispose();
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if(state==AppLifecycleState.paused || state==AppLifecycleState.inactive || state==AppLifecycleState.suspending)
_sharedPreferences.setDouble("scroll-offset-0", _scrollController.offset);
super.didChangeAppLifecycleState(state);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: Text("Smart Scroll View"),
),
body: ListView.builder(
itemCount: 50,
controller: _scrollController,
itemBuilder: (c,i)=>
Padding(
padding: EdgeInsets.symmetric(horizontal: 24,vertical: 16),
child: Text((i+1).toString()),
),
),
),
);
}
}
Solution without knowing the size of your widgets
the Solution I found without knowing the size of your widget is displaying a reverse 'sublist' from the index to the end, then scroll to the top of your 'sublist' and reset the entire list. As it is a reverse list the item will be add at the top of the list and you will stay at your position (the index).
the problem is that you can't use a listView.builder because you will need to change the size of the list
example
class _ListViewIndexState extends State<ListViewIndex> {
ScrollController _scrollController;
List<Widget> _displayedList;
#override
void initState() {
super.initState();
_scrollController = ScrollController();
_displayedList = widget.items.sublist(0, widget.items.length - widget.index);
if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.persistentCallbacks) {
SchedulerBinding.instance.addPostFrameCallback((_) {
//here the sublist is already build
completeList();
});
}
}
completeList() {
//to go to the last item(in first position)
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
//reset the list to the full list
setState(() {
_displayedList = widget.items;
});
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
ListView(
controller: _scrollController,
reverse: true,
children: _displayedList,
),
]
);
}
}
The https://pub.dev/packages/indexed_list_view package could maybe help you out for this. Use something like this:
IndexedListView.builder(
controller: indexScrollController,
itemBuilder: itemBuilder
);
indexScrollController.jumpToIndex(10000);
I'll present another approach, which supports list lazy loading unlike #Shinbly 's method, and also support tiles in list to resize without recalculating the correct offset of the ListView nor saving any persistent information like "#Nephew of Stackoverflow" does.
The essential key to this approach is to utilize CustomScrollView, the CustomScrollView.center property.
Here's an example based on the example code from Flutter document (widgets.CustomScrollView.2):
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
List<int> top = [];
List<int> bottom = [0];
List<int> test = List.generate(10, (i) => -5 + i);
bool positionSwitcher = true;
#override
Widget build(BuildContext context) {
positionSwitcher = !positionSwitcher;
final jumpIndex = positionSwitcher ? 1 : 9;
Key centerKey = ValueKey('bottom-sliver-list');
return Scaffold(
appBar: AppBar(
title: const Text('Press Jump!! to jump between'),
leading: IconButton(
icon: const Icon(Icons.add),
onPressed: () {
setState(() {
top.add(-top.length - 1);
bottom.add(bottom.length);
});
},
),
),
body: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
RaisedButton(
child: Text('Jump!!'),
onPressed: () => setState(() {}),
),
Text(positionSwitcher ? 'At top' : 'At bottom'),
],
),
Expanded(
child: CustomScrollView(
center: centerKey,
slivers: <Widget>[
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int i) {
final index = jumpIndex - 1 - i;
return Container(
alignment: Alignment.center,
color: Colors.blue[200 + test[index] % 4 * 100],
height: 100 + test[index] % 4 * 20.0,
child: Text('Item: ${test[index]}'),
);
},
childCount: jumpIndex,
),
),
SliverList(
key: centerKey,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int i) {
final index = i + jumpIndex;
return Container(
alignment: Alignment.center,
color: i == 0
? Colors.red
: Colors.blue[200 + test[index] % 4 * 100],
height: 100 + test[index] % 4 * 20.0,
child: Text('Item: ${test[index]}'),
);
},
childCount: test.length - jumpIndex,
),
),
],
),
)
],
),
);
}
}
Explanation:
We use single list as data source for both SliverList
During each rebuild, we use center key to reposition the second SliverList inside ViewPort
Carefully manage the conversion from SliverList index to data source list index
Notice how the scroll view build the first SliverList by passing an index starting from bottom of this SliverList (i.e. index 0 suggests last item in the first list sliver)
Give the CustomeScrollView a proper key to decide whether to "re-position" or not
Working Example:
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:scroll_to_index/scroll_to_index.dart';
class ScrollToIndexDemo extends StatefulWidget {
const ScrollToIndexDemo({Key? key}) : super(key: key);
#override
_ScrollToIndexDemoState createState() => _ScrollToIndexDemoState();
}
class _ScrollToIndexDemoState extends State<ScrollToIndexDemo> {
late AutoScrollController controller = AutoScrollController();
var rng = Random();
ValueNotifier<int> scrollIndex = ValueNotifier(0);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: ValueListenableBuilder(
valueListenable: scrollIndex,
builder: (context, index, child) {
return Text('Scroll Demo - $index');
},
),
),
body: ListView.builder(
itemCount: 100,
controller: controller,
itemBuilder: (context, index) {
return Padding(
padding: EdgeInsets.all(8),
child: AutoScrollTag(
key: ValueKey(index),
controller: controller,
index: index,
highlightColor: Colors.black.withOpacity(0.1),
child: Container(
padding: EdgeInsets.all(10),
alignment: Alignment.center,
color: Colors.grey[300],
height: 100,
child: Text(
'index: $index',
style: TextStyle(color: Colors.black),
),
),
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
scrollIndex.value = rng.nextInt(100);
await controller.scrollToIndex(scrollIndex.value, preferPosition: AutoScrollPosition.begin);
},
tooltip: 'Increment',
child: Center(
child: Text(
'Next',
textAlign: TextAlign.center,
),
),
),
);
}
}
You can use the flutter_scrollview_observer lib to implement your desired functionality without invasivity
Create and use instance of ScrollController normally.
ScrollController scrollController = ScrollController();
ListView _buildListView() {
return ListView.builder(
controller: scrollController,
...
);
}
Create an instance of ListObserverController pass it to ListViewObserver
ListObserverController observerController = ListObserverController(controller: scrollController);
ListViewObserver(
controller: observerController,
child: _buildListView(),
...
)
Now you can scroll to the specified index position
// Jump to the specified index position without animation.
observerController.jumpTo(index: 1)
// Jump to the specified index position with animation.
observerController.animateTo(
index: 1,
duration: const Duration(milliseconds: 250),
curve: Curves.ease,
);

Animated FloatingActionButton in Stack don't trigger onPressed event

I've created an animated Floating Action Button (FAB) menu:
Main FAB which spawns other FABs when clicked.
It is based on several online tutorials.
Works fine besides one major issue:
The spawned FABs don't trigger an onPressed event.
Seems like there's a problem with the combination of transform animation and the stack widget (which the FABs are children of).
Works fine if I replace the stack with a row...
Seems like the issue was addressed here, but without a proper solution:
FloatingActionButton onPressed not triggering
Here's the complete code for that FAB menu.
Just supply it a list with buttons data, similar to this one:
List<Map<String, dynamic>> _buttonsData = [
{'color': Colors.green ,'icon': Icons.stop},
{'color': Colors.blue ,'icon': Icons.subway},
{'color': Colors.green ,'icon': Icons.add},];
FancyFab2(_buttonsData, null)
The code:
import 'package:flutter/material.dart';
class FancyFab extends StatefulWidget {
FancyFab(this._buttonsData, this._onSelected);
final List<Map<String, dynamic>> _buttonsData;
final ValueChanged<int> _onSelected;
#override
createState() => FancyFabState(_buttonsData, _onSelected);
}
class FancyFabState extends State<FancyFab> with SingleTickerProviderStateMixin {
final List<Map<String, dynamic>> _buttonsData;
final ValueChanged<int> _onSelected;
// state vars
AnimationController _controller;
Animation <double> _transform;
bool _isOpened = false;
FancyFabState(this._buttonsData, this._onSelected);
#override
void initState() {
// call base
super.initState();
// _controller
_controller = AnimationController(
duration: Duration(milliseconds: 100),
vsync: this
);
_transform = Tween<double>(
begin: 0.0,
end: -64.0,
).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.fastOutSlowIn
),
);
}
#override
void dispose() {
// controller
_controller.dispose();
// call base
super.dispose();
}
#override
Widget build(BuildContext context) {
return _buildFab(_controller);
}
Widget _buildFab(AnimationController controller) {
return AnimatedBuilder(
animation: controller,
builder: (context, builder) {
return Stack(
children: List.generate(_buttonsData.length, (index) => _buildStackWidget(context, index))
);
}
);
}
Widget _buildStackWidget(BuildContext context, int index) {
Map<String, dynamic> buttonData = _buttonsData[index];
if (index == _buttonsData.length - 1)
return _buildMenuButton(index, buttonData);
else
return _buildMenuItem(index, buttonData);
}
Widget _buildMenuItem(int index, Map<String, dynamic> buttonData) {
return Transform.translate(
offset: Offset((1 + index) * _transform.value, 0.0),
child: FloatingActionButton(
heroTag: 100 + index,
backgroundColor: buttonData['color'],
//onPressed: () => _onSelected(index),
onPressed: () => print('click'),
child: Icon(buttonData['icon']),
),
);
}
Widget _buildMenuButton(int index, Map<String, dynamic> buttonData) {
return FloatingActionButton(
heroTag: 200,
backgroundColor: buttonData['color'],
onPressed: _toggle,
child: Icon(buttonData['icon']),
);
}
void _toggle() {
print('toggle');
_isOpened = !_isOpened;
if (true == _isOpened)
_controller.forward();
else
_controller.reverse();
}
}
That's because you are moving the items outside your Stack so you can't hitTest on those positions.
I modified a few lines of your code to make it works :
Add constraints to your Stack (you can use SizedBox also) , I'm using container to set a different color of background.
Widget _buildFab(AnimationController controller) {
return AnimatedBuilder(
animation: controller,
builder: (context, builder) {
return Container(
width: MediaQuery.of(context).size.width,
height: 100,
color: Colors.blueGrey,
child: Stack(
children: List.generate(_buttonsData.length,
(index) => _buildStackWidget(context, index))),
);
});
}
Center your items
Widget _buildMenuItem(int index, Map<String, dynamic> buttonData) {
return Center(
child: Transform.translate(
offset: Offset((1 + index) * _transform.value, 0.0),
child: FloatingActionButton(
heroTag: 100 + index,
backgroundColor: buttonData['color'],
//onPressed: () => _onSelected(index),
onPressed: () => print('click'),
child: Icon(buttonData['icon']),
),
),
);
}
Widget _buildMenuButton(int index, Map<String, dynamic> buttonData) {
return Center(
child: FloatingActionButton(
heroTag: 200,
backgroundColor: buttonData['color'],
onPressed: _toggle,
child: Icon(buttonData['icon']),
),
);
}

Resources