How can I speed up the loading speed of my AssetImage? - flutter

My image size is quite small which is about 50kb but it seems to load longer than I expected it would be, so my app doesn't load up that prettily as I would imagined. Any way I could speed it up?
`
import 'dart:async';
import 'package:egarment2/pages/home_page.dart';
import 'package:egarment2/sign_in.dart';
import 'package:flutter/material.dart';
class SplashScreen extends StatefulWidget {
const SplashScreen({Key? key}) : super(key: key);
#override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
// void initState() {
// super.initState();
// Timer(
// const Duration(seconds: 3),
// () {
// Navigator.pushReplacement(
// context,
// MaterialPageRoute(
// builder: (context) => HomePage(),
// ),
// );
// },
// );
// }
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
// color: const Color(0xFFf65d46),
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/bg.jpg'),
fit: BoxFit.fill,
),
),
width: MediaQuery.of(context).size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'assets/images/logo.png',
width: 75,
height: 75,
),
const SizedBox(
height: 25,
),
const Text(
'E-Garment',
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 30),
),
],
),
),
);
}
}
`
I tried compressing the image size which originally was 200 something kb to 50kb (current size) but it doesn't seem to do anything.

You can use the method precacheImage:
class _SplashScreenState extends State<SplashScreen> {
final _image = AssetImage('assets/images/bg.jpg');
#override
void initState() {
super.initState();
precacheImage(_image, context);
// Add your own initState code here
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
precacheImage(_image, context);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: _image,
fit: BoxFit.fill,
),
),
// ...
),
);
}
}

Related

flutter navigate new screen after animatedcontainer onEnd:

I'am noob of flutter and i want a create some apps. This is my main screen with animated container.
import 'package:flutter/material.dart';
import "./loginmenu.dart";
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: Color.fromRGBO(197, 197, 187, 1),
body: AnimatedContainer(
duration: Duration(milliseconds: 350),
width: double.infinity,
height: double.infinity,
child: Image(image: AssetImage("assets/images/Babapps-logos.jpeg")),
onEnd: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => loginscreen()),
),
),
),
);
}
}
when animation duration finish i want go new screen.
import 'package:flutter/material.dart';
class loginscreen extends StatefulWidget {
const loginscreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Column(
children: <Widget>[
Container(width: double.infinity, margin: EdgeInsets.all(130)),
Container(
width: double.infinity,
margin: EdgeInsets.all(5),
child: Center(
child:
Text("Welcome Diet&Life", style: TextStyle(fontSize: 19)),
),
),
Container(
width: 320,
margin: EdgeInsets.all(5),
child: Center(
child: TextField(
decoration: InputDecoration(
border: OutlineInputBorder(), hintText: "Username"),
),
),
),
Container(
width: 320,
margin: EdgeInsets.all(5),
child: Center(
child: TextField(
decoration: InputDecoration(
border: OutlineInputBorder(), hintText: "Password"),
)),
),
Container(
child: Center(
child: RaisedButton(
child: Text("Login"),
onPressed: null,
),
),
),
Container(
margin: EdgeInsets.all(10),
child: Center(
child: new InkWell(
child: Text("Don't have an account?"), onTap: null),
),
)
],
),
),
);
}
#override
State<StatefulWidget> createState() {
// TODO: implement createState
throw UnimplementedError();
}
}
but when I run this code, the animation does not go to the other screen even though it expires. Am I on the right track or do I have to work with setstate?
There is nothing changing on Container, therefor animation onEnd never gets call.
You need to change something inside the container in order to animate it.
If you just like to navigate after some delay, Just use Future.delayed then navigate.
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return const MaterialApp(home: StartPage());
}
}
class StartPage extends StatefulWidget {
const StartPage({Key? key}) : super(key: key);
#override
State<StartPage> createState() => _StartPageState();
}
class _StartPageState extends State<StartPage> {
Color containerColor = Colors.cyanAccent;
#override
void initState() {
super.initState();
_nextPage();
}
/// just nevigate
_nextPage() async {
Future.delayed(Duration(seconds: 1)).then((value) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AnimE(),
),
);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
containerColor = Colors.blue;
});
},
),
body: AnimatedContainer(
duration: const Duration(seconds: 2),
color: containerColor,
width: double.infinity,
height: double.infinity,
child: const Text("a"),
onEnd: () {
print("Ebnd"); // paste neviagte by removing init
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (context) => loginscreen(),
// ),
// );
}),
);
}
}

