i want to add a video player to my Listview.builder(child:???).Every video must have different links how can i do that?
ListviewBuilder
> buildSectionTitle(context, 'Videos'),
buildContainer(
ListView.builder(
itemBuilder: (ctx, index) => Container(
color: Theme.of(context).accentColor,
child: Padding(
padding: EdgeInsets.symmetric(
vertical: 5,
horizontal: 10,
),
child: ???,
),
),
),
1st step: Add video player plugin
2nd step: create a new widget containing vieo(for each element of your ListView
class VideoItem extends StatefulWidget {
final string url;
VideoItem(this.url);
#override
_VideoItemState createState() => VideoItem();
}
class _VideoItemState extends State<VideoApp> {
VideoPlayerController _controller;
#override
void initState() {
super.initState();
_controller = VideoPlayerController.network(widget.url)
..initialize().then((_) {
// Ensure the first frame is shown after the video is initialized, even before the play button has been pressed.
setState(() {});
});
}
#override
Widget build(BuildContext context) {
return Center(
child: _controller.value.initialized
?Stack(childre:[ AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: VideoPlayer(_controller),),
Center(child;:GestureDetector(onTap:_playPause,
child:Icon(Icons.play_circle))
]
)
: Container(),
),
),
);
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
_playPause(){
if(_controller.isPlaying){
_controller.pause();
}else{
_controller.play();
}
}
Then : Use it in ListView
ListView.builder(
itemCount: listOfVideoUrl.length, //Notice this
itemBuilder: (ctx, index) => VideoItem(listOfVideoUrl[index]))
NOTE: This is not the best way to achieve this . consider using images in ListViewthen open player page after users click
Related
I have a ListView.builder that is building items from a list of items, _cache. When I scroll to the end of the list _cache, a function is called to extend _cache by pulling more data from elsewhere. However, that data is limited, and eventually I run out of items to extend _cache with. So I want ListView.builder to stop building items there.
I understand that ListView.builder has a property called itemsCount, but I don't see how I can add that property only when I run out of items to add to _cache. How do I achieve what I want?
You can use ScrollController to load more data when you reached the end of the list item when you scroll.
This is the simple example I have done in my application.
class OtherUserListScreen extends StatefulWidget {
const OtherUserListScreen({Key? key}) : super(key: key);
#override
State<OtherUserListScreen> createState() => _OtherUserListScreenState();
}
class _OtherUserListScreenState extends State<OtherUserListScreen> {
// create a _scrollController variable
late ScrollController _scrollController;
#override
void initState() {
super.initState();
_scrollController = ScrollController();
_scrollController.addListener(_handleScroll);
}
#override
Widget build(BuildContext context) {
final _usersProvider = Provider.of<UsersProvider>(context);
return Scaffold(
body: ListView.builder(
itemBuilder: (context, index) {
if (index == _usersProvider.users.length) {
return MoreLoadingIndicator(
isMoreLoading: _usersProvider.isMoreLoading,
);
}
final user = _usersProvider.users[index];
return FadeIn(
duration: const Duration(milliseconds: 400),
delay: Duration(milliseconds: index * 100),
child: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => PatientDetailScreen(
patientType: PatientType.OTHER,
user: user,
),
),
);
},
child: RecordUserDescription(
user: user,
showToolkit: false,
margin: const EdgeInsets.fromLTRB(16, 16, 16, 0),
),
),
);
},
itemCount: _usersProvider.users.length,
),
);
}
_handleScroll() async {
if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
await context.read<UsersProvider>().loadMoreUsers(
context,
accountID: context.read<UserProvider>().user!.accountId!,
);
}
return;
}
}
Recently I have downloaded the Louis Vuitton App. I found a strange type of horizontal scroll of product items in listview. I tried card_swiper package but couldnot get through it. How can I achieve such scroll as in gif below?
the trick here is to use a stack and:
Use a page view to display every element except the first one
Use a left aligned FractionallySizedBox which displays the first item and grows with the first item offset
It took me a few tries but the result is very satisfying, I'll let you add the bags but here you go with colored boxes ;) :
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(home: FunList()));
}
class FunList extends StatefulWidget {
#override
State<FunList> createState() => _FunListState();
}
class _FunListState extends State<FunList> {
/// The colors of the items in the list
final _itemsColors = List.generate(
100,
(index) => Color((Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(1.0),
);
/// The current page of the page view
double _page = 0;
/// The index of the leftmost element of the list to be displayed
int get _firstItemIndex => _page.toInt();
/// The offset of the leftmost element of the list to be displayed
double get _firstItemOffset => _controller.hasClients ? 1 - (_page % 1) : 1;
/// Controller to get the current position of the page view
final _controller = PageController(
viewportFraction: 0.25,
);
/// The width of a single item
late final _itemWidth = MediaQuery.of(context).size.width * _controller.viewportFraction;
#override
void initState() {
super.initState();
_controller.addListener(() => setState(() {
_page = _controller.page!;
}));
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Center(
child: Stack(
children: [
Positioned.fill(
child: Align(
alignment: Alignment.centerLeft,
child: SizedBox(
width: _itemWidth,
child: FractionallySizedBox(
widthFactor: _firstItemOffset,
heightFactor: _firstItemOffset,
child: PageViewItem(color: _itemsColors[_firstItemIndex]),
),
),
),
),
SizedBox(
height: 200,
child: PageView.builder(
padEnds: false,
controller: _controller,
itemBuilder: (context, index) {
return Opacity(
opacity: index <= _firstItemIndex ? 0 : 1,
child: PageViewItem(color: _itemsColors[index]),
);
},
itemCount: _itemsColors.length,
),
),
],
),
);
}
}
class PageViewItem extends StatelessWidget {
final Color color;
const PageViewItem({
Key? key,
required this.color,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.all(10),
color: color,
);
}
}
Im developing a app that when the user enters the first screen is a loading screen where initialize somethings from the Provider, the user cant input anything in this screen, when the loading finishes i want to push a new screen without the user "clicking" for it.
In this code, im actually given a delay of 3 seconds for the _login.getStoredEmail() to run and set a variable inside LoginController which in the next screen i consume, but of course this wont work everytime, eventually will break.
class GeneralSplashScreen extends StatefulWidget {
#override
_GeneralSplashScreenState createState() => _GeneralSplashScreenState();
}
class _GeneralSplashScreenState extends State<GeneralSplashScreen> {
#override
void initState() {
Future.delayed(
Duration(
seconds: 3,
),
() {
Navigator.pushReplacementNamed(context, kRoutes.login);
},
);
super.initState();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
final LoginController _login = Provider.of<LoginController>(context);
_login.getStoredEmail();
return Scaffold(
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 60),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Image.asset(
'lib/assets/images/logo.png',
fit: BoxFit.contain,
),
Text(
"Business Mananger",
textAlign: TextAlign.end,
style: TextStyle(
fontFamily: kFontFamily.montserrat,
fontSize: 10,
),
),
LoadingBar(),
],
),
),
);
}
}
Navigate to a different screen inside the initState() method.
initState() {
initializeAndNavigate()
}
initializeAndNavigate() async {
await initializeSomething();
Navigator.of(context) ...
}
Declare initState
Future _future;
#override
void initState() {
// TODO: implement initState
super.initState();
_future = doStuff();
}
Use FutureBuilder
FutureBuilder(
future: _future,
builder: (_, dataSnapshot) {
if (dataSnapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator()); // here add loading screen
} else {
return Dashboard();
}
},
)
How to solve the exception -
Unhandled Exception: 'package:flutter/src/widgets/page_view.dart': Failed assertion: line 179 pos 7: 'positions.isNotEmpty': PageController.page cannot be accessed before a PageView is built with it.
Note:- I used it in two screens and when I switch between screen it shows the above exception.
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => _animateSlider());
}
void _animateSlider() {
Future.delayed(Duration(seconds: 2)).then(
(_) {
int nextPage = _controller.page.round() + 1;
if (nextPage == widget.slide.length) {
nextPage = 0;
}
_controller
.animateToPage(nextPage,
duration: Duration(milliseconds: 300), curve: Curves.linear)
.then(
(_) => _animateSlider(),
);
},
);
}
I think you can just use a Listener like this:
int _currentPage;
#override
void initState() {
super.initState();
_currentPage = 0;
_controller.addListener(() {
setState(() {
_currentPage = _controller.page.toInt();
});
});
}
I don't have enough information to see exactly where your problem is, but I just encountered a similar issue where I wanted to group a PageView and labels in the same widget and I wanted to mark active the current slide and the label so I was needing to access controler.page in order to do that. Here is my fix :
Fix for accessing page index before PageView widget is built using FutureBuilder widget
class Carousel extends StatelessWidget {
final PageController controller;
Carousel({this.controller});
/// Used to trigger an event when the widget has been built
Future<bool> initializeController() {
Completer<bool> completer = new Completer<bool>();
/// Callback called after widget has been fully built
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
completer.complete(true);
});
return completer.future;
} // /initializeController()
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
// **** FIX **** //
FutureBuilder(
future: initializeController(),
builder: (BuildContext context, AsyncSnapshot<void> snap) {
if (!snap.hasData) {
// Just return a placeholder widget, here it's nothing but you have to return something to avoid errors
return SizedBox();
}
// Then, if the PageView is built, we return the labels buttons
return Column(
children: <Widget>[
CustomLabelButton(
child: Text('Label 1'),
isActive: controller.page.round() == 0,
onPressed: () {},
),
CustomLabelButton(
child: Text('Label 2'),
isActive: controller.page.round() == 1,
onPressed: () {},
),
CustomLabelButton(
child: Text('Label 3'),
isActive: controller.page.round() == 2,
onPressed: () {},
),
],
);
},
),
// **** /FIX **** //
PageView(
physics: BouncingScrollPhysics(),
controller: controller,
children: <Widget>[
CustomPage(),
CustomPage(),
CustomPage(),
],
),
],
);
}
}
Fix if you need the index directly in the PageView children
You can use a stateful widget instead :
class Carousel extends StatefulWidget {
Carousel();
#override
_HomeHorizontalCarouselState createState() => _CarouselState();
}
class _CarouselState extends State<Carousel> {
final PageController controller = PageController();
int currentIndex = 0;
#override
void initState() {
super.initState();
/// Attach a listener which will update the state and refresh the page index
controller.addListener(() {
if (controller.page.round() != currentIndex) {
setState(() {
currentIndex = controller.page.round();
});
}
});
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Column(
children: <Widget>[
CustomLabelButton(
child: Text('Label 1'),
isActive: currentIndex == 0,
onPressed: () {},
),
CustomLabelButton(
child: Text('Label 2'),
isActive: currentIndex == 1,
onPressed: () {},
),
CustomLabelButton(
child: Text('Label 3'),
isActive: currentIndex == 2,
onPressed: () {},
),
]
),
PageView(
physics: BouncingScrollPhysics(),
controller: controller,
children: <Widget>[
CustomPage(isActive: currentIndex == 0),
CustomPage(isActive: currentIndex == 1),
CustomPage(isActive: currentIndex == 2),
],
),
],
);
}
}
This means that you are trying to access PageController.page (It could be you or by a third party package like Page Indicator), however, at that time, Flutter hasn't yet rendered the PageView widget referencing the controller.
Best Solution: Use FutureBuilder with Future.value
Here we just wrap the code using the page property on the pageController into a future builder, such that it is rendered little after the PageView has been rendered.
We use Future.value(true) which will cause the Future to complete immediately but still wait enough for the next frame to complete successfully, so PageView will be already built before we reference it.
class Carousel extends StatelessWidget {
final PageController controller;
Carousel({this.controller});
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
FutureBuilder(
future: Future.value(true),
builder: (BuildContext context, AsyncSnapshot<void> snap) {
//If we do not have data as we wait for the future to complete,
//show any widget, eg. empty Container
if (!snap.hasData) {
return Container();
}
//Otherwise the future completed, so we can now safely use the controller.page
return Text(controller.controller.page.round().toString);
},
),
//This PageView will be built immediately before the widget above it, thanks to
// the FutureBuilder used above, so whenever the widget above is rendered, it will
//already use a controller with a built `PageView`
PageView(
physics: BouncingScrollPhysics(),
controller: controller,
children: <Widget>[
AnyWidgetOne(),
AnyWidgetTwo()
],
),
],
);
}
}
Alternatively
Alternatively, you could still use a FutureBuilder with a future that completes in addPostFrameCallback in initState lifehook as it also will complete the future after the current frame is rendered, which will have the same effect as the above solution. But I would highly recommend the first solution as it is straight-forward
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
//Future will be completed here
// e.g completer.complete(true);
});
use this widget and modify it as you want:
class IndicatorsPageView extends StatefulWidget {
const IndicatorsPageView({
Key? key,
required this.controller,
}) : super(key: key);
final PageController controller;
#override
State<IndicatorsPageView> createState() => _IndicatorsPageViewState();
}
class _IndicatorsPageViewState extends State<IndicatorsPageView> {
int _currentPage = 0;
#override
void initState() {
widget.controller.addListener(() {
setState(() {
_currentPage = widget.controller.page?.toInt() ?? 0;
});
});
super.initState();
}
#override
void dispose() {
widget.controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
3,
(index) => IndicatorPageview(isActive: _currentPage == index, index: index),
),
);
}
}
class IndicatorPageview extends StatelessWidget {
const IndicatorPageview({
Key? key,
required this.isActive,
required this.index,
}) : super(key: key);
final bool isActive;
final int index;
#override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(right: 8),
width: 16,
height: 16,
decoration: BoxDecoration(color: isActive ?Colors.red : Colors.grey, shape: BoxShape.circle),
);
}
}
I'm trying to position some text over a video.
The video is currently taking as much space as possible while retaining it's original aspect ratio. Ideally I'd like it to keep doing this as I want the video to resize to fit the browser window.
I'm assuming I need to get get the height/width of the video dynamically..
If anyone knows how to make a video play automatically that would be great as well - I'm using the video_player package.
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
drawer: ResponsiveLayout.isSmallScreen(context) ? NavDrawer() : null,
body: Container(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
NavBar(),
Body(),
Footer(),
],
),
),
),
);
}
}
class Body extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ResponsiveLayout(
largeScreen: LargeScreen(),
mediumScreen: LargeScreen(),
smallScreen: LargeScreen(),
);
}
}
class LargeScreen extends StatefulWidget {
#override
_LargeScreenState createState() => _LargeScreenState();
}
class _LargeScreenState extends State<LargeScreen> {
VideoPlayerController _videoPlayerController;
Future<void> _initializeVideoPlayerFuture;
#override
void initState() {
_videoPlayerController = VideoPlayerController.asset(
'assets/videos/video.mp4',
);
_initializeVideoPlayerFuture = _videoPlayerController.initialize();
super.initState();
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 40),
child: Column(
children: <Widget>[
FutureBuilder(
future: _initializeVideoPlayerFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// If the VideoPlayerController has finished initialization, use
// the data it provides to limit the aspect ratio of the VideoPlayer.
return AspectRatio(
aspectRatio: _videoPlayerController.value.aspectRatio,
// Use the VideoPlayer widget to display the video.
child: VideoPlayer(_videoPlayerController),
);
} else {
// If the VideoPlayerController is still initializing, show a
// loading spinner.
return Center(child: CircularProgressIndicator());
}
},
),
],
),
);
}
#override
void dispose() {
super.dispose();
_videoPlayerController.dispose();
}
}
you can wrap your Widgets in stack widget and use positioning to position your text
AspectRatio(
aspectRatio: _videoPlayerController.value.aspectRatio,
// Use the VideoPlayer widget to display the video.
child: Stack(children:<Widget>[
VideoPlayer(_videoPlayerController),
Positioned(bottom:10,left:10,
child:Text("my text here))
])
)
update
If anyone knows how to make a video play automatically that would be great as well - I'm using the video_player package.
you can set the state of your video using your controller
_videoPlayerController.value.isPlaying
I want the video to resize to fit the browser window.
you can wrap your video in a SizedBox.expand which Creates a box that will become as large as its parent allows.
sized box constructors