How to control gif animation in Flutter? - flutter

I'm trying to restart an animated gif on Flutter. The gif image loads from network without a problem and animates after loading. I need to restart the animation on tapping a button.
Tried so far:
- setState
- change Key to some other unique key and setState to rebuild.
Solution as #chemamolins 's suggestion:
int _robotReloadCount=0;
....
GestureDetector(
onTap: () {
onTapRobot();
},
child: Center(
child: Container(
margin: EdgeInsets.only(top: 55.0, bottom: 5.0),
height: 150.0,
width: 150.0,
child:
FadeInImage(
key: this._robotImageKey,
placeholder: AssetImage('assets/common/robot_placeholder.png'),
image: NetworkImage(snapshot.data['robot_image_path'] +"robot_level" +snapshot.data['robot_level'].toString() +".gif"+"?"+this._robotReloadCount.toString()))),
),
),
....
onTapRobot() async{
setState(() {
this._robotReloadCount++;
});
}

I have done a lot of tests and it is not easy. The image is cached by the 'ImageProvider' and whatever you change or no matter the times you invoke build() the image is loaded from what is available in the cache.
So, apparently, you only have two options.
Either you rebuild with a new url, for instance by appending #whatever to the image url.
Or you remove the image from the cache as shown in the code below.
In either case you need to fetch again the image from the network.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String url = "https://media.giphy.com/media/hIfDZ869b7EHu/giphy.gif";
void _evictImage() {
final NetworkImage provider = NetworkImage(url);
provider.evict().then<void>((bool success) {
if (success) debugPrint('removed image!');
});
setState(() {});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: Image.network(url),
),
floatingActionButton: new FloatingActionButton(
onPressed: _evictImage,
child: new Icon(Icons.remove),
),
);
}
}

Related

Why is Flutter dialog not rebuilding on change notifier?

Well the issue is kinda simple, but it needs to be done on a specific way. First I have a Class extending "ChangeNotifier" this class will perform some async tasks, so while it is doing so there's a variable that indicates if the class is currently bussy or not, so far it works flawlessly.
Using Riverpod as state managment I instanciate said class and provide it along my widget tree, but there's one Widget that needs to display a dialog and inside this dialog it can execute async tasks from the Class that I've been passing around. It all works except for the fact that I would like to display a CircularProgressIndicator inside this dialog, and it doesn't seems to be reacting propperly to the state changes.
Here's a sample code to recreate the scenario:
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
final dataProvider = ChangeNotifierProvider<Data>((_) => Data());
void main() {
runApp(ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'huh?',
theme: ThemeData(primarySwatch: Colors.blue),
home: FirstPage(),
);
}
}
class FirstPage extends HookWidget {
#override
Widget build(BuildContext context) {
final data = useProvider(dataProvider);
print('DATA STATE [source: FirstPage, data: ${data.loading}]');
return Scaffold(
body: Center(
child: Container(
width: 200,
height: 50,
child: ElevatedButton(
child: Text('show dialog'),
onPressed: () => showDialog(
context: context,
builder: (_) => Alert(data: data),
),
),
),
),
);
}
}
class Alert extends StatelessWidget {
const Alert({required this.data});
final Data data;
Widget build(BuildContext context) {
print('DATA STATE [source: Alert, data: ${data.loading}]');
return AlertDialog(
content: Container(
width: 500,
height: 500,
padding: EdgeInsets.symmetric(horizontal: 100, vertical: 200),
child: ElevatedButton(
child: data.loading ? CircularProgressIndicator(color: Colors.white) : Text('click here'),
onPressed: () async => await data.randomTask(),
),
),
);
}
}
class Data extends ChangeNotifier {
Data({
this.loading = false,
});
bool loading;
Future<void> randomTask() async {
print('Actually waiting 3 seconds..');
_update(loading: true);
await Future.delayed(Duration(seconds: 3));
print('Waiting done.');
_update(loading: false);
}
void _update({bool? loading}) {
this.loading = loading ?? this.loading;
notifyListeners();
}
}
Notice the prints I've placed, because of them if you run the app you'll see outputs on the console like:
DATA STATE [source: FirstPage, data: false]
DATA STATE [source: Alert, data: false]
Actually waiting 3 seconds..
DATA STATE [source: FirstPage, data: true]
Waiting done.
DATA STATE [source: FirstPage, data: false]
Which means that the state is actually changing, and everything is working fine, except for the dialog that seems to be static.
I already tried adding a "loading" bool as part of the "Alert" widget, and letting it manage its own state, and it works, but the code is not as clean as I would like to, because the Class "Data" is supposed to manage this kind of stuff.
Is there anything that can be done?
Thankyou in advance!
Adding StatefulBulider do the trick
class Alert extends StatelessWidget {
const Alert({required this.data});
final Data data;
Widget build(BuildContext context) {
print('DATA STATE [source: Alert, data: ${data.loading}]');
return AlertDialog(
content: StatefulBuilder(builder: (context, setState) {
return Container(
width: 500,
height: 500,
padding: EdgeInsets.symmetric(horizontal: 100, vertical: 200),
child: ElevatedButton(
child: data.loading
? CircularProgressIndicator(color: Colors.white)
: Text('click here'),
onPressed: () async => await data.randomTask(),
),
);
}),
);
}
}