AnimationBuilder with FutureBuilder - How to execute animation after future returns and builds?

I am making an api call to get some data and then I want to animate in (from the bottom) the widget that displays it. The code for each piece works separately but I can't seem to figure out how to execute the animation after the FutureBuilder has returned from its builder function? Where or how do I call cardController.forward()?
Widget mapCard(Area area) {
return Align(
alignment: Alignment.bottomCenter,
child: AnimatedBuilder(
builder: (BuildContext context, Widget child) {
return InkWell(
onTap: () {
Navigator.of(context).push(_createHeroRoute(area));
},
child: Container(
margin: cardMargin.value,
width: 300,
height: 150,
child: Hero(
tag: 'location',
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
width: 274,
height: 150,
child: Image.network(area.images[0].url,
fit: BoxFit.cover)),
Align(
alignment: Alignment.centerRight,
child: Container(
height: 150,
//30, 42, 65
// 412A1E
decoration: new BoxDecoration(
color: Color.fromARGB(255, 30, 42, 65),
),
child:
Icon(Icons.arrow_right, color: Colors.white))),
],
),
),
),
);
},
animation: cardController,
),
);
}
In the build stack:
FutureBuilder(
future: fArea,
builder: (context, AsyncSnapshot<Area> snap) {
if (snap.hasData) {
return mapCard(snap.data);
} else {
return Text('here');
}
})
You can copy paste run full code below
You can make MapCard a StatefulWidget and call cardController.forward in initState
#override
void initState() {
super.initState();
cardController =
AnimationController(duration: const Duration(seconds: 10), vsync: this);
animation = Tween<double>(begin: 0, end: -300.0).animate(cardController)
..addStatusListener((state) => print('$state'));
cardController.forward();
}
working demo
full code
import 'package:flutter/material.dart';
import 'dart:math' as math;
class MapCard extends StatefulWidget {
Area area;
MapCard(Area data, {Key key}) : super(key: key);
#override
_MapCardState createState() => _MapCardState();
}
class _MapCardState extends State<MapCard> with TickerProviderStateMixin {
AnimationController cardController;
Animation<double> animation;
#override
void initState() {
super.initState();
cardController =
AnimationController(duration: const Duration(seconds: 10), vsync: this);
animation = Tween<double>(begin: 0, end: -300.0).animate(cardController)
..addStatusListener((state) => print('$state'));
cardController.forward();
}
#override
void dispose() {
cardController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Align(
alignment: AlignmentDirectional.bottomCenter,
child: AnimatedBuilder(
animation: cardController,
child: InkWell(
onTap: () {
//Navigator.of(context).push(_createHeroRoute(area));
},
child: Container(
//margin: cardMargin.value,
width: 300,
height: 150,
child: Hero(
tag: 'location',
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
width: 274,
height: 150,
child: Image.network("https://picsum.photos/250?image=9",
fit: BoxFit.cover)),
Align(
alignment: Alignment.centerRight,
child: Container(
height: 150,
//30, 42, 65
// 412A1E
decoration: new BoxDecoration(
color: Color.fromARGB(255, 30, 42, 65),
),
child: Icon(Icons.arrow_right, color: Colors.white))),
],
),
),
),
),
builder: (BuildContext context, Widget child) {
return Transform.translate(
offset: Offset(0, animation.value),
child: child,
);
},
),
);
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
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 Area {
String data;
Area(this.data);
}
class _MyHomePageState extends State<MyHomePage> {
Future<Area> fArea;
Future<Area> getArea() async {
await Future.delayed(Duration(seconds: 3), () {});
return Future.value(Area("data"));
}
#override
void initState() {
fArea = getArea();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: FutureBuilder(
future: fArea,
builder: (context, AsyncSnapshot<Area> snap) {
switch (snap.connectionState) {
case ConnectionState.none:
return Text('none');
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
case ConnectionState.active:
return Text('');
case ConnectionState.done:
if (snap.hasData) {
return MapCard(snap.data);
} else {
return Center(child: CircularProgressIndicator());
}
}
}));
}
}

Flutter. How can I make container wider than screen?

