How to create custom slivers in flutter? - user-interface

I want to create a custom container under the sliver appbar like the right screen in the image
https://cdn.dribbble.com/users/1720296/screenshots/6918712/dribbble_blog_2x.jpg

You can use custom header with SliverPersistentHeaderDelegate,
This is your custom SliverPersistentHeaderDelegate
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
#override
double get minExtent => 100;
#override
double get maxExtent => 300;
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return Container(
...
);
}
#override
bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
return false;
}
}
And use it like this
SliverPersistentHeader(
delegate: _SliverAppBarDelegate(
...
),
pinned: true,
),

Related

Flutter How do I change the scroll speed in ListView on the mouse wheel?

I'm a beginner. I'm writing an application on Flutter under Windows. The problem is that the text in the ListView scrolls too slowly by the mouse clip. I tried to override ScrollPhysics, but it didn't work. Please give a working way to change the scrolling speed.
For people finding this post:
Based on the accepted answer above, this custom class that can be thrown in and used throughout an application.
Customized AdjustableScrollController
// scrollcontroller.dart
import 'dart:math';
import 'package:flutter/rendering.dart';
import 'package:flutter/material.dart';
class AdjustableScrollController extends ScrollController {
AdjustableScrollController([int extraScrollSpeed = 40]) {
super.addListener(() {
ScrollDirection scrollDirection = super.position.userScrollDirection;
if (scrollDirection != ScrollDirection.idle) {
double scrollEnd = super.offset +
(scrollDirection == ScrollDirection.reverse
? extraScrollSpeed
: -extraScrollSpeed);
scrollEnd = min(super.position.maxScrollExtent,
max(super.position.minScrollExtent, scrollEnd));
jumpTo(scrollEnd);
}
});
}
}
Usage
// your_file.dart
ListView(
controller: AdjustableScrollController() // default is 40
or
// your_file.dart
ListView(
controller: AdjustableScrollController(80) // scroll even faster
class ScrollViewTest extends StatelessWidget{
static const _extraScrollSpeed = 80; // your "extra" scroll speed
final ScrollController _scrollController = ScrollController();
// Constructor
ScrollViewTest({Key? key}) : super(key: key)
{
_scrollController.addListener(() {
ScrollDirection scrollDirection = _scrollController.position.userScrollDirection;
if (scrollDirection != ScrollDirection.idle)
{
double scrollEnd = _scrollController.offset + (scrollDirection == ScrollDirection.reverse
? _extraScrollSpeed
: -_extraScrollSpeed);
scrollEnd = min(
_scrollController.position.maxScrollExtent,
max(_scrollController.position.minScrollExtent, scrollEnd));
_scrollController.jumpTo(scrollEnd);
}
});
}
#override
Widget build(BuildContext context)
{
return SingleChildScrollView(
controller: _scrollController,
child: Container(...),
);
}
}

Is there a way to show default image if network/asset image fails to load for circle avatar?

I want a way to load placeholder image if network/asset image fails to load. here's what i have tried.
class CircularImageWidget extends StatelessWidget {
CircularImageWidget({
Key key,
#required this.radius,
#required this.assetPath,
#required this.imageType,
}) : super(key: key);
final double radius;
final String assetPath;
final ImageType imageType;
Image getErrorImage() {
return Image.asset(AssetUtils.profilepic);
}
ImageProvider returnImageAccordingToImageType() {
if (imageType == ImageType.Network) {
return NetworkImage(
assetPath,
);
} else {
return AssetImage(
assetPath,
);
}
}
#override
Widget build(BuildContext context) {
return CircleAvatar(
backgroundImage: returnImageAccordingToImageType() ??
AssetImage(AssetUtils.profilepic),
radius: radius,
onBackgroundImageError: (exception, stackTrace) {
return AssetImage(AssetUtils.profilepic);
},
);
}
}
You can achieve this by checking the URL of the image and displaying different widgets based on it.
#override
Widget build(BuildContext context) {
return CircleAvatar(
imageURL== null? backgroundImage:
AssetImage(AssetUtils.profilepic),
radius: radius,
):backgroundImage:
NetworkImage(imageURL),
radius: radius,
),
}

Flutter: Image not updating

Im doing a school project with flutter which has a list view of cards. The card has an image, which are stored in maps.
The map is this Map<String,Image> _imgMap, the key is the name attribute on the _objectList, which is a dynamic list. I pass the image to another class called Loader, which is a stateful widget that returns a container with circularProgress indicator while the image is loading and returns the image if finished.
ListView.builder(
itemCount: _objectList.length,
itemBuilder: (context,index){
return Card(
...
Row(
children<Widget>[
Container(child: Loader(image: _imgMap[_objectList[index].name]))
]
...
Loader Class:
class Loader extends StatefulWidget {
Image image;
Loader({Key key,this.image}) : super(key : key);
#override
_LoaderState createState() => _LoaderState(image);
}
class _LoaderState extends State<Loader> {
Image _image;
bool wait=true;
_LoaderState(this._image);
void initState(){
_image.image.resolve(ImageConfiguration()).addListener(
ImageStreamListener(
(info, call) {
setState(() {
wait = false;
});
},
),
);
}
#override
Widget build(BuildContext context) {
return wait ?CircleAvatar(
backgroundColor: Colors.transparent,
radius: 50,
child: simpleCircleLoading() //this is the circleprogress widget,
): Image(image: _image.image);
}
}
It works fine the first time, shows the correct image. But after adding a search function which lets the user search by name and then updates the list with only the matching one, the resulting image is the wrong one.
void search(String value) {
if(value.isNotEmpty){
List<dynamic> result = List<dynamic>();
_backUp.forEach((obj) { //_backup is the complete list of objects, will not change
if(obj.name.toLowerCase().contains(value.toLowerCase())) {
result.add(obj);
}
});
setState(() {
_objectList = result;
});
}else {
setState(() {
_objectList = _backUp;
});
}
}
Examples:
The initial list
Search result
As you can see the information on the card is updating but the image not.The problem is somewhere in the loader class i think since replacing the Loader in the listview with a Image widget fix the error, but i want to show a loader.
Found the error, i had to pass an UniqueKey to Loader class.

What is prefered widget for each frame repaint job at flutter sdk?

Im looking forward to test flutter memory/cpu usage. But I'm totaly lost at what widget do I pick for:
widget will contain custom canvas drawing(full screen)
widget must update itself 30 times per second(calling repaint from 0 each time)
in general, we have our own engine that revolves around uiview/surfaceview. I want to write same stuff on dart, connect to server, get same data, get same picture. But I dont unrestand what widget to take. As far as I see, I'l pick statefull widget and will change it state 30 timer per sec with timer. But that's not sounds right to me thought
You can use Ticker, which is the same mechanism that animations (i.e. AnimationController) use to update every frame.
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
void main() => runApp(const CanvasWidget());
class CanvasWidget extends StatefulWidget {
const CanvasWidget({super.key});
#override
State<CanvasWidget> createState() => _CanvasWidgetState();
}
class _CanvasWidgetState extends State<CanvasWidget> with SingleTickerProviderStateMixin {
final drawState = DrawState();
Ticker? ticker;
#override
void initState() {
super.initState();
ticker = createTicker(tick);
ticker!.start();
}
#override
void dispose() {
ticker?.stop();
ticker?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
height: double.infinity,
color: Colors.white,
child: CustomPaint(
painter: MyPainter(drawState),
),
);
}
void tick(Duration elapsed) {
var t = elapsed.inMicroseconds * 1e-6;
double radius = 100;
drawState.x = radius * math.sin(t);
drawState.y = radius * math.cos(t);
setState(() {});
}
}
class DrawState {
double x = 0, y = 0;
}
class MyPainter extends CustomPainter {
final DrawState state;
MyPainter(this.state);
#override
void paint(Canvas canvas, Size size) {
var paint = Paint()..color = Colors.red;
canvas.drawCircle(Offset(state.x + size.width * 0.5, state.y + size.height * 0.5), 20, paint);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}

Flutter: Hero transition + widget animation at the same time?

So, I'm having a bit of an issue with Flutter in regards to a specific animation case.
Basically, what I'm trying to do is simultaneously have both a hero transition run for a route change and a custom animation on an adjacent widget.
Broken down, I have a custom InheritedWidget at my root which is fed an app state from a StatefulWidget parent. Nested within my InheritedWidget, I have a WidgetsApp and an adjacent sibling for a custom tab navigation. The tree looks something like this:
Root Widget (Stateful)
|
|__InheritedWidget
|
|__WidgetsApp (Handles routing)
|
|__Navigation Bar (Overlay)
My issue arises when I on my WidgetsApp perform a route change which uses a Hero transition. While this is happening, I'm trying to also animate the Navigation Bar to either be shown or hidden depending on what view the user is on. But, since I'm using a bool variable on my app state to either show or hide the Navigation Bar via an animation, the SetState call there 'overwrites' the hero transition since the tree is rebuilt in the process (is what I'm thinking).
My initial thought was that the InheritedWidget would catch the app state change and only rebuild the Navigation Bar via updateShouldNotify, but alas this isn't what I'm seeing as the desired effect :(
So - has anyone tried anything similar, or have an idea as to how this could be handled gracefully? :)
I have done something similar, but unfortunately my code also contains a bunch of other stuff & this is relatively convoluted to do, so I'd have to split things out to make an example which is a bit more than I can do right now. I'll explain the general concept of what I did though. There may also be better ways of doing this.
You want to write a StatefulWidget with a State that also extends NavigatorObserver (you may be able to use a stateless widget but I don't think so). I personally put this above the navigator in the tree (i.e. it builds the navigator in its' build function), but you could most likely also have it 'beside' the navigator.
Override the didPush, didRemove, didPop etc methods from NavigatorObserver. Within each of these, call a setState and save the animation & other paramters, something like this:
class NavigationFaderState extends State<NavigationFader> with NavigatorObserver {
Animation _animation;
// whatever else you need, maybe starting/finishing opacity or position etc.
#override
void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
setState(() {
_animation = route.animation;
}
route.animation.addStatusListener((status) {
if (status = AnimationStatus.completed) {
setState(() {
_animation = null;
});
}
});
}
....
}
In your build function you'll want to check the _animation and animate based on whether it exists, and any other parameters you might want to set (i.e. a flag whether to animate, and whether the is going forward or backwards could be helpful - I believe the 'pop' animation have have started at 0 and gone to 1 the same as the push one but I could be wrong).
You can then hook up this animation to however you want to animate your navigation bar, probably using an AnimatedBuilder or hooking up the animation directly, or something. If there are any specific questions about how this all works, comment and I'll add some comments etc.
Hope that helps =)
EDIT: With full code example. For the record, I don't propose that this code is all that good, or that this is something you should do. But it is a way of solving the problem. Before using it in a real app, it would be worth testing it and probably adding some assertions to check for states etc.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
PushListener listener = new PushListener();
#override
Widget build(BuildContext context) {
return new WidgetsApp(
locale: new Locale("en"),
navigatorObservers: [listener],
builder: (context, child) {
// this is here rather than outside the WidgetsApp so that it
// gets access to directionality, text styles, etc
return new Scaffold(
body: child,
bottomNavigationBar:
new ColorChangingNavigationBar(key: listener.navBarKey),
);
},
onGenerateRoute: (settings) {
switch (settings.name) {
case '/':
return new MaterialPageRoute(
settings: settings,
builder: (context) => Column(
children: <Widget>[
new Text(
"I have a green nav bar when you open me and blue when you come back"),
new RaisedButton(
onPressed: () {
Navigator.pushNamed(context, "/red");
},
child: new Text("Next"),
),
],
),
);
case '/red':
return new MaterialPageRoute(
settings: settings,
builder: (context) => Column(
children: <Widget>[
new Text("I have a red nav bar"),
new RaisedButton(
onPressed: () {
Navigator.pop(context);
},
)
],
),
);
}
},
color: Colors.blue,
);
}
}
class PushListener extends NavigatorObserver {
GlobalKey<ColorChangingNavigationBarState> navBarKey = new GlobalKey();
#override
void didPop(Route route, Route previousRoute) {
if (route is ModalRoute && navBarKey.currentState != null) {
var name = route.settings.name;
var color = name == "/" ? Colors.red.shade500 : Colors.blue.shade500;
var animation = new ReverseAnimation(route.animation);
print("Popping & changing color to: ${name == "/" ? "red" : "blue"}");
navBarKey.currentState.setAnimating(animation, color);
}
}
#override
void didPush(Route route, Route previousRoute) {
if (route is ModalRoute && navBarKey.currentState != null) {
var name = route.settings.name;
var color = name == "/" ? Colors.blue.shade500 : Colors.red.shade500;
print("Pushing & changing color to: ${name == "/" ? "red" : "blue"}");
var animation = route.animation;
navBarKey.currentState.setAnimating(animation, color);
}
}
#override
void didRemove(Route route, Route previousRoute) {
// probably don't need
}
#override
void didStartUserGesture() {
// might want to do if gestures are supported with whichever type of
// route you're using.
}
#override
void didStopUserGesture() {
// if you implement didStartUserGesture
}
}
class ColorChangingNavigationBar extends StatefulWidget {
final Color startColor;
ColorChangingNavigationBar(
{Key key, this.startColor = const Color.fromRGBO(0, 255, 0, 1.0)})
: super(key: key);
#override
State<StatefulWidget> createState() => new ColorChangingNavigationBarState();
}
class _ColorAnimationInfo {
final Animation animation;
final Tween<Color> colorTween;
final AnimationStatusListener statusListener;
_ColorAnimationInfo(this.animation, this.colorTween, this.statusListener);
}
class ColorChangingNavigationBarState
extends State<ColorChangingNavigationBar> {
#override
void initState() {
_toColor = widget.startColor;
super.initState();
}
Color _toColor;
_ColorAnimationInfo _colorAnimationInfo;
void setAnimating(Animation animation, Color to) {
var fromColor;
if (_colorAnimationInfo != null) {
fromColor = _colorAnimationInfo.colorTween
.lerp(_colorAnimationInfo.animation.value);
_colorAnimationInfo.animation
.removeStatusListener(_colorAnimationInfo.statusListener);
} else {
fromColor = _toColor;
}
var statusListener = (state) {
if (state == AnimationStatus.completed ||
state == AnimationStatus.dismissed) {
setState(() {
_colorAnimationInfo = null;
});
}
};
animation.addStatusListener(statusListener);
setState(() {
_toColor = to;
Tween<Color> colorTween = new ColorTween(begin: fromColor, end: to);
_colorAnimationInfo =
new _ColorAnimationInfo(animation, colorTween, statusListener);
});
}
#override
Widget build(BuildContext context) {
if (_colorAnimationInfo != null) {
return new AnimatedBuilder(
animation: _colorAnimationInfo.animation,
builder: (context, child) {
return new Container(
color: _colorAnimationInfo.colorTween
.lerp(_colorAnimationInfo.animation.value),
height: 30.0,
);
});
} else {
return new Container(
color: _toColor,
height: 30.0,
);
}
}
#override
void dispose() {
if (_colorAnimationInfo != null) {
_colorAnimationInfo.animation.removeStatusListener(_colorAnimationInfo.statusListener);
}
_colorAnimationInfo = null;
super.dispose();
}
}

Resources