Can anyone help?
Currently, the Text that I am displaying over a video has a fixed size and position.
I'm wondering how I can change this dynamically/responsively to match the size of its parent widget (the Video).
I tried a method using a GlobalKey but got an error, I think it's because the video hadn't loaded..
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 &&
!_videoPlayerController.value.isBuffering) {
// 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: Stack(
children: <Widget>[
VideoPlayer(_videoPlayerController),
Positioned(
bottom: 20,
left: 20,
child: FittedBox(
child: Text(
'Text over\na video',
style: TextStyle(
color: Colors.white,
fontSize:50),
),
),
)
],
),
);
} else {
// If the VideoPlayerController is still initializing, show a
// loading spinner.
return Center(child: CircularProgressIndicator());
}
},
),
],
),
);
}
#override
void dispose() {
super.dispose();
_videoPlayerController.dispose();
}
}
LayoutBuilder can provide you with width and height properties which corresponds to the currently available space. Check this documentation here. It provides the builder with a BoxConstraints instance as in here. You can use this information to size your text.
Check the Align widget. It can place the child on specific location within the parent widget's coordinate system. In your case it will be on the coordinates of the Stack widget.
I would try something like the following.
Wrap the Text widget inside a Align widget and use FractionalOffset to place align the widget. You can directly use the Alignment instance also. The origin will vary vary for both the approach. Check the docs here
Then Wrap my Align widget inside a LayoutBuilder widget to get the available size and decide my font size based on it. Something like fontSize: constraints.maxWidth / 25
Below is sample working code.
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
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(),
// );
return LargeScreen();
}
}
class LargeScreen extends StatefulWidget {
#override
_LargeScreenState createState() => _LargeScreenState();
}
class _LargeScreenState extends State<LargeScreen> {
VideoPlayerController _videoPlayerController;
Future<void> _initializeVideoPlayerFuture;
#override
void initState() {
_videoPlayerController = VideoPlayerController.network(
'http://www.sample-videos.com/video123/mp4/720/big_buck_bunny_720p_20mb.mp4',
);
_initializeVideoPlayerFuture =
_videoPlayerController.initialize().then((onValue) {
setState(() {});
});
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 &&
!_videoPlayerController.value.isBuffering) {
// 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: Stack(
children: <Widget>[
VideoPlayer(_videoPlayerController),
LayoutBuilder(
builder: (context, constraints) {
return Align(
// this decides the position of the text.
alignment: FractionalOffset(0.05, 0.95),
child: FittedBox(
child: Text(
'Text over\na video',
style: TextStyle(
color: Colors.white,
// here font size is ratio of the maxwidth available for this widget.
fontSize: constraints.maxWidth / 25,
),
),
),
);
},
)
],
),
);
} else {
// If the VideoPlayerController is still initializing, show a
// loading spinner.
return Center(child: CircularProgressIndicator());
}
},
),
FloatingActionButton(
onPressed: () {
setState(() {
_videoPlayerController.value.isPlaying
? _videoPlayerController.pause()
: _videoPlayerController.play();
});
},
child: Icon(
_videoPlayerController.value.isPlaying
? Icons.pause
: Icons.play_arrow,
),
),
],
),
);
}
#override
void dispose() {
super.dispose();
_videoPlayerController.dispose();
}
}
It's easily accessible via MediaQuery.of(context).size (Documentation).
Remember that you have to call inside your build method since it demands the context
Related
I want to show an overlay on the whole app so I tried to insert an overlay entry on the context of MaterialApp (root widget) but the problem is I'm getting the null value on invoking the following method :
Overlay.of(context);
GetMaterialApp.router(
debugShowCheckedModeBanner: false,
theme: AppTheme.lightTheme,
scaffoldMessengerKey: Keys.scaffold,
scrollBehavior: MyCustomScrollBehavior(),
routeInformationParser: WebRoutes.goRouter.routeInformationParser,
routerDelegate: WebRoutes.goRouter.routerDelegate,
routeInformationProvider: WebRoutes.goRouter.routeInformationProvider,
builder: (context, child) {
WidgetsBinding.instance.addPostFrameCallback((_){
addOverlay(context);
});
return child;
}
void addOverlay(BuildContext context) {
print(Overlay.of(context));
return Overlay.of(context)?.insert(OverlayEntry(
builder: (context) {
return SomeWidget();
},
));
}
Is there any way to get the state of overlay using the context of this root widget as I want to show the overlay globally.
Thanks alot, I really appreciate that If someone helps me.
MaterialApp(
navigatorKey: getIt.get<NavigatorService>().navigatorKey,
theme: AppTheme.defaultTheme,
initialRoute: AppRoutes.splashScreen,
builder: (context, child) {
return Scaffold(
body: Stack(
children: [
child!,
Positioned(
top: 15,
child: Container(
color: Colors.red,
height: 50,
width: MediaQuery.of(context).size.width,
child: const Center(child: Text("HI I AM AN OVERLAY")),
),
),
],
),
);
},
onGenerateRoute: AppRoutes.onGenerateRoute,
),
You can achieve that by create a class responsible to display/remove the overlay, this class need receive a BuildContext when creating to be able to create an instance of Overlay.
Basically what you need to do are:
Create a class OverlayScreen that build the OverlayState && OverlayEntry (in this case the OverylayEntry will be a list of OverlayEntry since we might have more than one Overlay on the screen so we can remove all of them at once).
Create an instance of this class earlier in your app (e.g MyApp). In your case you'll need to call this inside Material.router...builder param.
Access this overlayScreen in your HomePage to display|removeAll overlays
Lets create our OverlayScreen
import 'package:flutter/material.dart';
class OverlayScreen {
/// Create an Overlay on the screen
/// Declared [overlayEntrys] as List<OverlayEntry> because we might have
/// more than one Overlay on the screen, so we keep it on a list and remove all at once
BuildContext _context;
OverlayState? overlayState;
List<OverlayEntry>? overlayEntrys;
void closeAll() {
for (final overlay in overlayEntrys ?? <OverlayEntry>[]) {
overlay.remove();
}
overlayEntrys?.clear();
}
void show() {
overlayEntrys?.add(
OverlayEntry(
builder: (context) {
return _buildOverlayWidget();
},
),
);
overlayState?.insert(overlayEntrys!.last);
}
OverlayScreen._create(this._context) {
overlayState = Overlay.of(_context);
overlayEntrys = [];
}
factory OverlayScreen.of(BuildContext context) {
return OverlayScreen._create(context);
}
Widget _buildOverlayWidget() {
return Positioned(
top: 20,
left: 20,
right: 20,
child: Container(
width: 300,
color: Colors.black,
height: 300,
child: const Text("MY CHAT"),
),
);
}
}
Now lets create an instance on MyApp
// Need to have it global to be able to access everywhere
OverlayScreen? overlayScreen;
void main() {
runApp(
const MyApp(),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: const HomePage(),
builder: (context, child) {
return Overlay(
initialEntries: [
OverlayEntry(
builder: (context) {
// Create an instance of `OverlayScreen` to be accessed globally
overlayScreen = OverlayScreen.of(context);
return child ?? const SizedBox();
},
),
],
);
},
);
}
}
To finalise lets create our HomePage and access our overlayScreen there there
import 'package:flutter/material.dart';
import 'package:overlay_all_app/src/overlay_screen.dart';
class HomePage extends StatelessWidget {
const HomePage({super.key});
#override
Widget build(BuildContext context) {
// Create an instance of OverlayScreen
final overlayScreen = OverlayScreen.of(context);
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextButton(
onPressed: () {
// display the overlay
overlayScreen.show();
},
child: const Text('Display Overlay'),
),
const SizedBox(height: 30),
TextButton(
onPressed: () {
// Call your next screen here
},
child: const Text('Go to next page'),
),
const SizedBox(height: 30),
TextButton(
onPressed: () {
// removed all overlays on the screen
overlayScreen.closeAll();
},
child: const Text('Close Overlay'),
),
],
),
),
);
}
}
That's it. You can use this class OverlayScreen to show/removeAll wherever you want.
I created a PR with sample code, check it out https://github.com/antonio-nicolau/flutter-working-with-overlay
import 'package:flutter/material.dart';
import 'package:get/get_navigation/src/root/get_material_app.dart';
import 'package:go_router/go_router.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(App2());
}
class App2 extends StatelessWidget {
App2({super.key});
final _router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const OverlayWrapper(),
),
],
);
#override
Widget build(BuildContext context) {
return GetMaterialApp.router(
routeInformationParser: _router.routeInformationParser,
routerDelegate: _router.routerDelegate,
routeInformationProvider: _router.routeInformationProvider,
);
}
}
class OverlayWrapper extends StatefulWidget {
const OverlayWrapper({Key? key}) : super(key: key);
#override
State<OverlayWrapper> createState() => _OverlayWrapperState();
}
class _OverlayWrapperState extends State<OverlayWrapper> {
#override
void initState() {
super.initState();
}
showOverLay() {
OverlayEntry overlayEntry = OverlayEntry(
builder: (context) => Container(
color: Colors.red,
child: const Text('data'),
),
);
Overlay.of(context).insert(overlayEntry);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {
showOverLay();
},
child: const Text(
'ShowOverlay',
style: TextStyle(),
),
),
),
);
}
}
How am I supposed to pass a value in this big mess called Flutter?
30 years old php global $var wasn't good?
All these years were to come up with setState, passed in a controller which get redeclared as a key inside a stateful widget that receive the value from a Navigator?
By the way, I tried using Navigator.push but it seems to open a completely new window, the value is there but I'd need it to show in the tab body not in a new window, below is my code:
main.dart
import 'dart:core';
import 'dart:developer';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:qr_code_scanner/qr_code_scanner.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter App',
theme: ThemeData(
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: HomeView(),
);
}
}
class HomeView extends StatefulWidget {
#override
_HomeViewState createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> {
final tabs = [QRViewExample(), SecondView(res: '')];
int _currentIndex = 0;
#override
void initState() {
setState(() {});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
toolbarHeight: 40.0,
elevation: 0,
centerTitle: true,
title: Text('Flutter App'),
),
body: tabs[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
backgroundColor: Colors.red,
currentIndex: _currentIndex,
type: BottomNavigationBarType.fixed,
selectedItemColor: Colors.white,
unselectedItemColor: Colors.white.withOpacity(0.5),
items: [
BottomNavigationBarItem(
icon: Icon(Icons.qr_code),
label: 'Scan',
),
BottomNavigationBarItem(
icon: Icon(Icons.list),
label: 'List',
),
],
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
),
);
}
}
// SECOND TAB WIDGET (custom)
class SecondView extends StatelessWidget {
const SecondView({Key? key, required this.res}) : super(key: key);
final String? res;
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Text(res!),
),
);
}
}
// FIRST TAB WIDGET (qrcode)
class QRViewExample extends StatefulWidget {
const QRViewExample({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() => _QRViewExampleState();
}
class _QRViewExampleState extends State<QRViewExample> {
Barcode? result;
QRViewController? controller;
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
#override
void reassemble() {
super.reassemble();
if (Platform.isAndroid) {
controller!.pauseCamera();
}
controller!.resumeCamera();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
height: 500,
child: Padding(
padding: EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
Expanded(flex: 4, child: _buildQrView(context)),
Expanded(
flex: 1,
child: FittedBox(
fit: BoxFit.contain,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
if (result != null)
Text(
'Barcode Type: ${describeEnum(result!.format)} Data: ${result!.code}')
else
const Text('Scan a code'),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
margin: const EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () async {
await controller?.toggleFlash();
setState(() {});
},
child: FutureBuilder(
future: controller?.getFlashStatus(),
builder: (context, snapshot) {
return Text('Flash: ${snapshot.data}');
},
)),
),
Container(
margin: const EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () async {
await controller?.flipCamera();
setState(() {});
},
child: FutureBuilder(
future: controller?.getCameraInfo(),
builder: (context, snapshot) {
if (snapshot.data != null) {
return Text(
'Camera facing ${describeEnum(snapshot.data!)}');
} else {
return const Text('loading');
}
},
)),
)
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
margin: const EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () async {
await controller?.pauseCamera();
},
child: const Text('pause',
style: TextStyle(fontSize: 20)),
),
),
Container(
margin: const EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () async {
await controller?.resumeCamera();
},
child: const Text('resume',
style: TextStyle(fontSize: 20)),
),
)
],
),
],
),
),
)
],
),
),
),
);
}
Widget _buildQrView(BuildContext context) {
var scanArea = (MediaQuery.of(context).size.width < 400 ||
MediaQuery.of(context).size.height < 400)
? 150.0
: 300.0;
return QRView(
key: qrKey,
onQRViewCreated: _onQRViewCreated,
overlay: QrScannerOverlayShape(
borderColor: Colors.cyanAccent,
borderRadius: 10,
borderLength: 30,
borderWidth: 10,
cutOutSize: scanArea),
onPermissionSet: (ctrl, p) => _onPermissionSet(context, ctrl, p),
);
}
void _onQRViewCreated(QRViewController controller) {
setState(() {
this.controller = controller;
});
controller.scannedDataStream.listen((scanData) {
controller.pauseCamera();
setState(() {
result = scanData;
});
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondView(res: result!.code)))
.then((value) => controller.resumeCamera());
});
}
void _onPermissionSet(BuildContext context, QRViewController ctrl, bool p) {
log('${DateTime.now().toIso8601String()}_onPermissionSet $p');
if (!p) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('no Permission')),
);
}
}
#override
void dispose() {
controller?.dispose();
super.dispose();
}
}
How am I supposed to pass a value in this big mess called Flutter?
With state management tools like InheritedWidget, InheritedModel, Provider, BloC and many more.
30 years old php global $var wasn't good? All these years were to come up with setState, passed in a controller which get redeclared as a key inside a stateful widget that receive the value from a Navigator?
Well, you shouldn't do that and it's not meant to be done like that. We can use several methods to propagate data down the widget tree. Let me explain this with InheritedWidget. But sometimes you want to go for Provider which is a wrapper class for InheritedWidget.
First we create a class named QRListModel which extends InheritedModel:
class QRListModel extends InheritedWidget {
final List<Barcode> qrList = []; // <- This holds our data
QRListModel({required super.child});
#override
bool updateShouldNotify(QRListModel oldWidget) {
return !listEquals(oldWidget.qrList, qrList);
}
static QRListModel of(BuildContext context) {
final QRListModel? result = context.dependOnInheritedWidgetOfExactType<QRListModel>();
assert(result != null, 'No QRListModel found in context');
return result!;
}
}
updateShouldNotify is a method we have to override to tell Flutter, when we want the widgets to rebuild. We want this to happen when the list changes. The of method is just a handy way to access the QRListModel.
Now wrap a parent widget of both the scan tab view and the list tab view inside QRListModel. We go for HomeView:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter App',
theme: ThemeData(
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: QRListModel(child: HomeView()), // <- here!
);
}
}
We can take any parent widget but it should be a class where we don't call setState. Otherwise our QRListModel also gets rebuilt and our list is gone.
Now we can access QRListModel from anywhere inside the subtree. We need it here:
void _onQRViewCreated(QRViewController controller) {
setState(() {
this.controller = controller;
this.controller!.resumeCamera();
});
controller.scannedDataStream.listen((scanData) async {
controller.pauseCamera();
QRListModel.of(context).qrList.add(scanData); // <- Here we access the list
await showDialog(
context: context,
builder: (context) => SimpleDialog(
title: Text("Barcode was added!"),
children: [
Text(scanData.code!)
],
)
);
});
}
And here we read the list:
class SecondView extends StatelessWidget {
const SecondView({Key? key, required this.res}) : super(key: key);
final String? res;
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: QRListModel.of(context).qrList.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text(QRListModel.of(context).qrList[index].code ?? "NO"),
),
);
}
);
}
}
Now both pages have access to the qr list. Please do mind that a InheritedWidget can only have final fields. So if you need mutable fields, you need an additional wrapper class. We don't need it as we don't change the list but only its elements.
By the way: You shouldn't call setState inside initState. You did this here:
class _HomeViewState extends State<HomeView> {
final tabs = [QRViewExample(), SecondView(res: '')];
int _currentIndex = 0;
#override
void initState() {
setState(() {}); // <- Don't call setState inside initState!
super.initState();
}
I'm having a hard time displaying a video using the video_player package. I'm new to flutter and am trying to create a simple website with a title, some text, and a local video displayed in the center of the screen with a title. The video is local and is in an assets folder, and I have also added the video_player and video location to the pubspec.yaml file. Any advice or suggestions are appreciated, I would like to improve at this language. Below is the code I have put together so far. Thanks!
import 'package:video_player/video_player.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Center(
child: Text(
"My Website",
textScaleFactor: 2,
style: TextStyle(color: Colors.white),
),
),
),
body: Column(
children: [
MyHomePage(),
],
),
),
);
}
}
MyHomePage class:
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
#override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(32),
child: Row(
children: [
Expanded(
/*1*/
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/*2*/
Container(
padding: const EdgeInsets.only(bottom: 8),
child: Center(
child: Text(
'More photos & videos coming soon',
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
),
),
Container(
padding: const EdgeInsets.only(bottom: 8),
child: Center(child: BackgroundVideo()),
),
],
),
),
],
),
);
}
}
Stateful video player that I would like to embed in the home page:
//need to ensure this video fits on the screen and doesn't overflow the bottom.
class BackgroundVideo extends StatefulWidget {
const BackgroundVideo({super.key});
#override
_BackgroundVideoState createState() => _BackgroundVideoState();
}
class _BackgroundVideoState extends State<BackgroundVideo> {
late VideoPlayerController _controller;
late Future<void> _initializeBackgroundVideoFuture;
#override
void initState() {
super.initState();
//Create and store the BackgroundVideoController.
_controller = VideoPlayerController.asset(
"build\assets\welcomescreen.mp4",
);
_initializeBackgroundVideoFuture = _controller.initialize();
_controller.setLooping(true);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('First Video'),
),
// Use a FutureBuilder to display a loading spinner while waiting for the
// VideoPlayerController to finish initializing.
body: FutureBuilder(
future: _initializeBackgroundVideoFuture,
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 video.
return AspectRatio(
aspectRatio: _controller.value.aspectRatio,
// Use the VideoPlayer widget to display the video.
child: VideoPlayer(_controller),
);
} else {
// If the VideoPlayerController is still initializing, show a
// loading spinner.
return const Center(
child: CircularProgressIndicator(),
);
}
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// Wrap the play or pause in a call to `setState`. This ensures the
// correct icon is shown.
setState(() {
// If the video is playing, pause it.
if (_controller.value.isPlaying) {
_controller.pause();
} else {
// If the video is paused, play it.
_controller.play();
}
});
},
// Display the correct icon depending on the state of the player.
child: Icon(
_controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
),
),
);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
}
This is probably a really easy-to-fix question, but I figured that I might as well ask it here anyways: Is there any way to anchor a widget within a CustomScrollView? I want to use the CustomScrollView to support a flexible space in the app bar, but I need to have an input widget stay fixed at the bottom of the screen. I tried nesting the CustomScrollView into a Column with the given widget, but it doesn't seem to be working:
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
CustomScrollView(
slivers: <Widget>[
_buildAppBar(), // Returns a SliverAppBar
_buildMessages(), // Returns a StreamBuilder that returns a SliverList
],
),
MessageInputWidget(), // Input that needs to stay fixed
],
);
}
And here's that _buildMessages() method:
Widget _buildMessages(BuildContext context) {
return StreamBuilder<List<Message>>(
stream: widget.classroom.messages(),
builder: (context, snapshot) {
print('[DEBUG] Building chat with updated message stream...');
if (!snapshot.hasData || snapshot.data == null) {
return Center(
child: CircularProgressIndicator(),
);
}
_messages = snapshot.data;
print('[DEBUG] Building ${_messages.length} messages...');
return SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
if (index == _messages.length) {
return _buildHeader(context);
}
return MessageWidget(
message: _messages[index],
isReceived: _user.id != _messages[index].sentBy.id,
showUser: (index ==
0) || // Show _avatar if it's the first msg
(index >=
1 && // Or if it's a different _user than the last
!(_messages[index].sentBy.id ==
_messages[index - 1].sentBy.id)),
);
},
childCount: _messages.length,
),
);
});
}
Any suggestions? I've found some examples but that builds the whole CustomScrollView while I only want to build the SliverList whenever I get a new snapshot.
Any suggestions?
Yes, but for that you don't put it in the custom scroll, you use a stack widget. It puts the layered widget on the screen. What comes below you put before. In order for your widget to be positioned at the bottom, you must use a column with an expanded.
Stack(
children: <Widget>[
yourStreamBuilder(),
Column(
children: <Widget>[
Expanded(child: Container()),
Container(
width: MediaQuery.of(context).size.width,
height: 44,
color: Colors.red,
child: Text("Your bottom container"),
)
],
),
],
)
Complete exemple:
import 'package:flutter/material.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: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Map<String, dynamic> tocando = {};
String ultimoTocando;
Future<List<Map<String, dynamic>>> listaDeMusicas() async {
return List<Map<String, dynamic>>.generate(
1200,
(i) => {"audio": "musica $i", "idUnico": "$i"},
);
}
tocar(String musica) {
print("tocando $musica ");
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Stack(
children: <Widget>[
yourStreamBuilder(),
Column(
children: <Widget>[
Expanded(child: Container()),
Container(
width: MediaQuery.of(context).size.width,
height: 44,
color: Colors.red,
child: Text("Your bottom container"),
)
],
),
],
));
}
Widget yourStreamBuilder() {
return FutureBuilder<List<Map<String, dynamic>>>(
future: listaDeMusicas(),
initialData: [],
builder: (context, snapshot) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
var item = snapshot.data[index];
if (tocando[item["idUnico"]] == null)
tocando[item["idUnico"]] = false;
return Row(
children: <Widget>[
IconButton(
icon: Icon(
tocando[item["idUnico"]] ? Icons.stop : Icons.play_arrow),
onPressed: () {
setState(() {
if (ultimoTocando != null) {
tocando[ultimoTocando] = false;
}
if (ultimoTocando != item["idUnico"]) {
tocando[item["idUnico"]] = !tocando[item["idUnico"]];
}
if (tocando[item["idUnico"]]) {
tocar(item["audio"]);
}
});
ultimoTocando = item["idUnico"];
},
),
Text(item["audio"]),
],
);
},
);
},
);
}
}
I have a problem when displaying data to list view using the stream builder, my list view is always re-reload, when the tab is active.
i am implementing AutomaticKeepAliveClientMixin, but that is still not working.
here my code:
Home Bottom Nav :
https://pastebin.com/B9qf0zZR
List View index:
import 'package:eservice_f/src/blocs/listDataBloc.dart';
import 'package:eservice_f/src/models/listModel.dart';
import 'package:eservice_f/utils/layout.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class ServiceIndex extends StatefulWidget {
ServiceIndex({Key key}) : super(key: key);
_ServiceIndexState createState() => _ServiceIndexState();
}
class _ServiceIndexState extends State<ServiceIndex> with AutomaticKeepAliveClientMixin<ServiceIndex>{
ListDataBloc _bloc;
#override
// TODO: implement wantKeepAlive
bool get wantKeepAlive => true;
#override
void initState() {
// TODO: implement initState
super.initState();
_bloc = ListDataBloc();
_bloc.showAllData();
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
//bloclist.dispose();
}
#override
Widget build(BuildContext context) {
super.build(context);
return Container(
child: StreamBuilder(
stream: _bloc.allData,
builder: (context, AsyncSnapshot<ListData> snapshot) {
print(snapshot.data);
if (snapshot.hasData) {
return Container(
color: Colors.white,
child: Center(
child: getServiceList(context, snapshot),
),
);
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
}
return Container(
color: Colors.white,
child: Center(child: CupertinoActivityIndicator()));
}),
);
}
Widget getServiceList(
BuildContext context, AsyncSnapshot<ListData> snapshot) {
SizeConfig().init(context);
var _list_data = snapshot.data.data;
return ListView.builder(
itemCount: _list_data.length,
itemBuilder: (BuildContext contex, int index) {
return Column(
children: <Widget>[
ListTile(
onTap: () {
print("List Tapped");
},
leading: Column(
children: <Widget>[
Icon(
Icons.check_circle_outline,
size: SizeConfig.blocHorizontal * 10,
),
],
),
title: Text("SBG/LK/20180814"),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('SusSystem Update Training MEX',
overflow: TextOverflow.ellipsis),
Text(
'So basically you building a Facebook/Instagram like application, where user logs in, scrolls through their feed, stalks through different profiles, and when done, wants to log out of the app',
overflow: TextOverflow.ellipsis),
],
),
trailing: (_list_data[index].activityStatus == "1")
? Text("Draft")
: Text("Confirm"),
),
Divider(
height: 1.0,
),
],
);
});
}
}
Bloc:
import 'package:eservice_f/src/models/listModel.dart';
import 'package:eservice_f/src/resources/repository.dart';
import 'package:rxdart/rxdart.dart';
class ListDataBloc{
final _repository = Repository();
final _fetcher = PublishSubject<ListData>();
Observable<ListData> get allData => _fetcher.stream;
showAllData() async{
ListData datas = await _repository.fetchAll();
_fetcher.sink.add(datas);
}
dispose(){
_fetcher.close();
}
}
//initial Bloc
//final bloclist = ListDataBloc();