I'm trying to create a parallax background for page controller. For that purpuse I need to create a background image that is wider than the screen. I've put it inside a container like this:
#override
Widget build(BuildContext context) {
return Material(
child: Stack(
children: [
Container(
width: 4000,
height: 250,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/pizza_bg.png'),
fit: BoxFit.cover,
repeat: ImageRepeat.noRepeat
)
)
),
],
),
);
}
But the problem is that no matter what width I specify, the container (and the image, of course) never get wider than the screen. Is it possible at all?
p.s. I tried to use SizedBox and AspectRatio widgets, and they both give the same result
try this, as an option
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Stack(
children: [
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Container(
width: 4000,
height: 250,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/pizza_bg.png'),
fit: BoxFit.cover,
repeat: ImageRepeat.noRepeat,
),
),
),
),
],
),
),
);
}
}
also you can disable scroll for user and manage scroll position via scroll controller
SingleChildScrollView(
scrollDirection: Axis.horizontal,
physics: const NeverScrollableScrollPhysics(),
controller: controller, // your ScrollController
child: Container(
width: 4000,
height: 250,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('images/pizza_bg.png'),
fit: BoxFit.cover,
repeat: ImageRepeat.noRepeat,
),
),
),
),
For images you can use Transform.scale(), as found in the documentation. Using your example:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Stack(
children: [
Align(
alignment: Alignment.center,
child: Transform.scale(
scale: 10.0,
child: Container(
width: 400,
height: 25,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/pizza_bg.png'),
fit: BoxFit.cover,
repeat: ImageRepeat.noRepeat,
),
),
),
),
),
],
),
),
);
}
}
If you want to animate the scale, you can use ScaleTransition(), explained in this page of the docs. For example:
/// Flutter code sample for ScaleTransition
// The following code implements the [ScaleTransition] as seen in the video
// above:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
/// This is the main application widget.
class MyApp extends StatelessWidget {
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
/// This is the stateful widget that the main application instantiates.
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key key}) : super(key: key);
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
/// This is the private State class that goes with MyStatefulWidget.
/// AnimationControllers can be created with `vsync: this` because of TickerProviderStateMixin.
class _MyStatefulWidgetState extends State<MyStatefulWidget>
with TickerProviderStateMixin {
AnimationController _controller;
Animation<double> _animation;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..repeat(reverse: true);
_animation = CurvedAnimation(
parent: _controller,
curve: Curves.fastOutSlowIn,
);
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ScaleTransition(
scale: _animation,
child: const Padding(
padding: EdgeInsets.all(8.0),
child: FlutterLogo(size: 150.0),
),
),
),
);
}
}
NOTE: To avoid quality loss in the image, use an image of the size after scaling or a vector graphic as a source.

Flutter Bloc State Success State Not woking

