I want to create a list o buttons with text so that the user can select one of them.
For the state of the buttons I used a StateNotifier:
class SeleccionStateNotifier extends StateNotifier<List<bool>> {
int cantidad;
SeleccionStateNotifier(this.cantidad)
: super(List.generate(cantidad, (index) => false));
void CambioValor(int indice) {
for (int i = 0; i < cantidad; i++) {
if (i == indice) {
state[i] = true;
} else {
state[i] = false;
}
}
}
}
final seleccionProvider =
StateNotifierProvider<SeleccionStateNotifier, List<bool>>((ref) {
final lector = ref.watch(eleccionesSinSeleccionStateNotifierProvider);
return SeleccionStateNotifier(lector.length);
});
Now in the UI I just want to show a text and the value of the button (false for everyone except the one the user selects)
class EleccionesList5 extends ConsumerWidget {
const EleccionesList5({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, ScopedReader watch) {
bool este = false;
final eleccioneswatchCon =
watch(eleccionesSinSeleccionStateNotifierProvider);
final seleccionwatch = watch(seleccionProvider);
final buttons = List<Widget>.generate(
eleccioneswatchCon.length,
(i) => Container(
padding: const EdgeInsets.fromLTRB(5, 2, 5, 2),
child: TextButton(
onPressed: () {
context.read(seleccionProvider.notifier).CambioValor(i);
print('OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO');
for (int id = 0; id < eleccioneswatchCon.length; id++) {
print(seleccionwatch[id]);
}
},
child: Text(eleccioneswatchCon[i].eleccion + ' ' + seleccionwatch[i].toString()),
),
),
).toList();
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Wrap(
alignment: WrapAlignment.spaceAround,
children: buttons,
),
],
);
}
}
based on my previous answer you should update state itself, not an inner value of a list to take effect
void CambioValor(int indice) {
final list = List.of(state);
for (int i = 0; i < cantidad; i++) {
if (i == indice) {
list[i] = true;
} else {
list[i] = false;
}
}
/// it will update when you use the setter of state
state = list;
}
Related
I am new in flutter and trying to create a listview with load more functionality.
Here is my class. It is not displaying anything, blank screen. List has data I am getting result in console.
class ReportPurchaseNew extends StatefulWidget {
final AdminUserDetails userDetails;
final String title;
const ReportPurchaseNew({Key key, this.title, this.userDetails})
: super(key: key);
#override
State<StatefulWidget> createState() => new ReportPurchaseState();
}
class ReportPurchaseState extends State<ReportPurchaseNew> {
String fromDate = "", toDate = "", usageType = "";
int limit = 7, offset = 0;
static int page = 0;
List<Result> _List = new List();
List<Result> _filteredList;
Future<PurchaseReport> _PurchaseReportResponse;
List<UsageResult> _usageList = [];
UsageResult _usageVal;
ScrollController _sc = new ScrollController();
bool isLoading = false;
//List users = new List();
#override
void initState() {
this._getMorePurchase(page);
super.initState();
_sc.addListener(() {
if (_sc.position.pixels ==
_sc.position.maxScrollExtent) {
_getMorePurchase(page);
}
});
}
#override
void dispose() {
_sc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Lazy Load Large List"),
),
body: Container(
child: _buildList(),
),
resizeToAvoidBottomInset: false,
);
}
Widget _buildList() {
return ListView.builder(
itemCount: _List.length + 1, // Add one more item for progress indicator
padding: EdgeInsets.symmetric(vertical: 8.0),
itemBuilder: (BuildContext context, int index) {
if (index == _List.length) {
return _buildProgressIndicator();
} else {
return new ListTile(
leading: CircleAvatar(
radius: 30.0,
),
title :Text("my:"+(_List[index]
.product)),
subtitle: Text((_List[index]
.unitPrice)),
);
}
},
controller: _sc,
);
}
Widget _buildProgressIndicator() {
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: new Opacity(
opacity: isLoading ? 1.0 : 00,
child: new CircularProgressIndicator(),
),
),
);
}
Future<PurchaseReport> getProjectDetails() async {
var result = await PurchaseReportRequest(
context,
widget.userDetails.result.raLoginId,
limit.toString(),
offset.toString(),
fromDate,
toDate,
_usageVal!=null ? _usageVal.name : "" ,
);
return result;
}
void _getMorePurchase(int index) async {
if (!isLoading) {
setState(() {
isLoading = true;
});
_PurchaseReportResponse = getProjectDetails();
setState(() {
isLoading = false;
_PurchaseReportResponse.then((response) {
if (response != null) {
_List.addAll(response.result);
page = page + limit;
print("printing length : "
+_List.length.toString());
for (int i = 0; i < response.result.length; i++) {
print('name:' +_List[i].product );
}
} else {
errorRaisedToast(context);
}
});
});
}
}
}
Try This,
if (response != null) {
List newList = new List();
// _List.addAll(response.result);
page = page + limit;
print("printing length : "
+_List.length.toString());
for (int i = 0; i < response.result.length; i++) {
newList.add(response.result[i]);
print('name:' +_List[i].product);
}
isLoading = false;
_List.addAll(newList);
page++;
} else {
errorRaisedToast(context);
}
I have been writing a Sorting visualiser in flutter, I am so far able to animate the movement of blocks. But I also want to update the colours of the block, when the block goes through the states of being scanned, moved, and finally when it is completely sorted. I looked up the State management in flutter, and it is rather confusing to know what approach should I be using in my project. Below is the DashBoard Class:
import 'package:algolizer/sortingAlgorithms/Block.dart';
import 'package:flutter/material.dart';
import 'dart:math';
class DashBoard extends StatefulWidget {
double width;
double height;
DashBoard(#required this.width, #required this.height);
#override
_DashBoardState createState() => _DashBoardState();
}
class _DashBoardState extends State<DashBoard> {
double currentSliderValue = 50;
List<double> arr = new List(500);
List<Block> blockList;
bool running = false;
#override
void initState() {
// TODO: implement initState
super.initState();
fillArr((widget.width * 0.6) / 50, (widget.width * 0.1) / 50,
widget.height * 0.7);
}
void InsertionSort() async {
setState(() {
running = true;
});
int delay = (pow(15, 4) / pow(currentSliderValue, 2)).round();
for (int i = 1; i < currentSliderValue; i++) {
if (blockList[i] == null) break;
Block key = blockList[i];
int j = i - 1;
while (j >= 0 && blockList[j].height > key.height) {
setState(() {
blockList[j + 1] = blockList[j];
});
await Future.delayed(Duration(milliseconds: delay));
j--;
}
blockList[j + 1] = key;
}
setState(() {
running = false;
});
}
void BubbleSort() async {
setState(() {
running = true;
});
int delay = (pow(15, 4) / pow(currentSliderValue, 2)).round();
for (int i = 0; i < currentSliderValue - 1; i++) {
for (int j = 0; j < currentSliderValue - i - 1; j++) {
if (blockList[j].height > blockList[j + 1].height) {
Block temp = blockList[j + 1];
setState(() {
blockList[j + 1] = blockList[j];
blockList[j] = temp;
});
await Future.delayed(Duration(milliseconds: delay));
}
}
}
setState(() {
running = false;
});
}
// Map<String, >
void fillArr(double width, double margin, double height) {
for (int i = 0; i < arr.length; i++) arr[i] = null;
var rng = new Random();
for (int i = 0; i < currentSliderValue; i++) {
double val = rng.nextDouble() * height;
if (val == 0)
continue;
else
arr[i] = val;
}
blockList = [...arr.map((height) => Block(height, width, margin))];
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
SizedBox(height: 20),
Row(
children: [
Text(
"Length",
),
Slider(
value: currentSliderValue,
min: 5,
max: 200,
onChanged: (double value) {
setState(() {
currentSliderValue = value;
});
double newwidth =
(MediaQuery.of(context).size.width * 0.6) /
currentSliderValue;
double newmargin =
(MediaQuery.of(context).size.width * 0.1) /
currentSliderValue;
fillArr(newwidth, newmargin, widget.height * 0.7);
}),
RaisedButton(
child: Text("Insertion Sort"),
onPressed: InsertionSort,
),
RaisedButton(
onPressed: BubbleSort, child: Text("Bubble Sort")),
RaisedButton(onPressed: () {}, child: Text("Merge Sort")),
RaisedButton(onPressed: () {}, child: Text("Quick Sort")),
RaisedButton(
onPressed: () {}, child: Text("Counting Sort")),
RaisedButton(onPressed: () {}, child: Text("Radix Sort")),
RaisedButton(
onPressed: () {}, child: Text("Selection Sort")),
RaisedButton(onPressed: () {}, child: Text("Heap Sort")),
],
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [...blockList],
),
// Row(
// children: [
// Container(
// child: Row(children: [
// Text("Algorithm")
// ],)
// )]
// ),
],
),
),
);
}
}
Here's the Block class:
import 'package:flutter/material.dart';
class Block extends StatefulWidget {
Block(#required this.height, #required this.width, #required this.mar);
double height;
double width;
double mar;
#override
_BlockState createState() => _BlockState();
}
class _BlockState extends State<Block> {
Color col = Colors.blue;
// void isKey() {
// setState(() {
// col = Colors.pink;
// });
// }
// void notKey() {
// setState(() {
// col = Colors.purple;
// });
// }
#override
Widget build(BuildContext context) {
return (widget.height == null)
? Container()
: Container(
height: this.widget.height,
width: widget.width,
margin: EdgeInsets.all(widget.mar),
decoration: BoxDecoration(
color: col,
),
);
}
}
As far as which state management route to go with, it really can be done with any of them. GetX to me is the easiest and has the least boilerplate.
Here's one way to do this. I just updated the insertionSort method to get you started and you can go from there. Any other changes you notice in your other classes are just to get rid of linter errors.
All your methods and variables can now live in a GetX class. With the exception of color, the rest are now observable streams.
class BlockController extends GetxController {
RxDouble currentSliderValue = 50.0.obs; // adding .obs makes variable obserable
RxList arr = List(500).obs;
RxList blockList = [].obs;
RxBool running = false.obs;
Color color = Colors.red;
void insertionSort() async {
running.value = true; // adding .value access the value of observable variable
color = Colors.blue;
int delay = (pow(15, 4) / pow(currentSliderValue.value, 2)).round();
for (int i = 1; i < currentSliderValue.value; i++) {
if (blockList[i] == null) break;
Block key = blockList[i];
int j = i - 1;
while (j >= 0 && blockList[j].height > key.height) {
blockList[j + 1] = blockList[j];
await Future.delayed(Duration(milliseconds: delay));
j--;
}
blockList[j + 1] = key;
}
color = Colors.green;
update(); // only needed for the color property because its not an observable stream
running.value = false;
}
void bubbleSort() async {
running.value = true;
int delay = (pow(15, 4) / pow(currentSliderValue.value, 2)).round();
for (int i = 0; i < currentSliderValue.value - 1; i++) {
for (int j = 0; j < currentSliderValue.value - i - 1; j++) {
if (blockList[j].height > blockList[j + 1].height) {
Block temp = blockList[j + 1];
blockList[j + 1] = blockList[j];
blockList[j] = temp;
await Future.delayed(Duration(milliseconds: delay));
}
}
}
running.value = false;
}
// Map<String, >
void fillArr(double width, double margin, double height) {
for (int i = 0; i < arr.length; i++) arr[i] = null;
var rng = new Random();
for (int i = 0; i < currentSliderValue.value; i++) {
double val = rng.nextDouble() * height;
if (val == 0)
continue;
else
arr[i] = val;
}
blockList = [...arr.map((height) => Block(height, width, margin))].obs;
}
}
Initialize the controller in your main before running your app. Generally it can be done anywhere as long as its before you try to access the controller.
Get.put(BlockController());
Here's your much less busy DashBoard now that all that logic is tucked away in a GetX class. Here we find the controller, and use it access all those variables and methods.
Obx is the GetX widget that rebuilds on changes.
class DashBoard extends StatefulWidget {
final double width;
final double height;
DashBoard(this.width, this.height);
#override
_DashBoardState createState() => _DashBoardState();
}
class _DashBoardState extends State<DashBoard> {
final controller = Get.find<BlockController>(); // finding same instance of BlockConroller that you initialized in `Main`
#override
void initState() {
super.initState();
controller.fillArr((widget.width * 0.6) / 50, (widget.width * 0.1) / 50,
widget.height * 0.7);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
SizedBox(height: 50),
Obx(
// rebuilds when observable variables change
() => Column(
// changed to Column because a Row was overflowing
children: [
Text(
"Length",
),
Slider(
value: controller.currentSliderValue.value,
min: 5,
max: 200,
onChanged: (double value) {
controller.currentSliderValue.value = value;
double newwidth =
(MediaQuery.of(context).size.width * 0.6) /
controller.currentSliderValue.value;
double newmargin =
(MediaQuery.of(context).size.width * 0.1) /
controller.currentSliderValue.value;
controller.fillArr(
newwidth, newmargin, widget.height * 0.7);
}),
RaisedButton(
child: Text("Insertion Sort"),
onPressed: controller.insertionSort,
),
RaisedButton(
onPressed: controller.bubbleSort,
child: Text("Bubble Sort")),
RaisedButton(onPressed: () {}, child: Text("Merge Sort")),
RaisedButton(onPressed: () {}, child: Text("Quick Sort")),
RaisedButton(onPressed: () {}, child: Text("Counting Sort")),
RaisedButton(onPressed: () {}, child: Text("Radix Sort")),
RaisedButton(onPressed: () {}, child: Text("Selection Sort")),
RaisedButton(onPressed: () {}, child: Text("Heap Sort")),
],
),
),
Obx(
// rebuilds when observable variables change
() => Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [...controller.blockList],
),
),
// Row(
// children: [
// Container(
// child: Row(children: [
// Text("Algorithm")
// ],)
// )]
// ),
],
),
),
);
}
}
And here's your Block which can now be stateless. Key thing of note here is the GetBuilder widget that updates the color.
class Block extends StatelessWidget {
// now can be stateless
Block(this.height, this.width, this.mar);
final double height;
final double width;
final double mar;
#override
Widget build(BuildContext context) {
return (height == null)
? Container()
: GetBuilder<BlockController>(
// triggers rebuilds when update() is called from GetX class
builder: (controller) => Container(
height: this.height,
width: width,
margin: EdgeInsets.all(mar),
decoration: BoxDecoration(
color: controller.color,
),
),
);
}
}
someone can help with this code?
I must print a query result inside the dropdown as List
The error flutter give me back is:
The following NoSuchMethodError was thrown building
DropDownFormField(dirty, dependencies: [_FormScope], state:
FormFieldState#e264a): Class 'Future<List>' has no
instance method '[]'. Receiver: Instance of 'Future<List>'
Tried calling: The relevant error-causing widget was:
DropDownFormField
I've tryed getAllBrockers and getAllBrockers2 way
class _AutomationAddScreen extends State<AutomationAdd> {
DataRepository _repository;
final _formKey = GlobalKey<FormState>();
String _broN = "";
Future<Map> _broMap;
Future<List> _bro;
static Future<List> getAllBrockers2(Future<List<Brockers>> l) async {
List q;
List<Brockers> b = await l;
for (var i = 0; i < b.length; i++) {
q.add(DataList(b[i].name, b[i].id));
}
return q;
}
static Future<Map> getAllBrockers(Future<List<Brockers>> l) async {
Map hBro;
Map m;
List<Brockers> b = await l;
for (var i = 0; i < b.length; i++) {
m = {"display": b[i].name, "value": b[i].id};
hBro.addAll(m);
}
return hBro;
}
#override
void initState() {
super.initState();
_repository = SqlRepository();
//_broMap = getAllBrockers(_repository.getAllBrockers());
_bro = getAllBrockers2(_repository.getAllBrockers());
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Form(
key: _formKey,
child: ListView(
children: <Widget>[
//SELECT BROCKER
Padding(
padding: EdgeInsets.only(top: 16),
child: DropDownFormField(
titleText: 'Connessione',
hintText: 'Selezionane uno',
value: _broN,
//dataSource: [_broMap],
dataSource: [_bro],
textField: 'display',
valueField: 'value',
onChanged: (display) {
setState(() {
_broN = display.toString();
});
},
),
),
],
),
),
));
}
}
class DataList {
String display;
int value;
DataList(this.display, this.value);
#override
String toString() {
return '{ ${this.display}, ${this.value} }';
}
}
Instead of using Future<List> or Future<Map>, you should use List or Map. The error specifically mentions that it cannot call [] on Future<List>.
The DropdownFormField is unable to create its list of items because you have provided a Future.
You can try using the following approach -
class _AutomationAddScreen extends State<AutomationAdd> {
DataRepository _repository;
final _formKey = GlobalKey<FormState>();
String _broN = "";
Map _broMap; // New
List _bro = List<DataList>(); // New
static Future<void> getAllBrockers2() async { // New
final b = await _repository.getAllBrockers(); // New
for (var i = 0; i < b.length; i++) {
_bro.add(DataList(b[i].name, b[i].id)); // New
}
setState(() {}); // New
}
static Future<Map> getAllBrockers(Future<List<Brockers>> l) async {
Map hBro = {}; // New
Map m;
List<Brockers> b = await l;
for (var i = 0; i < b.length; i++) {
m = {"display": b[i].name, "value": b[i].id};
hBro.addAll(m);
}
return hBro;
}
#override
void initState() {
super.initState();
_repository = SqlRepository();
//_broMap = getAllBrockers(_repository.getAllBrockers());
getAllBrockers2(); // New
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Form(
key: _formKey,
child: ListView(
children: <Widget>[
//SELECT BROCKER
Padding(
padding: EdgeInsets.only(top: 16),
child: DropDownFormField(
titleText: 'Connessione',
hintText: 'Selezionane uno',
value: _broN,
//dataSource: [_broMap],
dataSource: [_bro],
textField: 'display',
valueField: 'value',
onChanged: (display) {
setState(() {
_broN = display.toString();
});
},
),
),
],
),
),
));
}
}
class DataList {
String display;
int value;
DataList(this.display, this.value);
#override
String toString() {
return '{ ${this.display}, ${this.value} }';
}
}
I am working on a Flutter Application, How do i select a single toggle button at a time, if i have multiple toggle buttons?
The problem is i have multiple cases with multiple choices within each case, i have 5 different cases.
onPressed: (int index) {
setState(() {
isSelected2[index] = !isSelected2[index];
switch (index) {
//This is the other area I had to make changes
case 0:
if (isSelected2[index]) {
print('true');
_choiceA += 5;
_choiceB += 5;
_choiceC += 10;
_choiceD += 10;
} else {
print('false');
_choiceA += -5;
_choiceB += -5;
_choiceC += -10;
_choiceD += -10;
}
break;
Thank you
Mohammad
Checkout the code below:
import 'package:flutter/material.dart';
void main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: SamplePage(),
);
}
}
class SamplePage extends StatefulWidget {
#override
_SamplePageState createState() => _SamplePageState();
}
class _SamplePageState extends State<SamplePage> {
List<bool> isSelected;
#override
void initState() {
// this is for 3 buttons, add "false" same as the number of buttons here
isSelected = [true, false, false];
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ToggleButtons Demo'),
),
body: Center(
child: ToggleButtons(
children: <Widget>[
// first toggle button
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'First',
),
),
// second toggle button
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Second',
),
),
// third toggle button
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Third',
),
),
],
// logic for button selection below
onPressed: (int index) {
setState(() {
for (int i = 0; i < isSelected.length; i++) {
isSelected[i] = i == index;
}
});
},
isSelected: isSelected,
),
),
);
}
}
Output:
You need to disable all others to achieve desirable behavior.
Make a list of values you want to update for each button index:
List values = [[5,5,10,10], [3,2,3,-5], [3,-1,3,4], [3,-8, 12,1]];
Then update your choices:
onPressed: (int index) {
setState(() {
for (int buttonIndex = 0; buttonIndex < isSelected.length; buttonIndex++) {
if (buttonIndex == index) {
isSelected[buttonIndex] = !isSelected[buttonIndex];
if(isSelected[buttonIndex]){
_choiceA += values[buttonIndex][0];
_choiceB += values[buttonIndex][1];
_choiceC += values[buttonIndex][2];
_choiceD += values[buttonIndex][3];
}else{
_choiceA -= values[buttonIndex][0];
_choiceB -= values[buttonIndex][1];
_choiceC -= values[buttonIndex][2];
_choiceD -= values[buttonIndex][3];
isSelected[buttonIndex] = false;
}
} else {
if(isSelected[buttonIndex]){
_choiceA -= values[buttonIndex][0];
_choiceB -= values[buttonIndex][1];
_choiceC -= values[buttonIndex][2];
_choiceD -= values[buttonIndex][3];
isSelected[buttonIndex] = false;
}
}
}
});
},
Edit:
This code should be refactored
I'm looking to recreate Snapchat's back-to-back video format in Flutter. Since video_player is lacking callbacks for when the video finishes (and is otherwise prone to callback hell), I was wondering if anyone has some pointers for building something like this.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
void main() {
runApp(MaterialApp(
title: 'My app', // used by the OS task switcher
home: MyHomePage(),
));
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<VideoPlayerController> _controllers = [];
VoidCallback listener;
bool _isPlaying = false;
int _current = 0;
#override
void initState() {
super.initState();
// Add some sample videos
_controllers.add(VideoPlayerController.network(
'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4',
));
_controllers.add(VideoPlayerController.network(
'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4',
));
_controllers.add(VideoPlayerController.network(
'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4',
));
this.tick();
// Try refreshing by brute force (this isn't going too well)
new Timer.periodic(Duration(milliseconds: 100), (Timer t) {
int delta = 99999999;
if(_controllers[_current].value != null) {
delta = (_controllers[_current].value.duration.inMilliseconds - _controllers[_current].value.position.inMilliseconds);
}
print("Tick " + delta.toString());
if(delta < 500) {
_current += 1;
this.tick();
}
});
}
void tick() async {
print("Current: " + _current.toString());
await _controllers[_current].initialize();
await _controllers[_current].play();
print("Ready");
setState((){
_current = _current;
});
}
#override
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio: _controllers[_current].value.aspectRatio,
// Use the VideoPlayer widget to display the video
child: VideoPlayer(_controllers[_current]),
);
}
}
What I have now plays the first video, but there is a very long delay between the first and second. I believe it has to do with my inability to get rid of the listener attached to the 0th item.
Initializing a network VideoPlayerController may take some time to finish. You can initialize the controller of the next video while playing the current. This will take more memory but I don't think it will create huge problems if you prebuffer only one or two videos. Then when the next or previous buttons get pressed, video will be ready to play.
Here is my workaround. It's functional, it prebuffers previous and next videos, skips to the next video when finishes, shows the current position and buffer, pauses and plays on long press.
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
main() {
runApp(MaterialApp(
home: VideoPlayerDemo(),
));
}
class VideoPlayerDemo extends StatefulWidget {
#override
_VideoPlayerDemoState createState() => _VideoPlayerDemoState();
}
class _VideoPlayerDemoState extends State<VideoPlayerDemo> {
int index = 0;
double _position = 0;
double _buffer = 0;
bool _lock = true;
Map<String, VideoPlayerController> _controllers = {};
Map<int, VoidCallback> _listeners = {};
Set<String> _urls = {
'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4#1',
'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4#2',
'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4#3',
'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4#4',
'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4#5',
'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4#6',
'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4#7',
};
#override
void initState() {
super.initState();
if (_urls.length > 0) {
_initController(0).then((_) {
_playController(0);
});
}
if (_urls.length > 1) {
_initController(1).whenComplete(() => _lock = false);
}
}
VoidCallback _listenerSpawner(index) {
return () {
int dur = _controller(index).value.duration.inMilliseconds;
int pos = _controller(index).value.position.inMilliseconds;
int buf = _controller(index).value.buffered.last.end.inMilliseconds;
setState(() {
if (dur <= pos) {
_position = 0;
return;
}
_position = pos / dur;
_buffer = buf / dur;
});
if (dur - pos < 1) {
if (index < _urls.length - 1) {
_nextVideo();
}
}
};
}
VideoPlayerController _controller(int index) {
return _controllers[_urls.elementAt(index)];
}
Future<void> _initController(int index) async {
var controller = VideoPlayerController.network(_urls.elementAt(index));
_controllers[_urls.elementAt(index)] = controller;
await controller.initialize();
}
void _removeController(int index) {
_controller(index).dispose();
_controllers.remove(_urls.elementAt(index));
_listeners.remove(index);
}
void _stopController(int index) {
_controller(index).removeListener(_listeners[index]);
_controller(index).pause();
_controller(index).seekTo(Duration(milliseconds: 0));
}
void _playController(int index) async {
if (!_listeners.keys.contains(index)) {
_listeners[index] = _listenerSpawner(index);
}
_controller(index).addListener(_listeners[index]);
await _controller(index).play();
setState(() {});
}
void _previousVideo() {
if (_lock || index == 0) {
return;
}
_lock = true;
_stopController(index);
if (index + 1 < _urls.length) {
_removeController(index + 1);
}
_playController(--index);
if (index == 0) {
_lock = false;
} else {
_initController(index - 1).whenComplete(() => _lock = false);
}
}
void _nextVideo() async {
if (_lock || index == _urls.length - 1) {
return;
}
_lock = true;
_stopController(index);
if (index - 1 >= 0) {
_removeController(index - 1);
}
_playController(++index);
if (index == _urls.length - 1) {
_lock = false;
} else {
_initController(index + 1).whenComplete(() => _lock = false);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Playing ${index + 1} of ${_urls.length}"),
),
body: Stack(
children: <Widget>[
GestureDetector(
onLongPressStart: (_) => _controller(index).pause(),
onLongPressEnd: (_) => _controller(index).play(),
child: Center(
child: AspectRatio(
aspectRatio: _controller(index).value.aspectRatio,
child: Center(child: VideoPlayer(_controller(index))),
),
),
),
Positioned(
child: Container(
height: 10,
width: MediaQuery.of(context).size.width * _buffer,
color: Colors.grey,
),
),
Positioned(
child: Container(
height: 10,
width: MediaQuery.of(context).size.width * _position,
color: Colors.greenAccent,
),
),
],
),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FloatingActionButton(onPressed: _previousVideo, child: Icon(Icons.arrow_back)),
SizedBox(width: 24),
FloatingActionButton(onPressed: _nextVideo, child: Icon(Icons.arrow_forward)),
],
),
);
}
}
All of the logic lives inside the state object, therefore makes it dirty. I might turn this into a package in the future.