I have a flutter project that i added web support to it. Now in my page i have 2 button.I want to when user clicked on each button, a web page shown.So in order to i create custom widget :
#override
Widget build(BuildContext context) {
print("url is ------> ${url}");
isAddedElement();
_iframeElement.height = double.maxFinite.toString();
_iframeElement.width = double.maxFinite.toString();
_iframeElement.src = url;
_iframeElement.style.border = 'none';
_iframeElement.id = 'iframe';
ui.platformViewRegistry.registerViewFactory(
'iframeElement',
(int viewId) => _iframeElement,
);
_iframeWidget = HtmlElementView(
// key: UniqueKey(),
viewType: 'iframeElement',
);
return SizedBox(
height: 600,
width: 600,
child: _iframeWidget,
);
}
inisAddedElement method i want to check iframeElement.id = 'iframe' is exist or not?If exist i want to change src with the new web page:
bool isAddedElement() {
IFrameElement frame = document.querySelector('iframe');
if (frame != null) frame.src = url;
}
but document.querySelector('iframe') always return null?
The query returns null, most likely because flutter adds the HtmlElementView widget inside a shadowDom and these elements are not available directly in the document scope.
In order to access the element you need to go via a shadowroot which is usually placed under custome tag called flutter-platform-view.
So the following approaches may work.
Accessing via flt-platform-view
Try to access the iframe element as shown below.
document.getElementsByTagName('flutter-platform-view')[0].shadowRoot.getElementById('iframe');
Accessing the IframeElement instance
You already have the reference of IframeElement instance and can check what you need with it as shown below.
bool isAddedElement() {
//IFrameElement frame = document.querySelector('iframe');
if (_iframeElement!= null && _iframeElement.src == null) _iframeElement.src = url;
}
However I don't understand why in your code you are calling the isAddedElement() even before setting up the IframeElement.
You can find more details about the why its put behind a shadow dom in this issue in github.
Here is full working demo using the first approach. The live version is available as a codepen demo
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
import 'dart:html';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: IframeDemo(),
),
),
);
}
}
class IframeDemo extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MyWidget();
}
}
class MyWidget extends State<IframeDemo> {
String _url;
IFrameElement _iframeElement;
#override
initState() {
super.initState();
_url = 'https://en.wikipedia.org/wiki/Twitter';
_iframeElement = IFrameElement()
..src = _url
..id = 'iframe'
..style.border = 'none';
ui.platformViewRegistry.registerViewFactory(
'iframeElement',
(int viewId) => _iframeElement,
);
}
void updateUrl(String page) {
setState(() {
// building a new url
_url = 'https://en.wikipedia.org/wiki/$page';
// Setting the url to the src field of the iframe element.
HtmlElement elem = document.getElementsByTagName('flt-platform-view')[0];
IFrameElement ifrelem = elem.shadowRoot.getElementById('iframe');
ifrelem..src = _url;
});
}
#override
Widget build(BuildContext context) {
print('url is $_url');
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
MaterialButton(
color: Colors.blueAccent,
child: Text('Show Twitter wiki'),
onPressed: () {
updateUrl('Twitter');
},
),
SizedBox(
width: 50,
),
MaterialButton(
color: Colors.orangeAccent,
child: Text('Show Facebook Wiki'),
onPressed: () {
updateUrl('Facebook');
},
),
],
),
SizedBox(
height: 100,
),
SizedBox(
height: 600,
width: 600,
child: HtmlElementView(
// key: UniqueKey(),
viewType: 'iframeElement',
),
),
],
);
}
}
Related
In my app I am generating a ListView and items can be highlighted by tapping on them. That works fine and I also have a callback function that gives me the key for the just selected item. I can currently manually deselect the item by tapping on it again, but will ultimately take that functionality out.
My problem is that I want one and only one item to be selected at a time. In order to create the list I currently take some initial content in the form of a list, generate the tiles and add them to another list. I then use that list to create the ListView. My plan was on the callback from a new selection, run through the list of tiles and deselect them before highlighting the new chosen tile and carrying out the other functions. I have tried various methods to tell each tile to deselect itself but have not found any way to address each of the tiles. Currently I get the error:
Class 'OutlineTile' has no instance method 'deselect'.
Receiver: Instance of 'OutlineTile'
Tried calling: deselect()
I have tried to access a method within the tile class and to use a setter but neither worked so far. I am quite new to flutter so it could be something simple I am missing. My previous experience was with Actionscript where this system would have worked fine and I could access a method of an object (in this case the tile) easily as long s it is a public method.
I'd be happy to have another way to unselect the old item or to find a way to access a method within the tile. The challenge is to make the tiles show not highlighted without them being tapped themselves but when a different tile is tapped.
The code in my parent class is as follows:
class WorkingDraft extends StatefulWidget {
final String startType;
final String name;
final String currentContent;
final String currentID;
final List startContent;
WorkingDraft(
{this.startType,
this.name,
this.currentContent,
this.currentID,
this.startContent});
#override
_WorkingDraftState createState() => _WorkingDraftState();
}
class _WorkingDraftState extends State<WorkingDraft> {
final _formKey = GlobalKey<FormState>();
final myController = TextEditingController();
//String _startType;
String _currentContent = "";
String _name = "Draft";
List _startContent = [];
List _outLineTiles = [];
int _counter = 0;
#override
void dispose() {
// Clean up the controller when the widget is disposed.
myController.dispose();
super.dispose();
}
void initState() {
super.initState();
_currentContent = widget.currentContent;
_name = widget.name;
_startContent = widget.startContent;
_counter = 0;
_startContent.forEach((element) {
_outLineTiles.add(OutlineTile(
key: Key("myKey$_counter"),
outlineName: element[0],
myContent: element[1],
onTileSelected: clearHilights,
));
_counter++;
});
}
dynamic clearHilights(Key myKey) {
_outLineTiles.forEach((element) {
element.deselect(); // this throws an error Class 'OutlineTile' has no instance method 'deselect'.
Key _foundKey = element.key;
print("Element Key $_foundKey");
});
}
.......
and further down within the widget build scaffold:
child: ListView.builder(
itemCount: _startContent.length,
itemBuilder: (context, index) {
return _outLineTiles[index];
},
),
Then the tile class is as follows:
class OutlineTile extends StatefulWidget {
final Key key;
final String outlineName;
final Icon myIcon;
final String myContent;
final Function(Key) onTileSelected;
OutlineTile(
{this.key,
this.outlineName,
this.myIcon,
this.myContent,
this.onTileSelected});
#override
_OutlineTileState createState() => _OutlineTileState();
}
class _OutlineTileState extends State<OutlineTile> {
Color color;
Key _myKey;
#override
void initState() {
super.initState();
color = Colors.transparent;
}
bool _isSelected = false;
set isSelected(bool value) {
_isSelected = value;
print("set is selected to $_isSelected");
}
void changeSelection() {
setState(() {
_myKey = widget.key;
_isSelected = !_isSelected;
if (_isSelected) {
color = Colors.lightBlueAccent;
} else {
color = Colors.transparent;
}
});
}
void deselect() {
setState(() {
isSelected = false;
color = Colors.transparent;
});
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 4.0),
child: Row(
children: [
Card(
elevation: 10,
margin: EdgeInsets.fromLTRB(10.0, 6.0, 5.0, 0.0),
child: SizedBox(
width: 180,
child: Container(
color: color,
child: ListTile(
title: Text(widget.outlineName),
onTap: () {
if (widget.outlineName == "Heading") {
Text("Called Heading");
} else (widget.outlineName == "Paragraph") {
Text("Called Paragraph");
widget.onTileSelected(_myKey);
changeSelection();
},
),
........
Thanks for any help.
Amended Code sample and explanation, that builds to a complete project, from here:
Following the advice from phimath I have created a full buildable sample of the relevant part of my project.
The problem is that the tiles in my listview are more complex with several elements, many of which are buttons in their own right so whilst phimath's solution works for simple text tiles I have not been able to get it working inside my own project. My approach is trying to fundamentally do the same thing as phimath's but when I include these more complex tiles it fails to work.
This sample project is made up of three files. main.dart which simply calls the project and passes in some dummy data in the way my main project does. working_draft.dart which is the core of this issue. And outline_tile.dart which is the object that forms the tiles.
Within working draft I have a function that returns an updated list of the tiles which should show which tile is selected (and later any other changes from the other buttons). This gets called when first going to the screen. When the tile is tapped it uses a callback function to redraw the working_draft class but this seems to not redraw the list as I would expect it to. Any further guidance would be much appreciated.
The classes are:
first class is main.dart:
import 'package:flutter/material.dart';
import 'package:listexp/working_draft.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: WorkingDraft(
startType: "Basic",
name: "Draft",
currentID: "anID",
startContent: [
["Heading", "New Heading"],
["Paragraph", "New Text"],
["Image", "placeholder"],
["Signature", "placeholder"]
],
));
}
}
Next file is working_draft.dart:
import 'package:flutter/material.dart';
import 'package:listexp/outline_tile.dart';
class WorkingDraft extends StatefulWidget {
final String startType;
final String name;
final String currentContent;
final String currentID;
final List startContent;
final int selectedIndex;
WorkingDraft(
{this.startType,
this.name,
this.currentContent,
this.currentID,
this.startContent,
this.selectedIndex});
#override
_WorkingDraftState createState() => _WorkingDraftState();
}
class _WorkingDraftState extends State<WorkingDraft> {
int selectedIndex;
String _currentContent = "";
String _name = "Draft";
List _startContent = [];
var _outLineTiles = [];
int _counter = 0;
int _selectedIndex;
bool _isSelected;
dynamic clearHilights(int currentIndex) {
setState(() {
_selectedIndex = currentIndex;
});
}
updatedTiles() {
if (_selectedIndex == null) {
_selectedIndex = 0;
}
_currentContent = widget.currentContent;
_name = widget.name;
_startContent = widget.startContent;
_counter = 0;
_outLineTiles = [];
_startContent.forEach((element) {
_isSelected = _selectedIndex == _counter ? true : false;
_outLineTiles.add(OutlineTile(
key: Key("myKey$_counter"),
outlineName: element[0],
myContent: element[1],
myIndex: _counter,
onTileSelected: clearHilights,
isSelected: _isSelected,
));
_counter++;
});
}
#override
Widget build(BuildContext context) {
updatedTiles();
return Scaffold(
body: Center(
child: Column(children: [
SizedBox(height: 100),
Text("Outline", style: new TextStyle(fontSize: 15)),
Container(
height: 215,
width: 300,
decoration: BoxDecoration(
border: Border.all(
color: Colors.lightGreenAccent,
width: 2,
),
borderRadius: BorderRadius.circular(2),
),
child: ListView.builder(
itemCount: _startContent.length,
itemBuilder: (context, index) {
return _outLineTiles[index];
},
),
),
]),
));
}
}
and finally is outline_tile.dart
import 'package:flutter/material.dart';
class OutlineTile extends StatefulWidget {
final Key key;
final String outlineName;
final Icon myIcon;
final String myContent;
final int myIndex;
final Function(int) onTileSelected;
final bool isSelected;
OutlineTile(
{this.key,
this.outlineName,
this.myIcon,
this.myContent,
this.myIndex,
this.onTileSelected,
this.isSelected});
#override
_OutlineTileState createState() => _OutlineTileState();
}
class _OutlineTileState extends State<OutlineTile> {
Color color;
// Key _myKey;
bool _isSelected;
#override
void initState() {
super.initState();
_isSelected = widget.isSelected;
if (_isSelected == true) {
color = Colors.lightBlueAccent;
} else {
color = Colors.transparent;
}
}
void deselect() {
setState(() {
_isSelected = widget.isSelected;
if (_isSelected == true) {
color = Colors.lightBlueAccent;
} else {
color = Colors.transparent;
}
});
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 4.0),
child: Row(
children: [
Card(
elevation: 10,
margin: EdgeInsets.fromLTRB(10.0, 6.0, 5.0, 0.0),
child: SizedBox(
width: 180,
child: Container(
color: color,
child: ListTile(
title: Text(widget.outlineName),
onTap: () {
if (widget.outlineName == "Heading") {
Text("Called Heading");
} else if (widget.outlineName == "Paragraph") {
Text("Called Paragraph");
} else if (widget.outlineName == "Signature") {
Text("Called Signature");
} else {
Text("Called Image");
}
var _myIndex = widget.myIndex;
widget.onTileSelected(_myIndex);
deselect();
},
),
),
),
),
SizedBox(
height: 60,
child: Column(
children: [
SizedBox(
height: 20,
child: IconButton(
iconSize: 30,
icon: Icon(Icons.arrow_drop_up),
onPressed: () {
print("Move Up");
}),
),
SizedBox(height: 5),
SizedBox(
height: 20,
child: IconButton(
iconSize: 30,
icon: Icon(Icons.arrow_drop_down),
onPressed: () {
print("Move Down");
}),
),
],
),
),
SizedBox(
height: 60,
child: Column(
children: [
SizedBox(
height: 20,
child: IconButton(
iconSize: 20,
icon: Icon(Icons.add_box),
onPressed: () {
print("Add another");
}),
),
SizedBox(
height: 10,
),
SizedBox(
height: 20,
child: IconButton(
iconSize: 20,
icon: Icon(Icons.delete),
onPressed: () {
print("Delete");
}),
),
],
),
),
],
),
);
}
}
Thanks again
Instead of manually deselecting tiles, just keep track of which tile is currently selected.
I've made a simple example for you. When we click a tile, we just set the selected index to the index we clicked, and each tile looks at that to see if its the currently selected tile.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(body: Home()),
);
}
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
int selectedIndex;
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: 10,
itemBuilder: (context, index) {
return ListTile(
title: Text('Item: $index'),
tileColor: selectedIndex == index ? Colors.blue : null,
onTap: () {
setState(() {
selectedIndex = index;
});
},
);
},
);
}
}
I am into flutter to port my android app from java. One thing that is evident in flutter is widgets. Now my biggest obstacle to make my app work as it was on android is starting an async task to request data from the server. I have a custom progress dialog that can be shown or hidden.
class MySelection extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MySelectionState();
}
}
class MySelectionState extends State<MySelection> {
final globalKey = new GlobalKey<ScaffoldState>();
ProgressDialog progressDialog = ProgressDialog.getProgressDialog("Loading books ...");
List<Book> books;
void requestData() async {
EventObject eventObject = await getBooks();
books = eventObject.object;
populateData();
}
#override
Widget build(BuildContext context) {
if (books == null) {
books = List<Book>();
requestData();
}
var appBar = AppBar();
return Scaffold(
appBar: AppBar(
title: Text('Set up your Collection'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.refresh),
onPressed: () {
books = List<Book>();
requestData();
},
),
],
),
body: SingleChildScrollView(
child: Stack(
Container(
height: (MediaQuery.of(context).size.height - (appBar.preferredSize.height * 2)),
padding: const EdgeInsets.symmetric(horizontal: 10),
margin: EdgeInsets.only(top: 50.0),
child: ListView.builder(
physics: BouncingScrollPhysics(),
itemCount: books.length,
itemBuilder: bookListView,
),
),
Container(
height: (MediaQuery.of(context).size.height),
padding: const EdgeInsets.symmetric(horizontal: 10),
child: progressDialog,
),
],
),
),
}
}
Now, this code works well when I don't call the progress dialog unlike when I try to do that by calling my progressdialog widget.
if (books == null) {
progressDialog.showProgress();
books = List<Book>();
requestData();
}
It throws the error that
The method 'showProgress' was called on null. Receiver: null Tried
calling: showProgress()
Of course, the reason is that I am calling this before its widget is even created. Now my question is how can I do this because I can't afford to put a button for the user to click. I just want this to work on its own once the user is on this particular screen.
import 'package:flutter/scheduler.dart';
#override
void initState() {
super.initState();
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
// add your code which you want to execute after your build is complete
});
}
Thanks.
I am learning app development on Flutter and can't get my Slider to work within the AlertDialog. It won't change it's value.
I did search the problem and came across this post on StackOverFlow:
Flutter - Why slider doesn't update in AlertDialog?
I read it and have kind of understood it. The accepted answer says that:
The problem is, dialogs are not built inside build method. They are on a different widget tree. So when the dialog creator updates, the dialog won't.
However I am not able to understand how exactly does it have to be implemented as not enough background code is provided.
This is what my current implementation looks like:
double _fontSize = 1.0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(qt.title),
actions: <Widget>[
IconButton(
icon: Icon(Icons.format_size),
onPressed: () {
getFontSize(context);
},
),
],
),
body: ListView.builder(
padding: EdgeInsets.symmetric(vertical: 15.0),
itemCount: 3,
itemBuilder: (context, index) {
if (index == 0) {
return _getListTile(qt.scripture, qt.reading);
} else if (index == 1) {
return _getListTile('Reflection:', qt.reflection);
} else {
return _getListTile('Prayer:', qt.prayer);
}
})
);
}
void getFontSize(BuildContext context) {
showDialog(context: context,builder: (context){
return AlertDialog(
title: Text("Font Size"),
content: Slider(
value: _fontSize,
min: 0,
max: 100,
divisions: 5,
onChanged: (value){
setState(() {
_fontSize = value;
});
},
),
actions: <Widget>[
RaisedButton(
child: Text("Done"),
onPressed: (){},
)
],
);
});
}
Widget parseLargeText(String text) {...}
Widget _getListTile(String title, String subtitle) {...}
I understand that I will need to make use of async and await and Future. But I am not able to understand how exactly. I've spent more than an hour on this problem and can't any more. Please forgive me if this question is stupid and noobish. But trust me, I tried my best.
Here is a minimal runnable example. Key points:
The dialog is a stateful widget that stores the current value in its State. This is important because dialogs are technically separate "pages" on your app, inserted higher up in the hierarchy
Navigator.pop(...) to close the dialog and return the result
Usage of async/await
import 'package:flutter/material.dart';
void main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
double _fontSize = 20.0;
void _showFontSizePickerDialog() async {
// <-- note the async keyword here
// this will contain the result from Navigator.pop(context, result)
final selectedFontSize = await showDialog<double>(
context: context,
builder: (context) => FontSizePickerDialog(initialFontSize: _fontSize),
);
// execution of this code continues when the dialog was closed (popped)
// note that the result can also be null, so check it
// (back button or pressed outside of the dialog)
if (selectedFontSize != null) {
setState(() {
_fontSize = selectedFontSize;
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('Font Size: ${_fontSize}'),
RaisedButton(
onPressed: _showFontSizePickerDialog,
child: Text('Select Font Size'),
)
],
),
),
);
}
}
// move the dialog into it's own stateful widget.
// It's completely independent from your page
// this is good practice
class FontSizePickerDialog extends StatefulWidget {
/// initial selection for the slider
final double initialFontSize;
const FontSizePickerDialog({Key key, this.initialFontSize}) : super(key: key);
#override
_FontSizePickerDialogState createState() => _FontSizePickerDialogState();
}
class _FontSizePickerDialogState extends State<FontSizePickerDialog> {
/// current selection of the slider
double _fontSize;
#override
void initState() {
super.initState();
_fontSize = widget.initialFontSize;
}
#override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Font Size'),
content: Container(
child: Slider(
value: _fontSize,
min: 10,
max: 100,
divisions: 9,
onChanged: (value) {
setState(() {
_fontSize = value;
});
},
),
),
actions: <Widget>[
FlatButton(
onPressed: () {
// Use the second argument of Navigator.pop(...) to pass
// back a result to the page that opened the dialog
Navigator.pop(context, _fontSize);
},
child: Text('DONE'),
)
],
);
}
}
You just need to warp the AlertDialog() with a StatefulBuilder()
I need quick access to the CameraPreview data for my new flutter app.
If I take a picture with controller.takePicture(filePath) it takes a few seconds for the file to save to the disk, so that I can access it.
I don't need a high quality image, so getting the same resolution as the phone screen display resolution would be fine. I have tried this method, but it only captures the overlays and widgets that I drew myself, not the Camera preview data.
Here is a minimum working example of the problem when using this method:
https://www.youtube.com/watch?v=CWBLjCwH5c0
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'dart:async';
import 'package:camera/camera.dart';
import 'dart:ui' as ui;
List<CameraDescription> cameras;
Future<Null> main() async {
debugPaintSizeEnabled = false;
debugPaintLayerBordersEnabled = false;
try {
cameras = await availableCameras();
} on CameraException catch (e) {
logError(e.code, e.description);
}
runApp(new MaterialApp(
home: new MyApp(),
));
}
void logError(String code, String message) =>
print('Error: $code\nError Message: $message');
class MyApp extends StatefulWidget {
#override
_State createState() => new _State();
}
class _State extends State<MyApp> {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
static GlobalKey previewContainer = new GlobalKey();
CameraController controller;
ui.Image image;
Offset blueSquareOffset = new Offset(10.0, 10.0);
#override
void initState() {
super.initState();
controller = new CameraController(cameras[0], ResolutionPreset.low);
controller.initialize().then((_) {
if (!mounted) {
return;
}
setState(() {});
});
}
#override
void dispose() {
controller?.dispose();
super.dispose();
}
void _getScreenShotImage() async {
_capturePng();
image = await _capturePng();
debugPrint("im height: ${image.height}, im width: ${image.width}");
setState(() {});
}
Future<ui.Image> _capturePng() async {
RenderRepaintBoundary boundary =
previewContainer.currentContext.findRenderObject();
ui.Image image = await boundary.toImage();
return image;
}
/// Display the preview from the camera (or a message if the preview is not available).
Widget _cameraPreviewWidget() {
if (controller == null || !controller.value.isInitialized) {
return const Text('Camera is initialising...');
} else {
return Center(
child: new AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: RepaintBoundary(
//key: previewContainer,
child: new GestureDetector(
child: new CameraPreview(controller),
),
)),
);
}
}
void _moveBlueSquare(DragUpdateDetails details) {
setState(() {
_getScreenShotImage();
blueSquareOffset = blueSquareOffset + details.delta;
});
}
Widget _blueSquare() {
return new Positioned(
top: blueSquareOffset.dy,
left: blueSquareOffset.dx,
width: 50.0,
height: 50.0,
child: new GestureDetector(
onPanUpdate: _moveBlueSquare,
child: new Container(
color: Color.fromARGB(255, 10, 10, 255),
)));
}
#override
Widget build(BuildContext context) {
return new Scaffold(
key: _scaffoldKey,
appBar: new AppBar(
title: new Text('Render Boundry Screenshot Error Example'),
),
body: RepaintBoundary(
key: previewContainer,
child: new Container(
padding: new EdgeInsets.all(0.0),
margin: new EdgeInsets.all(0.0),
child: new RepaintBoundary(
//key: previewContainer,
child: new Stack(
fit: StackFit.expand,
overflow: Overflow.clip,
children: <Widget>[
new Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
new Expanded(
child: new Stack(children: <Widget>[
new RepaintBoundary(
child: new Container(child: _cameraPreviewWidget()),
),
_blueSquare(),
])),
new Expanded(
child: new Container(
//color: Color.fromARGB(50, 50, 50, 50),
child: new CustomPaint(
painter: new RectanglePainter(image),
)),
)
],
),
],
)))));
}
}
class RectanglePainter extends CustomPainter {
RectanglePainter(this.image);
ui.Image image;
#override
void paint(Canvas canvas, Size size) {
if (image == null) {
canvas.drawRect(
new Rect.fromLTRB(100.0, 50.0, 300.0, 200.0),
new Paint()
..color = Color.fromARGB(255, 50, 50, 255)
..style = PaintingStyle.stroke
..strokeWidth = 6.0);
} else {
canvas.drawImage(image, new Offset(0.0, 0.0), new Paint());
}
}
#override
bool shouldRepaint(RectanglePainter old) {
return true;
}
}
Any help would be greatly appreciated.
Update: July 2020
At the moment, the best way to get a screenshot of the CameraPreview on Flutter, is to use the native_screenshot package.
You can simply use,
Future<void> getScreenshot() async{
String path = await NativeScreenshot.takeScreenshot();
print(path);
}
to save the screenshot. Please refer to package page for additional permissions and settings. Performance-wise, it seems to be a bit slow (500ms-1s on my 2018 Xiaomi A1). I am currently looking at ways to improve the screen capture speed.
There is a way to take snapshot from layouts. try this:
Android get Image of Main Relativelayout from xml layout?
I hope this helps to you.
New to flutter. Working on a personal project. Stuck with a small issue related to show images. Here is my widget code which I'm using for showing images.
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cached_network_image/cached_network_image.dart';
class UserProfile extends StatefulWidget {
#override
UserProfileState createState() => new UserProfileState();
}
class UserProfileState extends State<UserProfile> {
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
Map userDetails = {};
String profileImgPath;
#override
void initState() {
super.initState();
getUserDetails();
}
Future<Null> getUserDetails() async {
try {
final SharedPreferences prefs = await _prefs;
this.userDetails = json.decode(prefs.getString('user'));
if (prefs.getString('user') != null) {
if (this.userDetails['isLoggedIn']) {
setState(() {
this.profileImgPath = this.userDetails['profileImg'];
print('Shared preference userDetailsss : ${this.userDetails}');
});
}
} else {
print('Shared preference has no data');
}
} catch (e) {
print('Exception caught at getUserDetails method');
print(e.toString());
}
}
#override
Widget build(BuildContext context) {
Widget profileImage = new Container(
margin: const EdgeInsets.only(top: 20.0),
child: new Row(
children: <Widget>[
new Expanded(
child: new Column(
children: <Widget>[
new CircleAvatar(
backgroundImage: (this.profileImgPath == null) ? new AssetImage('images/user-avatar.png') : new CachedNetworkImageProvider(this.profileImgPath),
radius:50.0,
)
],
)
)
],
)
);
return new Scaffold(
appBar: new AppBar(title: new Text("Profile"), backgroundColor: const Color(0xFF009688)),
body: new ListView(
children: <Widget>[
profileImage,
],
),
);
}
}
What I'm trying to do is, show the default user-avatar.png image as long as CachedNetworkImageProvider don't get original image. But, it's bit behaving differently.
Whenever I'm opening the page - I'm getting a blank blue box then suddenly the original image from CachedNetworkImageProvider comes up.
Can't able to understand what's happening.
#Jonah Williams for your reference -
CachedNetworkImage can't be used for backgroundImage property because it does not extends ImageProvider. You can create a custom CircleAvatar like described below to use the CachedNetworkImage package:
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
class CustomCircleAvatar extends StatelessWidget {
final int animationDuration;
final double radius;
final String imagePath;
const CustomCircleAvatar({
Key key,
this.animationDuration,
this.radius,
this.imagePath
}) : super(key: key);
#override
Widget build(BuildContext context) {
return new AnimatedContainer(
duration: new Duration(
milliseconds: animationDuration,
),
constraints: new BoxConstraints(
minHeight: radius,
maxHeight: radius,
minWidth: radius,
maxWidth: radius,
),
child: new ClipOval(
child: new CachedNetworkImage(
errorWidget: (context, url, error) => Icon(Icons.error),
fit: BoxFit.cover,
imageUrl: imagePath,
placeholder: (context, url) => CircularProgressIndicator(),
),
),
);
}
}
And how to use:
body: new Center(
child: new CustomCircleAvatar(
animationDuration: 300,
radius: 100.0,
imagePath: 'https://avatars-01.gitter.im/g/u/mi6friend4all_twitter?s=128',
),
),
Maybe it is not the better way. But, it works!
(I'm assuming that CachedNetworkImageProvider is actually CachedNetworkImage from this package).
This line of code will always display the second image.
(this.profileImgPath == null)
? new AssetImage('images/user-avatar.png')
: new CachedNetworkImageProvider(this.profileImgPath)
Since profileImagePath is not null, the AssetImage is never created. Even if it was, as soon as it's not the cached network image will replace it before it has loaded. Instead, use the placeholder parameter of the network image. This will display your asset image until the network image loads.
new CachedNetworkImage(
placeholder: new AssetImage('images/user-avatar.png'),
imageUrl: profileImgPath,
)