I want Popup dialog or Card when state.success is NotEmpty
When success state is empty it return Text as i set in code
I don't know, Widget error or Other
I need help
import 'package:bloc_salesearch/src/screen/page_settings.dart';
import 'package:bloc_salesearch/src/search_bloc/bloc.dart';
import 'package:bloc_salesearch/src/util/utils.dart';
import 'package:bloc_salesearch/src/widget/utils_widget.dart';
import 'package:flutter/material.dart';
import 'package:bloc_salesearch/src/widget/widgets.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../src.dart';
class SearchPage extends StatefulWidget {
#override
_SearchPageState createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
Screen size;
#override
void dispose() {
// TODO: implement dispose
super.dispose();
}
#override
Widget build(BuildContext context) {
size = Screen(MediaQuery.of(context).size);
return Scaffold(
backgroundColor: backgroundColor,
body: AnnotatedRegion(
value: SystemUiOverlayStyle(
statusBarColor: backgroundColor,
statusBarBrightness: Brightness.dark,
statusBarIconBrightness: Brightness.dark,
systemNavigationBarColor: backgroundColor,
systemNavigationBarIconBrightness: Brightness.dark,
),
child: Container(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
upperPart(),
],
),
),
),
),
);
}
Widget upperPart() {
return Stack(
children: <Widget>[
ClipPath(
clipper: UpperClipper(),
child: Container(
height: size.getWidthPx(240),
decoration: BoxDecoration(
gradient:
LinearGradient(colors: [colorCurve, colorCurveSecondary]),
),
),
),
Column(
children: <Widget>[
Container(
padding: EdgeInsets.only(top: size.getWidthPx(36)),
child: Column(
children: <Widget>[
titleWidget(),
SizedBox(
height: size.getWidthPx(10),
),
upperBoxCard(),
SizedBox(
height: size.getWidthPx(10),
),
SearchResult(),
],
),
),
],
),
],
);
}
Text titleWidget() {
return Text("Search Lottery",
style: TextStyle(
// fontFamily: 'Exo2',
fontSize: 24.0,
fontWeight: FontWeight.w900,
color: Colors.white));
}
Card upperBoxCard() {
return Card(
elevation: 4.0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
margin: EdgeInsets.symmetric(
horizontal: size.getWidthPx(20), vertical: size.getWidthPx(16)),
borderOnForeground: true,
child: Container(
height: size.getWidthPx(150),
child: Column(
children: <Widget>[
SearchWidget(),
leftAlignText(
text: "Your Number foo win :",
leftPadding: size.getWidthPx(16),
textColor: textPrimaryColor,
fontSize: 16.0),
rightAlignText(
text: '150000',
rightPadding: size.getWidthPx(16),
textColor: textPrimaryColor,
fontSize: 16.0)
],
),
));
}
//
}
class SearchResult extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<SearchBloc, SearchState>(
builder: (context, state) {
if (state is SearchStateLoading) {
return Container(
height: 40,
child: Center(
child: CircularProgressIndicator(),
),
);
}
if (state is SearchStateError) {
return Text(state.error);
}
if (state is SearchStateSuccess) {
print(state);
return state.items.isEmpty
? Text('No Found!Good Luck Next Month!')
: Navigator.pushReplacement(context, MaterialPageRoute(builder: (context)=> SettingPage()));
}
return Text('Please Type your Lottery Number To begin');
},
);
}
}
class _SearchResult extends StatelessWidget {
final List<Sale> items;
const _SearchResult({Key key, this.items}) : super(key: key);
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (BuildContext context, int index) {
return _SearchView(item: items[index]);
},
);
}
}
class _SearchView extends StatelessWidget {
final Sale item;
const _SearchView({Key key, this.item}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(child: Text(''
'You Win'),);
}
}
class SearchWidget extends StatefulWidget {
#override
_SearchWidgetState createState() => _SearchWidgetState();
}
class _SearchWidgetState extends State<SearchWidget> {
final texEdc = TextEditingController();
SearchBloc _searchBloc;
#override
void initState() {
// TODO: implement initState
super.initState();
_searchBloc = BlocProvider.of<SearchBloc>(context);
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
texEdc.dispose();
}
#override
Widget build(BuildContext context) {
return TextField(
controller: texEdc,
autocorrect: false,
onChanged: (text) {
_searchBloc.dispatch(
TextChanged(text: text),
);
},
decoration: InputDecoration(
prefixIcon: Icon(Icons.search),
suffixIcon: GestureDetector(
child: Icon(Icons.clear),
onTap: _onClearTapped,
),
border: InputBorder.none,
hintText: 'Enter your Lottery number',
),
);
}
void _onClearTapped() {
texEdc.text = '';
_searchBloc.dispatch(TextChanged(text: ''));
}
}
You can listen to Bloc state with the BlocListener and execute actions depending on different states.
You should wrap your widget or bloc builder into a BlocListener as this:
BlocListener(
bloc: _searchBloc,
listener: (BuildContext context, SearchState state) {
if(state is SearchStateSuccess){
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context)=> SettingPage()));
}
},
child: your_blocBuilder
)

How to update Draggable child when entering DragTarget in Flutter?