How to transfer ID of image from carousel_pro flutter

How to transfer ID if user onTap image to other page from carousel_pro.
I get Image to carousel_pro from database mysql. I try to make user can click image and move to other page with id of image.
full my code:
void main() => runApp(MaterialApp(home: Demo()));
class Demo extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<Demo> {
String SelectIdCategory;
bool lodaing=true;
List data;
Future GetAllCategory()async{
var response=await http.get("https://****************.php"
, headers: {"Accept": "application/json"}
);
var jsoBody = response.body;
var jsoData =json.decode(jsoBody);
setState(() {
data= jsoData;
lodaing=false;
});
print('show all data $jsoData');
}
#override
void initState() {
// TODO: implement initState
super.initState();
GetAllCategory();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body:lodaing? CircularProgressIndicator() :
GestureDetector(
child: Center(
child: SizedBox(
height: 150.0,
width: 300.0,
child: InkWell(
onTap: () {
},
child: Carousel(
// onImageTap:(index) { print(index.toString()); } ,
boxFit: BoxFit.cover,
autoplay: true,
dotSize: 4.0,
dotSpacing: 15.0,
dotColor: Colors.lightGreenAccent,
indicatorBgPadding: 5.0,
dotBgColor: Colors.purple.withOpacity(0.5),
borderRadius: true,
// onImageChange: (prev, next) {_selectedIndex = next;}, initialIndex: selectedItem,
images:
data .map(
(list) {
return Image.network(list['image']);
},
).toList(),
),
),
),
),
)
);
}
}
class ImageScreen extends StatefulWidget {
final String id;
ImageScreen(this.id);
#override
_MyImageScreen createState() => _MyImageScreen(id);
}
class _MyImageScreen extends State<ImageScreen> {
final String id;
_MyImageScreen(this.id);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ImageScreen'),
),
body: Center());
}
}
Here is the print result:
I/flutter (24335): show all data [{id: 1, name: one, image: https://*****************/image},
{id: 3, name: one, image: https://*****************/image}]
As you can see from print data I have two image and each image has a id different. That what I want to send to other page.
Does anyone know how to do this?
You should define a "skeleton" of your app.
Do you prefer to use global variables to store values at runtime? Do you want to send parameters through pages without let them in app ram?
If you want to send data thor pages your class for the new page should accept parameters.. so you can pass the id.
or you can set a global variable with the currentImageId then in the new page you can access that variable
To accept parameters in the new page you have to declare final variables then in the signature of the class you request them.. something like this
class DetailPage extends StatefulWidget {
final int yourId;
DetailPage(this.yourId);
}

Image not showing with full width in splash screen

I am using following code to display image as splash screen :
class WelcomeWidget extends StatefulWidget {
static const routeName = '/welcome_page';
#override
_WelcomeWidgetState createState() => _WelcomeWidgetState();
}
class _WelcomeWidgetState extends State<WelcomeWidget> {
#override
void initState() {
super.initState();
Timer(
Duration(seconds: 10),
() => Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (_) => LowerStripWidget(),
),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body:
Container(
child:Center(
child: Image.asset('assets/images/Splash-Screen-bg.png'),
),
width: double.infinity,
),
);
}
}
This shows the image but does not stretch image to fill the white space.
How we can do that in flutter?
Here is the current screen:
You can use the fit property of the Image widget to determine how to inscribe the image into the space allocated during layout.
I added an example using your code:
Image.asset(
'assets/images/Splash-Screen-bg.png',
// set the fit property to cover
it: BoxFit.cover, // new line
),

How do I make a specific part of a transparent image clickable?

I have a stack in a Flutter app, that stacks multiple images on top of each other. They are all of the same width and height, and have transparent backgrounds.
Individually, they look like this:
When they overlap, they look like this:
I need to make the visible part of each picture clickable. I do not want any interaction with the transparent part of any image. I've tried using GestureDetector, but since all the images are of the same size, it isn't working too well. How do I achieve this?
Circle the borders of the picture in any vector graphics editor, I used figma.com, it's free.
Save it as svg file, open it and copy path from svg.
Convert svg paths to Flutter Paths, I've used the path_drawing package.
Use custom clipper to clip image by path.
Unfortunately, path_drawing package ignores the beginning of the path. So you need to add it, by adding offset.
Add GestureDetector.
import 'package:flutter/material.dart';
import 'package:path_drawing/path_drawing.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
body: SafeArea(
child: MyHomePage(),
),
),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String clicked = '';
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
_getClippedImage(
clipper: _Clipper(
svgPath: svgCarPath,
offset: Offset(66, 157),
),
image: 'assets/image.png',
onClick: _handleClick('car'),
),
_getClippedImage(
clipper: _Clipper(
svgPath: svgManPath,
offset: Offset(115, 53),
),
image: 'assets/image.png',
onClick: _handleClick('man'),
),
Positioned(
child: Text(
clicked,
style: TextStyle(fontSize: 30),
),
bottom: 0,
),
],
);
}
void Function() _handleClick(String clickedImage) {
return () => setState(() {
clicked = clickedImage;
});
}
Widget _getClippedImage({
_Clipper clipper,
String image,
void Function() onClick,
}) {
return ClipPath(
clipper: clipper,
child: GestureDetector(
onTap: onClick,
child: Image.asset('assets/image.png'),
),
);
}
}
class _Clipper extends CustomClipper<Path> {
_Clipper({this.svgPath, this.offset = Offset.zero});
String svgPath;
Offset offset;
#override
Path getClip(Size size) {
var path = parseSvgPathData(svgPath);
return path.shift(offset);
}
#override
bool shouldReclip(CustomClipper oldClipper) {
return false;
}
}
const svgCarPath =
'M35 13.7742L46.9628 1.52606L58.8398 5.97996V17.1147L111.544 13.7742L117.111 50.8899L109.688 55.715C108.575 61.2823 103.75 72.417 93.3574 72.417C82.965 72.417 80.4751 64.3753 80.4751 59.4266C68.1032 55.5913 53.5355 53.8592 39.5397 57.5708C35.0128 76.8252 14.4397 76.0591 12.0741 55.715H0.939362V26.7647L12.0741 17.1147L35.8281 13.7742Z';
const svgManPath =
'M50.2647 19.9617C50.6461 5.85163 47.5952 0.703364 38.2521 0.703369C32.0776 2.87051 31.0217 6.36354 30.625 14.0016C30.625 14.0016 27.9555 28.1424 30.625 32.8584C33.2945 37.5744 42.1784 35.788 39.3961 40.7456C36.6138 45.7032 27.9555 63.6268 27.9555 63.6268H22.6165C14.7864 70.572 19.1843 79.9011 12.1293 88.7962C3.01255 100.291 -0.77319 103.733 0.879345 106.911L8.12508 109.199L19.1844 96.8046L12.1293 120.258L15.9428 123.499L22.6165 121.402L32.7224 97.9487L39.3961 104.622C36.5995 110.597 32.2267 122.088 37.108 120.258C43.2097 117.97 54.2865 120.258 66.0909 113.394C75.3267 28.4915 49.8834 34.0719 50.2647 19.9617Z';

