In my app/game I have this fan, it turnes around when you click on it, and then keeps going untill you click again. It works fine, with curves.easein etc.
But when ever you try to run it a second time (or any number above 1 for that matter) it skipes the initial start with a curve, and first begins when I run repeat after 2 seconds (so it starts without the desired curve)
Button code:
GestureDetector(
onTap: () {
Duration startStopTime = Duration(seconds: 3);
if(_animating) {
_rotationAnimationController.animateBack(1, duration: startStopTime, curve: Curves.easeOut);
setState(() {});
} else {
_rotationAnimationController.animateTo(1, duration: startStopTime, curve: Curves.easeInCubic);
Future.delayed(startStopTime, () {
_rotationAnimationController.repeat();
});
}
setState(() {
_animating = !_animating;
});
},
child: Padding(
padding: const EdgeInsets.only(top: 70),
child: Container(
color: Colors.blue[300],
width: MediaQuery.of(context).size.width/6*5,
height: MediaQuery.of(context).size.width/6*5,
),
),
),
The fan widget:
IgnorePointer(
child: Container(
child: Transform.rotate(
angle: _animation.value,
child: Image.asset('assets/FanArms.png')
),
),
),
The animation:
var _animating = false;
AnimationController _rotationAnimationController;
Animation<double> _animation;
#override
void initState() {
super.initState();
_rotationAnimationController = AnimationController(
duration: Duration(milliseconds: 2000),
vsync: this,
);
_animation =
Tween<double>(begin: 0, end: 4 * pi ).animate(_rotationAnimationController)
..addListener(() {
setState(() {});
});
}
#override
void dispose() {
_rotationAnimationController.dispose();
super.dispose();
}
Here is a video of my the fan in my app
https://imgur.com/a/wtxFNvL
Right when the fan stoppes, I click on it again, and it takes like 3 seconds before it starts (and that's without the smooth curve)
And yea, my app is wierd, I know :)
Thaks allready :)
Related
I'm working on an audiobook listening app. There I have my main page, which consists of 3 page widgets linked through a Page View that I can swipe to navigate through these pages. On each page I have laid exactly one banner ad from google_mobile_ads package. The problem is that if I add those 3 banners, my app starts to lag a lot, I did check the release version of my app, it is still laggy. In order to understand better the situation I will explain how my ad loading works.
I have a file, where I store the classes regarding those banner ads:
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:flutter/material.dart';
late AnchoredAdaptiveBannerAdSize adaptiveBannerSize;
class AdWidgets {
static Container? libraryPag;
static Container? homePag;
static Container? settingsPag;
}
void _setLoaded() {
AdState.loaded = true;
}
class AdState {
static bool loaded = false;
late Future<InitializationStatus> initialization;
AdState(this.initialization);
String get bannerAdUnitId => 'ca-app-pub-3940256099942544/6300978111';
BannerAdListener get adListener => _adListener;
BannerAdListener _adListener = BannerAdListener(
onAdLoaded: (ad) {
_setLoaded();
}
);
}
There is the AdState class which I use to load my banner ads by providing them a BannerAdListener and an AdUnitId, the bool loaded is used in the widgets that display those banners in order for them to know if the ads where loaded and can be displayed properly.
Next I have a Stateful Widget Content, that I use to navigate through my app routes:
class _ContentState extends State<Content> with SingleTickerProviderStateMixin {
#override
void didChangeDependencies() {
// TODO: implement didChangeDependencies
super.didChangeDependencies();
final adState = Provider.of<AdState>(context);
adState.initialization.then((status) {
AdWidgets.libraryPag = Container(
width: MediaQuery.of(context).size.width,
height: adaptiveBannerSize.height.toDouble(),
child: AdWidget(
ad: BannerAd(
adUnitId: adState.bannerAdUnitId,
size: adaptiveBannerSize,
request: AdRequest(),
listener: adState.adListener
)
..load(),
)
);
AdWidgets.homePag = Container(
width: 320,
height: 100,
child: AdWidget(ad: BannerAd(
adUnitId: adState.bannerAdUnitId,
size: AdSize.largeBanner,
request: AdRequest(),
listener: adState.adListener
)
..load()),
);
AdWidgets.settingsPag = Container(
width: 320,
height: 250,
child: AdWidget(ad: BannerAd(
adUnitId: adState.bannerAdUnitId,
size: AdSize.mediumRectangle,
request: AdRequest(),
listener: adState.adListener
)
..load()
)
);
});
}
#override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: Settings.theme,
builder: (context, value, _) {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Settings.theme.value == 'Dark' ? Brightness.light : Brightness.dark,
));
return WillPopScope(
onWillPop: () async {
if (bottomBarIndex.value != 1) {
moveHome();
}
return false;
},
child: Scaffold(
resizeToAvoidBottomInset: false,
body: Navigator(
key: Content.contentNavigatorKey,
initialRoute: '/',
onGenerateRoute: (RouteSettings settings) {
Widget builder;
switch (settings.name) {
case '/':
builder = MainPage();
break;
case '/bookPage':
Book book = settings.arguments as Book;
builder = BookPage(book: book);
bottomBarIndex.value = -1;
break;
case '/addBook':
builder = AddBookPage();
bottomBarIndex.value = -1;
break;
case '/changeCover':
Book book = settings.arguments as Book;
builder = ChangeCoverPage(book: book);
bottomBarIndex.value = -1;
break;
default:
throw Exception('Invalid route: ${settings.name}');
}
return PageRouteBuilder(
transitionDuration: Duration(milliseconds: 300),
transitionsBuilder:
(context, animation, secAnimation, child) {
animation = CurvedAnimation(
parent: animation, curve: Curves.easeIn);
return FadeTransition(
opacity: animation,
child: child,
);
},
pageBuilder: (context, animation, secAnimation) {
return builder;
});
},
onPopPage: (route, result) {
return route.didPop(result);
},
),
));
},
);
}
}
In the didChangeDependencies method I initialize the adState (the way it is done in the Monetizing apps with Flutter official tutorial) and then assign three banner widgets to the each of the static Container from the AdWidgets custom class. Then I just display those Containers as I please, for example:
class LibraryPage extends StatefulWidget{
const LibraryPage({Key? key}) : super(key: key);
#override
_LibraryPageState createState() => _LibraryPageState();
}
class _LibraryPageState extends State<LibraryPage> with TickerProviderStateMixin {
late TabController _tabController;
late AppBar appBar;
#override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
appBar = AppBar(
brightness: Settings.theme.value == 'Dark' ? Brightness.dark : Brightness.light,
shadowColor: Settings.theme.value == 'Dark' ? Color.fromRGBO(0, 0, 0, 0.1) : Color.fromRGBO(0, 0, 0, 0.5),
backgroundColor: Settings.colors[2],
title: Text('Library'),
bottom: TabBar(
controller: _tabController,
labelPadding: EdgeInsets.zero,
labelStyle: TextStyle(
fontFamily: 'Montserrat',
fontWeight: FontWeight.w500
),
labelColor: Settings.colors[3],
indicator: UnderlineTabIndicator(
borderSide: BorderSide(color: Settings.colors[3], width: 2)
),
tabs: [
Tab(
text: 'Reading',
),
Tab(
text: 'New',
),
Tab(
text: 'Read',
)
],
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Settings.colors[1],
resizeToAvoidBottomInset: false,
appBar: appBar,
body: Column(
children: [
if (AdWidgets.libraryPag != null && AdState.loaded)
AdWidgets.libraryPag!
else
Container(
width: MediaQuery.of(context).size.width,
height: adaptiveBannerSize.height.toDouble(),
color: Settings.colors[0],
child: Center(
child: Text(
'Ad not loaded',
style: TextStyle(
color: Settings.colors[4],
fontFamily: 'Open Sans'),
)),
),
ScrollConfiguration(
behavior: MyBehavior(),
child: SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height - appBar.preferredSize.height - appBar.bottom!.preferredSize.height - adaptiveBannerSize.height,
child: TabBarView(
controller: _tabController,
children: [
Category(category: 'reading'),
Category(category: 'new'),
Category(category: 'read')
],
),
),
),
],
),
);
}
}
As you can see there, I check if the AdWidgets.libraryPag is not null and if the ad was loaded and then if true - I display my ad.
The major issue is that - when I navigate from one page to another or scroll down it is very laggy even in the release build, also my bottomNavBar starts flickering a lot. Also when I do any kind of action in my app I get a stack of messages in of the console log saying D/AudioManager(30711): getStreamVolume isRestricted mode = 0. My guess is that the ads are reloading too often causing some performance issues.
Here is a small portion of my console log:
W/ContentCatcher(30711): Failed to notify a WebView
W/Choreographer(30711): Already have a pending vsync event. There should only be one at a time.
W/Choreographer(30711): OPTS_INPUT: First frame was drawed before optimized, so skip!
W/ContentCatcher(30711): Failed to notify a WebView
W/ContentCatcher(30711): Failed to notify a WebView
W/Choreographer(30711): Already have a pending vsync event. There should only be one at a time.
W/Choreographer(30711): Already have a pending vsync event. There should only be one at a time.
W/ContentCatcher(30711): Failed to notify a WebView
W/ContentCatcher(30711): Failed to notify a WebView
W/Choreographer(30711): OPTS_INPUT: First frame was drawed before optimized, so skip!
W/Choreographer(30711): OPTS_INPUT: First frame was drawed before optimized, so skip!
W/ContentCatcher(30711): Failed to notify a WebView
I/Ads (30711): Use RequestConfiguration.Builder().setTestDeviceIds(Arrays.asList("B0B49AF69923DB48675A21F6F88D2525")) to get test ads on this device.
D/AudioManager(30711): getStreamVolume isRestricted mode = 0
D/AudioManager(30711): getStreamVolume isRestricted mode = 0
How can I fix this performance issue?
Refer to this answer on issue,
Prior to Android 10 AndroidView Should have better performance. if this is the case, maybe we can check the device OS and use AndroidView Instead of PlatformViewLink.
atrope make fork from repo google_mobile_ads and do this trick for better performance for device os less than Android 10
Use this repo instead of official google_ads in your pubspec.yaml
google_mobile_ads:
git:
url: https://github.com/SuaMusica/googleads-mobile-flutter.git
path: packages/google_mobile_ads
ref: feature/suamusica
Special thanks for atrope
The problem is not in your implementation or code. It is because the package is using androidView which is very expensive when handled by devices with android below 10. You can force flutter to switch to platformviewlink like this and it will fix the issue for the moment.
if (Platform.isAndroid) {
androidInfo = await DeviceInfoPlugin().androidInfo;
final isAndroidOld = (androidInfo.version.sdkInt ?? 0) < 29; //Android 10
useHybridComposition = remoteConfig.getBool(
isAndroidOld
? RemoteConfigKey.useHybridCompositionOlderOS
: RemoteConfigKey.useHybridCompositionNewerOS,
);
if (isAndroidOld && useHybridComposition) {
await PlatformViewsService.synchronizeToNativeViewHierarchy(false);
}
}
I'm creating a timer, and it has a play and pause button that changes the text inside the timer, I press "Play" and the timer starts counting, but when I pause, the text on the button and the text inside the timer doesn't change, does anyone know why?
AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Padding(
padding: const EdgeInsets.fromLTRB(0, 10, 0, 0),
child: FloatingActionButton.extended(
backgroundColor: Colors.grey[50],
onPressed: () {
if (controller.isAnimating){
controller.stop();
}
else {
controller.reverse(
from: controller.value == 0.0
? 1.0
: controller.value);
}
},
icon: Icon(controller.isAnimating
? Icons.pause
: Icons.play_arrow),
label: Text(
controller.isAnimating ? "Pause" : "Play")),
);
}),
Normal
Couting
Paused
Try call controller.stop(); into a setState
like this:
setState(() {
controller.stop();
});
I want to add a button at the end of my GridView. I viewed another similar problem but the code given in the answer does not scroll. Here is the link to that answer.
My design has a similar. Here is a rough sketch.
Also just for clarification, I want the button to appear once the user has scrolled to the end of the grid view.
I am still new to flutter so your help would be much appreciated :)
The thing which you need is ScrollController class.
WHY SCROLLCONTROLLER?
It keeps track of what are we doing with scrolling, that is, scrolling, reached bottom, or top
HOW CAN WE USE IT?
You need to use it inside GridView, to get your things up and running
// Simply initialise your controller in your project
ScrollController _controller = new ScrollController();
// add listener to the controller to find out the scrolling event
_controller.addListener((){});
// pass this into your GridView.
// We we will add it to GridView. ScrollController is for every scrolling widget
// in Flutter
GridView.builder(
controller: _controller,
)
DISCLAIMER: Please do not look at the UI aspect, since we care about the scrolling event tracking and show/hide our button
I have referred to your given link only for creating the UI => Your Provided Link
Also, I have added scrolling event to identify whether we're scrolling or not, but it is commented
The project currently show the button when we reach the bottom, and hide it when we are the top
CODE
class _MyHomePageState extends State<MyHomePage> {
List<String> homeList = [];
//to check whether we have reached bottom
bool isBottom = false;
ScrollController _controller = new ScrollController();
#override
void initState() {
super.initState();
homeList = List.generate(10, (ind) => 'Item $ind').toList();
// adding controller to check whether the page is
// at the bottom
_controller.addListener((){
// reached bottom
if (_controller.offset >= _controller.position.maxScrollExtent &&
!_controller.position.outOfRange) {
setState(() => isBottom = true);
}
// IS SCROLLING
// if (_controller.offset >= _controller.position.minScrollExtent &&
// _controller.offset < _controller.position.maxScrollExtent && !_controller.position.outOfRange) {
// setState(() {
// isBottom = false;
// });
// }
// REACHED TOP
if (_controller.offset <= _controller.position.minScrollExtent &&
!_controller.position.outOfRange) {
setState(() => isBottom = false);
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
height: MediaQuery.of(context).size.height,
child: Stack(
children: [
GridView.builder(
shrinkWrap: true,
controller: _controller,
itemCount: homeList.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, crossAxisSpacing: 20),
itemBuilder: (ctx, i) {
return GestureDetector(
onTap: () => print(i),
child: Container(
margin: EdgeInsets.only(bottom: 20.0),
decoration: BoxDecoration(
color: Colors.indigo[300],
borderRadius: BorderRadius.circular(15.0)
)
)
);
}
),
// if we are bottom we show the button
// else empty container, which is nothing but
// hiding technique in Flutter
isBottom ? Positioned(
bottom: 20,
left: 18,
right: 18,
child: Container(
alignment: Alignment.center,
height: 50,
decoration: BoxDecoration(
color: Colors.orangeAccent,
borderRadius: BorderRadius.circular(15),
),
child: Text('Your widget at the end')
)
) : Container()
]
)
)
);
}
}
RESULT
Whenever I scroll in my listview, I get this error spammed in console:
ScrollController not attached to any scroll views.
'package:flutter/src/widgets/scroll_controller.dart':
Failed assertion: line 110 pos 12: '_positions.isNotEmpty'
I've been trying to fix this all day and I'd like to have someone else take a look at it.
There are more problems with this code, but right now I'm mainly interested in fixing this error.
I've tried to use Listview.builder, checking for hController.hasClients and many more small things. They didn't seem to change anything
class MyHome extends StatefulWidget {
#override
MyHomeState createState() => new MyHomeState();
}
class MyHomeState extends State<MyHome> with SingleTickerProviderStateMixin {
ScrollController hController;
ScrollController tController;
ScrollController fController;
ScrollController bController;
#override
void initState() {
super.initState();
hController = new ScrollController()..addListener(_scrollListener);
tController = new ScrollController()..addListener(_scrollListener);
fController = new ScrollController()..addListener(_scrollListener);
bController = new ScrollController()..addListener(_scrollListener);
}
#override
void dispose() {
super.dispose();
hController.removeListener(_scrollListener);
tController.removeListener(_scrollListener);
fController.removeListener(_scrollListener);
bController.removeListener(_scrollListener);
}
#override
Widget build(BuildContext context) {
return new DefaultTabController(
length: 4,
child: new Scaffold(
//Removed AppBar for readability
body: new TabBarView(
children: [
new Container(// hot
child: ListView(
controller: hController,
children: <Widget>[
Utils.show("hot")
],
),
),
new Container( //Trending
child: ListView(
controller: tController,
children: <Widget>[
Utils.show("trending")
],
),
),
new Container( //Fresh
child: ListView(
controller: fController,
children: <Widget>[
Utils.show("fresh")
],
),
),
new Container( //Best
child: ListView(
controller: bController,
children: <Widget>[
Utils.show("best")
],
),
),
],
),
));
}
void _scrollListener() {
if (hController.position.extentAfter == 0.0) {
setState(() {
Utils.show("hot");
});
}else if (tController.position.extentAfter == 0.0) {
setState(() {
Utils.show("trending");
});
} else if (fController.position.extentAfter == 0.0) {
setState(() {
Utils.show("fresh");
});
} else if (bController.position.extentAfter == 0.0) {
setState(() {
Utils.show("best");
});
}
}
}
Edit: For some clarity, the first time I posted this code, I used tController twice. This was ofcourse a mistake, but did not solve the error. The error happens when scrolling in every one of the four listviews.
To avoid such type of errors use the getter
ScrollController.hasClient
If this is false, then members that interact with the
[ScrollPosition],such as [position], [offset], [animateTo], and [jumpTo],
must not be called.
for example:
if (_controller.hasClients) {
_controller.animateTo(
...
}
The problem is inside _scrollListener.
When you are checking controllers in if-else there is only one view at the scene. Means only one listview is rendered & only one scrollcontroller is completely setup. But in your code they are checking all scrollcontroller's positions in single function. Thats why you are getting this error. First check if controller have the positions, which it will only have if the controller is attached & view is rendered correctly. After that check for extentAfter value.
Exa -
if (hController.positions.length > 0 && tController.position.extentAfter == 0.0) {
}
else if (tController.positions.length > 0 && tController.position.extentAfter == 0.0) {
}
& so on
check controller does not have client ant then delay jump:
if (!_scrollController.hasClients) {
_scrollController.animateTo(_scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut);
}
If you paste your code correctly - it seems there can be mistake:
new Container(// hot
child: ListView(
controller: tController,
children: <Widget>[
Utils.show("hot")
],
),
),
new Container( //Trending
child: ListView(
controller: tController,
children: <Widget>[
Utils.show("trending")
],
),
),
You have used tController two times and haven't used hController
Update your flutter sdk it will solve this problem
That is work for me
run this on your cmd - flutter update
I am creating a file manager in flutter for learning purpose. I have just started learning flutter so i don't have much knowledge about it.
My issue is that I want to display thumbnails for images in a folder, for that i am calling a function written in android native code using a FutureBuilder in ListView. It is working fine but whenever I open a directory with lots of images, it freezes the UI for some time & I am unable to scroll it for that time even though thumbnails are called asynchronously.
Me code of Listview:-
#override
Widget build(BuildContext context) {
return new ListView.builder(
itemBuilder: (itemContext, i) {
if (_filesList != null && i < _filesList.length) {
String currDir = _filesList[i]['path'];
return new ListTile(
title: new Text(_filesList[i]['name']),
leading: (_filesList[i]['isDir'] == "false")
? new FutureBuilder<Uint8List>(
future: _getThumbnail(currDir),
builder: (BuildContext context,
AsyncSnapshot<Uint8List> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return new Container(
decoration: new BoxDecoration(
color: Colors.brown,
borderRadius: new BorderRadius.circular(20.0),
),
width: 30.0,
height: 30.0,
);
default:
if (snapshot.hasError)
return new Container(
decoration: new BoxDecoration(
color: Colors.brown,
borderRadius: new BorderRadius.circular(20.0),
),
width: 30.0,
height: 30.0,
);
else {
return new Image.memory(snapshot.data);
}
}
},
)
: new Container(
decoration: new BoxDecoration(
color: (_filesList[i]['isDir'] == "true")
? Colors.yellow
: Colors.brown,
borderRadius: new BorderRadius.circular(20.0),
),
width: 30.0,
height: 30.0),
onTap: () {
setState(() {
_currentDir = currDir;
});
_getFilesList(currDir);
},
);
} else {
return null;
}
},
);
}
Code for _getThumbnail():-
Future <Uint8List> _getThumbnail(path) async {
try {
String thumbnailString =
await channel.invokeMethod("getThumbnail", {"path": path});
thumbnailString = thumbnailString.replaceAll('\n', '');
Uint8List bytes = BASE64.decode(thumbnailString);
return bytes;
} on PlatformException catch (e) {
print("Files Not Found $e{e.message}");
return null;
}
}
Even though calls are asynchronous in Flutter, the code on Android that handles getThumbnail can block the UI thread, leading to frozen loading or choppy scrolling. A solution is to use AsyncTask to build/fetch the thumbnails in doInBackground and return the result in onPostExecute.