I have a number of Draggables and DragTargets. On the Draggables I have specified child and feedback, however I also want it to change it's look when the Draggable enters the DragTarget. I can't see any way to do this.
Whenever I drag the Draggable around, I change it's color to be red, however as soon as it enters the DragTarget I want to update the Draggable color to green.
I am aware of DragTarget.OnWillAccept, this method is called whenever the Draggable enters the DragTarget, but I only have the data. I tried updating the data with new color and then calling setState, but that seemed not to work.
Any suggestions on how to get this behaviour?
I want something like the following callback Draggable.onEnteringDragTarget and Draggable.onLeavingDragTarget.
The only way I can think of is using a streambuilder and a stream that will carry the information of whether the draggable is on a drag target. This code provides a basic solution.
class _MyHomePageState extends State<MyHomePage> {
BehaviorSubject<bool> willAcceptStream;
#override
void initState() {
willAcceptStream = new BehaviorSubject<bool>();
willAcceptStream.add(false);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
children: <Widget>[
Container(
height: 400,
width: double.infinity,
child: Container(
width: 100,
height: 100,
child: Center(
child: Draggable(
feedback: StreamBuilder(
initialData: false,
stream: willAcceptStream,
builder: (context, snapshot) {
return Container(
height: 100,
width: 100,
color: snapshot.data ? Colors.green : Colors.red,
);
},
),
childWhenDragging: Container(),
child: Container(
height: 100,
width: 100,
color: this.willAcceptStream.value ?? false
? Colors.green
: Colors.blue,
),
onDraggableCanceled: (v, f) => setState(
() {
this.willAcceptStream.add(false);
},
),
),
),
),
),
DragTarget(
builder: (context, list, list2) {
return Container(
height: 50,
width: double.infinity,
color: Colors.blueGrey,
child: Center(child: Text('TARGET ZONE'),),
);
},
onWillAccept: (item) {
debugPrint('will accept');
this.willAcceptStream.add(true);
return true;
},
onLeave: (item) {
debugPrint('left the target');
this.willAcceptStream.add(false);
},
),
],
),
);
}
}
Edit: The first example doesn't handle multiple drags at the same time this on does and it's a little cleaner.
import 'package:rxdart/rxdart.dart';
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: 'Draggable Test',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
MyDraggableController<String> draggableController;
#override
void initState() {
this.draggableController = new MyDraggableController<String>();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Draggable Test'),
),
body: Column(
children: <Widget>[
Container(
height: 400,
width: double.infinity,
child: Container(
width: 100,
height: 100,
child: Center(
child: Stack(
children: <Widget>[
Positioned(
left: 30,
top: 30,
child: MyDraggable<String>(draggableController, 'Test1'),
),
Positioned(
left: 230,
top: 230,
child: MyDraggable<String>(draggableController, 'Test2'),
)
],
),
),
),
),
DragTarget<String>(
builder: (context, list, list2) {
return Container(
height: 50,
width: double.infinity,
color: Colors.blueGrey,
child: Center(
child: Text('TARGET ZONE'),
),
);
},
onWillAccept: (item) {
debugPrint('draggable is on the target');
this.draggableController.onTarget(true, item);
return true;
},
onLeave: (item) {
debugPrint('draggable has left the target');
this.draggableController.onTarget(false, item);
},
),
],
),
);
}
}
class MyDraggable<T> extends StatefulWidget {
final MyDraggableController<T> controller;
final T data;
MyDraggable(this.controller, this.data);
#override
_MyDraggableState createState() =>
_MyDraggableState<T>(this.controller, this.data);
}
class _MyDraggableState<T> extends State<MyDraggable> {
BehaviorSubject<DraggableInfo<T>> willAcceptStream;
MyDraggableController<T> controller;
T data;
_MyDraggableState(this.controller, this.data);
#override
void initState() {
willAcceptStream = this.controller._isOnTarget;
willAcceptStream.add(new DraggableInfo<T>(false, this.data));
super.initState();
}
#override
Widget build(BuildContext context) {
return Draggable<T>(
data: this.data,
feedback: StreamBuilder<DraggableInfo<T>>(
initialData: DraggableInfo<T>(false, this.data),
stream: willAcceptStream,
builder: (context, snapshot) {
return Container(
height: 100,
width: 100,
color: snapshot.data.isOnTarget && snapshot.data.data == this.data ? Colors.green : Colors.red,
);
},
),
childWhenDragging: Container(),
child: Container(
height: 100,
width: 100,
color: (this.willAcceptStream.value.isOnTarget ?? this.willAcceptStream.value.data == this.data)
? Colors.green
: Colors.blue,
),
onDraggableCanceled: (v, f) => setState(
() {
this.willAcceptStream.add(DraggableInfo(false, null));
},
),
);
}
}
class DraggableInfo<T> {
bool isOnTarget;
T data;
DraggableInfo(this.isOnTarget, this.data);
}
class MyDraggableController<T> {
BehaviorSubject<DraggableInfo<T>> _isOnTarget;
MyDraggableController() {
this._isOnTarget = new BehaviorSubject<DraggableInfo<T>>();
}
void onTarget(bool onTarget, T data) {
_isOnTarget.add(new DraggableInfo(onTarget, data));
}
}
Edit 2: Solution without using streams and streambuilder. The important part is the feedback widget is stateful.
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: 'Draggable Test',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
MyDraggableController<String> draggableController;
#override
void initState() {
this.draggableController = new MyDraggableController<String>();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Draggable Test'),
),
body: Column(
children: <Widget>[
Container(
height: 400,
width: double.infinity,
child: Container(
width: 100,
height: 100,
child: Center(
child: Stack(
children: <Widget>[
Positioned(
left: 30,
top: 30,
child: MyDraggable<String>(
draggableController,
'Test1',
),
),
Positioned(
left: 230,
top: 230,
child: MyDraggable<String>(
draggableController,
'Test2',
),
),
],
),
),
),
),
DragTarget<String>(
builder: (context, list, list2) {
return Container(
height: 100,
width: double.infinity,
color: Colors.blueGrey,
child: Center(
child: Text('TARGET ZONE'),
),
);
},
onWillAccept: (item) {
debugPrint('draggable is on the target $item');
this.draggableController.onTarget(true, item);
return true;
},
onLeave: (item) {
debugPrint('draggable has left the target $item');
this.draggableController.onTarget(false, item);
},
),
],
),
);
}
}
class MyDraggable<T> extends StatefulWidget {
final MyDraggableController<T> controller;
final T data;
MyDraggable(this.controller, this.data, {Key key}) : super(key: key);
#override
_MyDraggableState createState() =>
_MyDraggableState<T>(this.controller, this.data);
}
class _MyDraggableState<T> extends State<MyDraggable> {
MyDraggableController<T> controller;
T data;
bool isOnTarget;
_MyDraggableState(this.controller, this.data);
FeedbackController feedbackController;
#override
void initState() {
feedbackController = new FeedbackController();
this.controller.subscribeToOnTargetCallback(onTargetCallbackHandler);
super.initState();
}
void onTargetCallbackHandler(bool t, T data) {
this.isOnTarget = t && data == this.data;
this.feedbackController.updateFeedback(this.isOnTarget);
}
#override
void dispose() {
this.controller.unSubscribeFromOnTargetCallback(onTargetCallbackHandler);
super.dispose();
}
#override
Widget build(BuildContext context) {
return Draggable<T>(
data: this.data,
feedback: FeedbackWidget(feedbackController),
childWhenDragging: Container(
height: 100,
width: 100,
color: Colors.blue[50],
),
child: Container(
height: 100,
width: 100,
color: (this.isOnTarget ?? false) ? Colors.green : Colors.blue,
),
onDraggableCanceled: (v, f) => setState(
() {
this.isOnTarget = false;
this.feedbackController.updateFeedback(this.isOnTarget);
},
),
);
}
}
class FeedbackController {
Function(bool) feedbackNeedsUpdateCallback;
void updateFeedback(bool isOnTarget) {
if (feedbackNeedsUpdateCallback != null) {
feedbackNeedsUpdateCallback(isOnTarget);
}
}
}
class FeedbackWidget extends StatefulWidget {
final FeedbackController controller;
FeedbackWidget(this.controller);
#override
_FeedbackWidgetState createState() => _FeedbackWidgetState();
}
class _FeedbackWidgetState extends State<FeedbackWidget> {
bool isOnTarget;
#override
void initState() {
this.isOnTarget = false;
this.widget.controller.feedbackNeedsUpdateCallback = feedbackNeedsUpdateCallbackHandler;
super.initState();
}
void feedbackNeedsUpdateCallbackHandler(bool t) {
setState(() {
this.isOnTarget = t;
});
}
#override
Widget build(BuildContext context) {
return Container(
height: 100,
width: 100,
color: this.isOnTarget ?? false ? Colors.green : Colors.red,
);
}
#override
void dispose() {
this.widget.controller.feedbackNeedsUpdateCallback = null;
super.dispose();
}
}
class DraggableInfo<T> {
bool isOnTarget;
T data;
DraggableInfo(this.isOnTarget, this.data);
}
class MyDraggableController<T> {
List<Function(bool, T)> _targetUpdateCallbacks = new List<Function(bool, T)>();
MyDraggableController();
void onTarget(bool onTarget, T data) {
if (_targetUpdateCallbacks != null) {
_targetUpdateCallbacks.forEach((f) => f(onTarget, data));
}
}
void subscribeToOnTargetCallback(Function(bool, T) f) {
_targetUpdateCallbacks.add(f);
}
void unSubscribeFromOnTargetCallback(Function(bool, T) f) {
_targetUpdateCallbacks.remove(f);
}
}