Flutter, How to update a text in an item in listview after updating the content from it's detail view?

I am following this link,
https://medium.com/…/developing-for-multiple-screen-sizes-a…
to create a master detail ipad application.
I have a scenario, there is a text field and button in detail page. When i change the text field value and press the button, the listview item (in left side) at that specific index also should be updated. can somebody suggest a work around?
You can return the edited object using Navigator.pop(context,object) to the Navigator.push() caller. I wrote an example app for you.
the data class:
class Item {
final String name;
Item(this.name);
}
the home page, where I display the item:
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
Item item = Item('ali2236');
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
child: Center(
child: Column(
children: <Widget>[
Text(item.name),
FlatButton(
child: Text('edit'),
onPressed: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) {
return ItemEditingPage(
item: item,
callbackFunction: (editedItem){
setState(() {
item = editedItem;
});
},
);
}));
},
),
],
),
),
),
);
}
}
and the editing page:
class ItemEditingPage extends StatefulWidget {
final Item item;
final void Function(Item item) callbackFunction;
const ItemEditingPage({Key key, this.item, this.callbackFunction}) : super(key: key);
#override
_ItemEditingPageState createState() => _ItemEditingPageState();
}
class _ItemEditingPageState extends State<ItemEditingPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Center(
child: FlatButton(
child: Text('change name to aligator'),
onPressed: () {
///
/// if the name is [final], you create a new Item and pass it back
///
Item item = Item('aligator');
widget.callbackFunction(item);
///
/// if the name is not final you can just change it on the current object
///
//widget.item.name = 'aligator';
//widget.callbackFunction(widget.item);
},
),
),
),
);
}
}
edit: used a callback function instead of Navigator.pop() to notify the showcase page.